@sleep2agi/agent-network-dashboard 0.5.1-preview.1 → 0.5.1-preview.100

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 (253) 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 +6 -6
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +2 -2
  14. package/.next/server/app/_not-found.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/admin.html +2 -2
  23. package/.next/server/app/admin.rsc +2 -2
  24. package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
  25. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
  27. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  30. package/.next/server/app/index.html +2 -2
  31. package/.next/server/app/index.rsc +3 -3
  32. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  34. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  35. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  36. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  38. package/.next/server/app/login.html +2 -2
  39. package/.next/server/app/login.rsc +3 -3
  40. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  41. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  46. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  47. package/.next/server/app/logs.html +2 -2
  48. package/.next/server/app/logs.rsc +2 -2
  49. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  50. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  52. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  54. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  55. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  56. package/.next/server/app/messages.html +2 -2
  57. package/.next/server/app/messages.rsc +2 -2
  58. package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
  59. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  60. package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
  61. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  63. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  64. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app/node.html +2 -2
  66. package/.next/server/app/node.rsc +2 -2
  67. package/.next/server/app/node.segments/_full.segment.rsc +2 -2
  68. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  69. package/.next/server/app/node.segments/_index.segment.rsc +2 -2
  70. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  72. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  73. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/nodes.html +2 -2
  75. package/.next/server/app/nodes.rsc +2 -2
  76. package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
  77. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  78. package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
  79. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  81. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  82. package/.next/server/app/page_client-reference-manifest.js +1 -1
  83. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs.html +2 -2
  85. package/.next/server/app/server-logs.rsc +2 -2
  86. package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
  87. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  88. package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
  89. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  91. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/settings/networks.html +2 -2
  94. package/.next/server/app/settings/networks.rsc +2 -2
  95. package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
  96. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  97. package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
  98. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  99. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  100. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  101. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  102. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens.html +2 -2
  105. package/.next/server/app/settings/tokens.rsc +2 -2
  106. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
  109. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  110. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  111. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  112. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  113. package/.next/server/app/settings.html +2 -2
  114. package/.next/server/app/settings.rsc +3 -3
  115. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  116. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  117. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  118. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  121. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  122. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks.html +2 -2
  124. package/.next/server/app/tasks.rsc +2 -2
  125. package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
  126. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  127. package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
  128. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  130. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  131. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  132. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  133. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  134. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  137. package/.next/server/middleware-build-manifest.js +3 -3
  138. package/.next/server/pages/404.html +2 -2
  139. package/.next/server/pages/500.html +1 -1
  140. package/.next/static/chunks/0.pk4~tmpnb0_.js +1 -0
  141. package/.next/static/chunks/004_pmytjt5g..js +4 -0
  142. package/.next/static/chunks/017hq2-5l~_98.css +2 -0
  143. package/.next/static/chunks/0poutjg8.1a9n.js +1 -0
  144. package/.next/trace +2 -2
  145. package/.next/trace-build +1 -1
  146. package/app/components/TopoGraph.tsx +2126 -145
  147. package/package.json +1 -1
  148. package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
  149. package/scripts/topo-active-links-freshness-color-test.mjs +86 -0
  150. package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
  151. package/scripts/topo-chip-digit-hover-bold-test.mjs +94 -0
  152. package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -0
  153. package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
  154. package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
  155. package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
  156. package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
  157. package/scripts/topo-edge-badge-digit-fw-test.mjs +103 -0
  158. package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
  159. package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
  160. package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
  161. package/scripts/topo-edge-badge-letter-spacing-test.mjs +86 -0
  162. package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
  163. package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
  164. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  165. package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
  166. package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
  167. package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
  168. package/scripts/topo-edge-particle-radius-test.mjs +76 -0
  169. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  170. package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
  171. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  172. package/scripts/topo-filter-pill-value-fw-test.mjs +88 -0
  173. package/scripts/topo-filter-pill-weight-test.mjs +75 -0
  174. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  175. package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -0
  176. package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
  177. package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
  178. package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
  179. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  180. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  181. package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
  182. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  183. package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
  184. package/scripts/topo-group-label-letter-spacing-test.mjs +113 -0
  185. package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
  186. package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
  187. package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
  188. package/scripts/topo-hover-detail-rx-test.mjs +81 -0
  189. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  190. package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
  191. package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
  192. package/scripts/topo-hub-halo-radius-test.mjs +86 -0
  193. package/scripts/topo-hub-halo-trough-test.mjs +83 -0
  194. package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
  195. package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
  196. package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
  197. package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
  198. package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
  199. package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
  200. package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
  201. package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
  202. package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
  203. package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
  204. package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
  205. package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
  206. package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
  207. package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
  208. package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
  209. package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
  210. package/scripts/topo-minimap-online-radius-test.mjs +85 -0
  211. package/scripts/topo-minimap-viewport-hover-test.mjs +109 -0
  212. package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
  213. package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
  214. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  215. package/scripts/topo-more-flows-hover-letterspacing-test.mjs +98 -0
  216. package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
  217. package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
  218. package/scripts/topo-node-label-card-rx-test.mjs +85 -0
  219. package/scripts/topo-node-pulse-peak-test.mjs +89 -0
  220. package/scripts/topo-node-pulse-trough-test.mjs +91 -0
  221. package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
  222. package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
  223. package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
  224. package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
  225. package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
  226. package/scripts/topo-panel-title-hover-letterspacing-test.mjs +97 -0
  227. package/scripts/topo-pin-intersection-unit-test.mjs +100 -0
  228. package/scripts/topo-pressure-bar-height-test.mjs +92 -0
  229. package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
  230. package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
  231. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  232. package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
  233. package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
  234. package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
  235. package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
  236. package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
  237. package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
  238. package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
  239. package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
  240. package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
  241. package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
  242. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  243. package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
  244. package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
  245. package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
  246. package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
  247. package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
  248. package/.next/static/chunks/0wratil0li9xw.js +0 -4
  249. package/.next/static/chunks/10paf4uz-_0g1.js +0 -1
  250. package/.next/static/chunks/12wcm9omjb_x6.js +0 -1
  251. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_buildManifest.js +0 -0
  252. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_clientMiddlewareManifest.js +0 -0
  253. /package/.next/static/{4t8OH-lNFieRsHEC2eZfD → gcwnb1fGF5WXL0umdpAPw}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.1-preview.1",
3
+ "version": "0.5.1-preview.100",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -0,0 +1,93 @@
1
+ /* Round 399 verification: active-links chip closes the 3-chip
2
+ * chip-row by extending R398 hover translate-y-px lift onto the
3
+ * third (rightmost) chip. Gated to clickable variant (flowLinks
4
+ * > 0) so empty active-links chip stays planted.
5
+ *
6
+ * Contract:
7
+ * - With seeded messages (active flow links present):
8
+ * * data-chip-hover-lift === 'true'
9
+ * * data-active-links-clickable === 'true'
10
+ * * className includes 'hover:-translate-y-px'
11
+ * * className includes 'transition-transform' + 'transform-gpu'
12
+ * - Pre-R399 invariants preserved:
13
+ * * R232 tabular-nums + R313 font-medium + R266 chip-focus
14
+ * * R193 hover:bg-cyan-500/10 + hover:text-cyan-200 + hover:border-cyan-500/30
15
+ * * inline transition list still covers color/bg/border/opacity
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const mk = (alias, status) => ({
34
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [
39
+ mk('alpha', 'working'),
40
+ mk('beta', 'idle'),
41
+ mk('gamma', 'idle'),
42
+ ] } });
43
+ });
44
+ // Seed messages so flowLinks > 0 → chip becomes clickable
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
46
+ json: { messages: [
47
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
48
+ { id: 'm2', from_alias: 'alpha', to_alias: 'gamma', content: 'pong', created_at: fresh, network_id: 'default' },
49
+ ] },
50
+ }));
51
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
52
+
53
+ const page = await ctx.newPage();
54
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
55
+ await page.waitForSelector('[data-active-links-chip]', { timeout: 15000 });
56
+ await page.waitForTimeout(600);
57
+
58
+ const probe = await page.evaluate(() => {
59
+ const chip = document.querySelector('[data-active-links-chip]');
60
+ return chip ? {
61
+ className: chip.className,
62
+ lift: chip.getAttribute('data-chip-hover-lift'),
63
+ clickable: chip.getAttribute('data-active-links-clickable'),
64
+ flowCount: chip.getAttribute('data-active-links-flow-count'),
65
+ } : null;
66
+ });
67
+
68
+ await browser.close();
69
+
70
+ const cls = probe?.className || '';
71
+ const results = {
72
+ // Setup: active-links chip mounted with flow > 0
73
+ chip_mounted: !!probe,
74
+ flow_count_present: parseInt(probe?.flowCount || '0', 10) > 0,
75
+ // R399 lift attrs + className tokens (clickable variant)
76
+ lift_true: probe?.lift === 'true',
77
+ clickable_true: probe?.clickable === 'true',
78
+ has_hover_translate: cls.includes('hover:-translate-y-px'),
79
+ has_transition_transform: cls.includes('transition-transform'),
80
+ has_transform_gpu: cls.includes('transform-gpu'),
81
+ // Pre-R399 invariants
82
+ has_tabular_nums: cls.includes('tabular-nums'),
83
+ has_font_medium: cls.includes('font-medium'),
84
+ has_chip_focus: cls.includes('anet-topo-chip-focus'),
85
+ has_hover_cyan_bg: cls.includes('hover:bg-cyan-500/10'), // R193 invariant
86
+ has_hover_cyan_text: cls.includes('hover:text-cyan-200'),
87
+ has_hover_cyan_border: cls.includes('hover:border-cyan-500/30'),
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} active-links chip hover lift translateY(-1px):`, JSON.stringify(results),
91
+ '\n className:', cls,
92
+ '\n flowCount:', probe?.flowCount, '/ lift:', probe?.lift, '/ clickable:', probe?.clickable);
93
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,86 @@
1
+ /* Round 342 verification: active-links chip freshness suffix wrapper
2
+ * lifts text-gray-500 → text-gray-400 (R317 subordinate-text-lift
3
+ * family). Affects the literal "last {rel}" text; the freshness dot
4
+ * keeps its own inline color via `style={{color: dotColor}}`.
5
+ *
6
+ * Contract:
7
+ * - The wrapper span containing [data-active-links-freshness-dot]
8
+ * has className containing 'text-gray-400' (not 'text-gray-500').
9
+ * - Wrapper textContent includes 'last' (the trailing literal).
10
+ * - Freshness dot keeps its own color (not the wrapper's).
11
+ * - R341 pin-intersection " pins" unit + R317/R318 chrome
12
+ * regressions intact.
13
+ * - R294 pulse absent.
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
21
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
22
+ await ctx.addInitScript(() => {
23
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
24
+ });
25
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
26
+ await ctx.route('**/api/hub/status*', async (route) => {
27
+ const r = await route.fetch();
28
+ const b = await r.json();
29
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
30
+ const mk = (alias) => ({
31
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
32
+ network_id: nid, project_dir: null,
33
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
34
+ });
35
+ await route.fulfill({ response: r, json: { ...b, sessions: [
36
+ mk('alpha'), mk('beta'), mk('gamma'),
37
+ ] } });
38
+ });
39
+ // One recent message → active-links chip's freshness suffix renders.
40
+ const now = Date.now();
41
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
42
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping',
43
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
44
+ ] } }));
45
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
46
+
47
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
49
+ await page.waitForSelector('[data-active-links-freshness-dot]', { timeout: 15000 });
50
+ await page.waitForTimeout(300);
51
+
52
+ const probe = await page.evaluate(() => {
53
+ const dot = document.querySelector('[data-active-links-freshness-dot]');
54
+ const wrapper = dot?.parentElement || null;
55
+ return {
56
+ wrapperClass: wrapper?.className ?? '',
57
+ wrapperText: wrapper?.textContent ?? null,
58
+ dotInlineColor: dot?.getAttribute('style') ?? '',
59
+ // R341 regression.
60
+ pinIntersectionUnit: document.querySelector('[data-pin-intersection-unit]')?.className ?? '',
61
+ layoutInactiveCls: document.querySelector('[data-topo-chrome-layout="grid"]')?.className ?? '',
62
+ layoutActiveCls: document.querySelector('[data-topo-chrome-layout="ring"]')?.className ?? '',
63
+ pulseCount: document.querySelectorAll('[data-pulse-wrapper]').length,
64
+ };
65
+ });
66
+ await browser.close();
67
+
68
+ const results = {
69
+ wrapper_has_gray_400: /text-gray-400/.test(probe.wrapperClass),
70
+ wrapper_no_gray_500: !/text-gray-500/.test(probe.wrapperClass),
71
+ wrapper_text_last: /\blast\b/.test(probe.wrapperText || ''),
72
+ dot_has_inline_color: /color:/.test(probe.dotInlineColor),
73
+ // R341 regression — only triggers when a pin is active. Test
74
+ // doesn't click filters, so accept null OR opacity-70.
75
+ r341_pin_unit_or_absent: probe.pinIntersectionUnit === '' ||
76
+ /opacity-70/.test(probe.pinIntersectionUnit),
77
+ // R317 / R318 chrome regression.
78
+ r317_inactive_gray_400: probe.layoutInactiveCls.includes('text-gray-400'),
79
+ r318_active_font_medium: probe.layoutActiveCls.includes('font-medium'),
80
+ r294_pulse_absent: probe.pulseCount === 0,
81
+ };
82
+ const ok = Object.values(results).every(Boolean);
83
+ console.log(`${ok ? '✅' : '❌'} active-links freshness wrapper gray-400:`, JSON.stringify(results),
84
+ '\n wrapper class:', probe.wrapperClass,
85
+ '\n wrapper text:', JSON.stringify(probe.wrapperText));
86
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,105 @@
1
+ /* Round 362 verification: 3 chip-row chip digits (working / online /
2
+ * active-links) wrap their digit in a font-semibold span, lifting it
3
+ * fw 500 (chip-row R313 baseline) → fw 600 within each chip. The
4
+ * outer chip className keeps font-medium so the chip's overall data-
5
+ * weight stays consistent with the R313 family; the digit override
6
+ * adds a within-chip tier:
7
+ * digit fw 600 + full opacity
8
+ * unit fw 500 + R338 opacity-70
9
+ *
10
+ * Joins the R333-R341 chip-internal-hierarchy arc at the chip-count
11
+ * scope (8th surface family).
12
+ *
13
+ * Contract (cyber, fresh fixture):
14
+ * - Each of the 3 chip digit spans has data-{name}-chip-digit attr.
15
+ * - Computed fontWeight of the digit span === '600'.
16
+ * - The unit sibling span still has opacity 0.7 + data-{name}-chip-
17
+ * unit attr (R338 invariants).
18
+ * - Parent chip retains font-medium (R313 baseline).
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) => ({
36
+ alias, status: 'working', model: 'claude-opus-4', 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: [ mk('a'), mk('b'), mk('c') ] } });
41
+ });
42
+ const now = Date.now();
43
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
44
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
45
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
46
+ ] } }));
47
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForSelector('[data-working-chip-digit]', { timeout: 15000 });
52
+ await page.waitForSelector('[data-online-chip-digit]', { timeout: 5000 });
53
+ await page.waitForSelector('[data-active-links-chip-digit]', { timeout: 5000 });
54
+ await page.waitForTimeout(300);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ function readChip(digitSel, unitSel, parentSel) {
58
+ const digit = document.querySelector(digitSel);
59
+ const unit = document.querySelector(unitSel);
60
+ const parent = document.querySelector(parentSel);
61
+ return {
62
+ digitFw: digit ? getComputedStyle(digit).fontWeight : null,
63
+ digitText: digit?.textContent ?? null,
64
+ unitOp: unit ? getComputedStyle(unit).opacity : null,
65
+ unitPresent: !!unit,
66
+ parentCls: parent?.getAttribute('class') ?? null,
67
+ };
68
+ }
69
+ return {
70
+ working: readChip('[data-working-chip-digit]', '[data-working-chip-unit]', '[data-working-chip]'),
71
+ online: readChip('[data-online-chip-digit]', '[data-online-chip-unit]', '[data-online-chip]'),
72
+ activeLinks: readChip('[data-active-links-chip-digit]', '[data-active-links-chip-unit]', '[data-active-links-chip]'),
73
+ };
74
+ });
75
+
76
+ await browser.close();
77
+
78
+ const isFw600 = (v) => v === '600' || v === 'bold'; // Tailwind font-semibold === 600
79
+ const approxEq = (a, b, tol = 0.02) => {
80
+ const x = parseFloat(a); const y = parseFloat(b);
81
+ return Number.isFinite(x) && Number.isFinite(y) && Math.abs(x - y) <= tol;
82
+ };
83
+
84
+ const checkChip = (chip) => ({
85
+ digit_fw_600: isFw600(chip.digitFw),
86
+ unit_op_0_7: approxEq(chip.unitOp, 0.7), // R338
87
+ unit_present: chip.unitPresent,
88
+ parent_medium: /font-medium/.test(chip.parentCls || ''), // R313
89
+ });
90
+
91
+ const results = {
92
+ working_digit_fw_600: checkChip(probe.working).digit_fw_600,
93
+ working_unit_op_0_7: checkChip(probe.working).unit_op_0_7,
94
+ working_parent_medium: checkChip(probe.working).parent_medium,
95
+ online_digit_fw_600: checkChip(probe.online).digit_fw_600,
96
+ online_unit_op_0_7: checkChip(probe.online).unit_op_0_7,
97
+ online_parent_medium: checkChip(probe.online).parent_medium,
98
+ active_digit_fw_600: checkChip(probe.activeLinks).digit_fw_600,
99
+ active_unit_op_0_7: checkChip(probe.activeLinks).unit_op_0_7,
100
+ active_parent_medium: checkChip(probe.activeLinks).parent_medium,
101
+ };
102
+ const ok = Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} chip-row digit fontWeight 500 → 600:`, JSON.stringify(results),
104
+ '\n probe:', JSON.stringify(probe, null, 2));
105
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,94 @@
1
+ /* Round 416 verification (100-round milestone): chip-row chip digits
2
+ * gain group-hover:font-bold. Extends chip-internal-hierarchy with a
3
+ * hover-state weight lift — digits lift from fw=600 (R362 font-semibold)
4
+ * to fw=700 (font-bold) on chip hover, matching the R414 unit-brighten
5
+ * gesture at the same scope.
6
+ *
7
+ * Contract:
8
+ * - 3 chip digits with class tokens:
9
+ * * font-semibold (rest)
10
+ * * group-hover:font-bold
11
+ * * transition-[font-weight] duration-200
12
+ * - Parent chip className has `group` (R414 invariant)
13
+ * - Pre-R416 invariants:
14
+ * * data-*-chip-digit attrs present
15
+ * * unit spans group-hover:opacity-100 (R414 invariant)
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 fresh = new Date(Date.now() - 60 * 1000).toISOString();
22
+
23
+ const browser = await chromium.launch({ headless: true });
24
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
28
+ });
29
+ await ctx.route('**/api/hub/status*', async (route) => {
30
+ const r = await route.fetch();
31
+ const b = await r.json();
32
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
33
+ const mk = (alias, status) => ({
34
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
35
+ network_id: nid, project_dir: null,
36
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
37
+ });
38
+ await route.fulfill({ response: r, json: { ...b, sessions: [
39
+ mk('alpha', 'working'),
40
+ mk('beta', 'idle'),
41
+ ] } });
42
+ });
43
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
44
+ json: { messages: [
45
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
46
+ ] },
47
+ }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForSelector('[data-working-chip-digit]', { timeout: 15000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ const probe = await page.evaluate(() => {
56
+ const read = (selector) => {
57
+ const el = document.querySelector(selector);
58
+ return el ? {
59
+ className: el.className,
60
+ parentHasGroup: el.parentElement?.className.includes('group') === true,
61
+ } : null;
62
+ };
63
+ return {
64
+ working: read('[data-working-chip-digit]'),
65
+ online: read('[data-online-chip-digit]'),
66
+ activeLinks: read('[data-active-links-chip-digit]'),
67
+ };
68
+ });
69
+
70
+ await browser.close();
71
+
72
+ const check = (digit) => digit &&
73
+ digit.className.includes('font-semibold') &&
74
+ digit.className.includes('group-hover:font-bold') &&
75
+ digit.className.includes('transition-[font-weight]') &&
76
+ digit.className.includes('duration-200') &&
77
+ digit.parentHasGroup;
78
+
79
+ const results = {
80
+ // All 3 chip digits mounted
81
+ working_digit_mounted: !!probe.working,
82
+ online_digit_mounted: !!probe.online,
83
+ active_digit_mounted: !!probe.activeLinks,
84
+ // Each has all R416 + R414 invariants
85
+ working_full_set: check(probe.working),
86
+ online_full_set: check(probe.online),
87
+ active_full_set: check(probe.activeLinks),
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} chip-row chip digit group-hover:font-bold (R416 milestone):`, JSON.stringify(results),
91
+ '\n working:', probe.working?.className,
92
+ '\n online: ', probe.online?.className,
93
+ '\n active: ', probe.activeLinks?.className);
94
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,107 @@
1
+ /* Round 414 verification: chip-row chip unit spans gain group-hover-
2
+ * brighten — sibling to R355 filter pill inner-span hover-brighten.
3
+ * Chip-row 3 chips (working / online / active-links) get `group`
4
+ * parent + inner unit span `group-hover:opacity-100`.
5
+ *
6
+ * Contract:
7
+ * - 3 chips with data-chip-group-hover-brighten="true"
8
+ * - Each chip className includes `group`
9
+ * - Each chip's unit span (data-*-chip-unit) className includes:
10
+ * * opacity-70
11
+ * * transition-opacity
12
+ * * group-hover:opacity-100
13
+ * - Pre-R414 invariants preserved:
14
+ * * R398/R399 hover:-translate-y-px on clickable variant
15
+ * * R232 tabular-nums, font-medium
16
+ * * R266 anet-topo-chip-focus
17
+ */
18
+ import { chromium } from 'playwright';
19
+ import { readFileSync } from 'node:fs';
20
+
21
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
22
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
23
+
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
+ await ctx.route('**/api/hub/status*', async (route) => {
31
+ const r = await route.fetch();
32
+ const b = await r.json();
33
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
34
+ const mk = (alias, status) => ({
35
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
36
+ network_id: nid, project_dir: null,
37
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
38
+ });
39
+ await route.fulfill({ response: r, json: { ...b, sessions: [
40
+ mk('alpha', 'working'),
41
+ mk('beta', 'idle'),
42
+ ] } });
43
+ });
44
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({
45
+ json: { messages: [
46
+ { id: 'm1', from_alias: 'alpha', to_alias: 'beta', content: 'ping', created_at: fresh, network_id: 'default' },
47
+ ] },
48
+ }));
49
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
50
+
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
53
+ await page.waitForSelector('[data-chip-group-hover-brighten="true"]', { timeout: 15000 });
54
+ await page.waitForTimeout(400);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ const chips = Array.from(document.querySelectorAll('[data-chip-group-hover-brighten="true"]'));
58
+ const read = (chip) => {
59
+ const unit = chip.querySelector('[data-working-chip-unit], [data-online-chip-unit], [data-active-links-chip-unit]');
60
+ return {
61
+ chipClass: chip.className,
62
+ unitClass: unit?.className ?? null,
63
+ chipAttr: chip.getAttribute('data-working-chip') !== null ? 'working'
64
+ : chip.getAttribute('data-online-chip') !== null ? 'online'
65
+ : chip.getAttribute('data-active-links-chip') !== null ? 'active-links'
66
+ : 'unknown',
67
+ };
68
+ };
69
+ return {
70
+ count: chips.length,
71
+ chips: chips.map(read),
72
+ };
73
+ });
74
+
75
+ await browser.close();
76
+
77
+ const wc = probe.chips.find((c) => c.chipAttr === 'working');
78
+ const oc = probe.chips.find((c) => c.chipAttr === 'online');
79
+ const ac = probe.chips.find((c) => c.chipAttr === 'active-links');
80
+
81
+ const results = {
82
+ // 3 chips mounted
83
+ three_chips: probe.count === 3,
84
+ // working chip
85
+ working_has_group: wc?.chipClass.includes('group') === true,
86
+ working_unit_brighten: wc?.unitClass?.includes('group-hover:opacity-100') === true,
87
+ working_unit_transition: wc?.unitClass?.includes('transition-opacity') === true,
88
+ working_unit_opacity_70: wc?.unitClass?.includes('opacity-70') === true,
89
+ // online chip
90
+ online_has_group: oc?.chipClass.includes('group') === true,
91
+ online_unit_brighten: oc?.unitClass?.includes('group-hover:opacity-100') === true,
92
+ online_unit_transition: oc?.unitClass?.includes('transition-opacity') === true,
93
+ // active-links chip
94
+ active_has_group: ac?.chipClass.includes('group') === true,
95
+ active_unit_brighten: ac?.unitClass?.includes('group-hover:opacity-100') === true,
96
+ active_unit_transition: ac?.unitClass?.includes('transition-opacity') === true,
97
+ // Pre-R414 invariants
98
+ working_chip_focus: wc?.chipClass.includes('anet-topo-chip-focus') === true,
99
+ online_tabular_nums: oc?.chipClass.includes('tabular-nums') === true,
100
+ active_font_medium: ac?.chipClass.includes('font-medium') === true,
101
+ };
102
+ const ok = Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} chip-row chip group-hover-brighten:`, JSON.stringify(results),
104
+ '\n working:', wc,
105
+ '\n online: ', oc,
106
+ '\n active: ', ac);
107
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,95 @@
1
+ /* Round 398 verification: chip-row chips (working / online) gain
2
+ * hover translateY(-1px) lift on clickable variant only. Extends
3
+ * R397's filter-pill lift to the static header chips, gated to
4
+ * the chip's clickable role so empty chips stay planted.
5
+ *
6
+ * Contract:
7
+ * - Populated working chip (workingCount > 0):
8
+ * * data-chip-hover-lift === 'true'
9
+ * * className includes 'hover:-translate-y-px'
10
+ * * className includes 'transition-transform' + 'transform-gpu'
11
+ * - Populated online chip (onlineNodes.length > 0):
12
+ * * data-chip-hover-lift === 'true'
13
+ * * className includes 'hover:-translate-y-px'
14
+ * - Pre-R398 invariants preserved:
15
+ * * transition-colors duration-200 still present
16
+ * * R201 hover:bg + hover:border tokens still present
17
+ * * anet-topo-chip-focus + tabular-nums + font-medium present
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
24
+
25
+ const browser = await chromium.launch({ headless: true });
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+ await ctx.route('**/api/hub/status*', async (route) => {
32
+ const r = await route.fetch();
33
+ const b = await r.json();
34
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
35
+ const mk = (alias, status) => ({
36
+ alias, status, model: 'claude-opus-4', runtime: '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', 'working'),
42
+ mk('beta', 'idle'),
43
+ mk('gamma', 'idle'),
44
+ ] } });
45
+ });
46
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
47
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
48
+
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
51
+ await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
52
+ await page.waitForTimeout(400);
53
+
54
+ const probe = await page.evaluate(() => {
55
+ const work = document.querySelector('[data-working-chip]');
56
+ const online = document.querySelector('[data-online-chip]');
57
+ return {
58
+ work: work ? {
59
+ className: work.className,
60
+ lift: work.getAttribute('data-chip-hover-lift'),
61
+ clickable: work.getAttribute('data-working-chip-clickable'),
62
+ } : null,
63
+ online: online ? {
64
+ className: online.className,
65
+ lift: online.getAttribute('data-chip-hover-lift'),
66
+ clickable: online.getAttribute('data-online-chip-clickable'),
67
+ } : null,
68
+ };
69
+ });
70
+
71
+ await browser.close();
72
+
73
+ const wc = probe.work?.className || '';
74
+ const oc = probe.online?.className || '';
75
+ const results = {
76
+ // working chip: clickable + R398 lift attrs + className tokens
77
+ work_lift_true: probe.work?.lift === 'true',
78
+ work_clickable_true: probe.work?.clickable === 'true',
79
+ work_has_hover_translate: wc.includes('hover:-translate-y-px'),
80
+ work_has_transition_transform: wc.includes('transition-transform'),
81
+ work_has_transform_gpu: wc.includes('transform-gpu'),
82
+ work_has_transition_colors: wc.includes('transition-colors'), // R201 preserved
83
+ work_has_hover_bg: wc.includes('hover:bg-green-500/15'), // R201 preserved
84
+ work_has_chip_focus: wc.includes('anet-topo-chip-focus'),
85
+ // online chip mirror
86
+ online_lift_true: probe.online?.lift === 'true',
87
+ online_clickable_true: probe.online?.clickable === 'true',
88
+ online_has_hover_translate: oc.includes('hover:-translate-y-px'),
89
+ online_has_transition_transform: oc.includes('transition-transform'),
90
+ };
91
+ const ok = Object.values(results).every(Boolean);
92
+ console.log(`${ok ? '✅' : '❌'} chip-row chips hover lift translateY(-1px):`, JSON.stringify(results),
93
+ '\n workingClass:', wc,
94
+ '\n onlineClass: ', oc);
95
+ process.exit(ok ? 0 : 1);