@sleep2agi/agent-network-dashboard 0.5.3-preview.6 → 0.5.3-preview.61

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 (217) 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 +32 -32
  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 +12 -12
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  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 +14 -14
  24. package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
  25. package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
  26. package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
  27. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
  29. package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
  30. package/.next/server/app/index.html +2 -2
  31. package/.next/server/app/index.rsc +14 -14
  32. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  33. package/.next/server/app/index.segments/_full.segment.rsc +14 -14
  34. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  35. package/.next/server/app/index.segments/_index.segment.rsc +7 -7
  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 +14 -14
  40. package/.next/server/app/login.segments/_full.segment.rsc +14 -14
  41. package/.next/server/app/login.segments/_head.segment.rsc +4 -4
  42. package/.next/server/app/login.segments/_index.segment.rsc +7 -7
  43. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  45. package/.next/server/app/login.segments/login.segment.rsc +3 -3
  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 +14 -14
  49. package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
  50. package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
  51. package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
  52. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
  54. package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
  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 +14 -14
  58. package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
  59. package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
  60. package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
  61. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
  63. package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
  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 +14 -14
  67. package/.next/server/app/node.segments/_full.segment.rsc +14 -14
  68. package/.next/server/app/node.segments/_head.segment.rsc +4 -4
  69. package/.next/server/app/node.segments/_index.segment.rsc +7 -7
  70. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
  72. package/.next/server/app/node.segments/node.segment.rsc +3 -3
  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 +14 -14
  76. package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
  77. package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
  78. package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
  79. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
  81. package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
  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 +14 -14
  86. package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
  87. package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
  88. package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
  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 +4 -4
  91. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
  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 +14 -14
  95. package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
  96. package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
  97. package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
  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 +4 -4
  100. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
  101. package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
  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 +14 -14
  106. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
  107. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
  108. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
  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 +4 -4
  111. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
  112. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
  113. package/.next/server/app/settings.html +2 -2
  114. package/.next/server/app/settings.rsc +14 -14
  115. package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
  116. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  117. package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
  118. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  120. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  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 +14 -14
  125. package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
  126. package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
  127. package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
  128. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
  130. package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
  131. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
  132. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
  133. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  134. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  137. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  138. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  139. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
  140. package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js.map +1 -1
  141. package/.next/server/middleware-build-manifest.js +3 -3
  142. package/.next/server/pages/404.html +2 -2
  143. package/.next/server/pages/500.html +1 -1
  144. package/.next/static/chunks/{16qx24lc72~7v.js → 0.oqi5e71_k-b.js} +1 -1
  145. package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
  146. package/.next/static/chunks/0zop07q5qaq.x.js +4 -0
  147. package/.next/static/chunks/11ue0vx~aooky.css +2 -0
  148. package/.next/static/chunks/147n27~o0ha5z.js +1 -0
  149. package/.next/static/chunks/16_ei2l8ob67~.js +1 -0
  150. package/.next/trace +2 -2
  151. package/.next/trace-build +1 -1
  152. package/app/components/ServersDrawer.tsx +16 -3
  153. package/app/components/TopoGraph.tsx +1621 -80
  154. package/app/globals.css +55 -7
  155. package/package.json +4 -4
  156. package/scripts/p157-servers-copy-test.mjs +95 -0
  157. package/scripts/topo-alias-glow-test.mjs +121 -0
  158. package/scripts/topo-avatar-brightness-test.mjs +116 -0
  159. package/scripts/topo-chip-row-digit-ls-test.mjs +135 -0
  160. package/scripts/topo-chip-row-press-test.mjs +93 -0
  161. package/scripts/topo-cluster-count-attr-test.mjs +80 -0
  162. package/scripts/topo-crescent-breath-test.mjs +104 -0
  163. package/scripts/topo-crescent-recede-test.mjs +111 -0
  164. package/scripts/topo-edge-badge-hover-glow-test.mjs +90 -0
  165. package/scripts/topo-edge-pill-glow-test.mjs +67 -0
  166. package/scripts/topo-filter-pill-glow-test.mjs +90 -0
  167. package/scripts/topo-filter-pills-press-test.mjs +96 -0
  168. package/scripts/topo-fleet-density-tier-test.mjs +84 -0
  169. package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
  170. package/scripts/topo-fullscreen-attr-test.mjs +73 -0
  171. package/scripts/topo-grid-content-bottom-attr-test.mjs +72 -0
  172. package/scripts/topo-group-label-hover-glow-test.mjs +86 -0
  173. package/scripts/topo-group-pill-glow-test.mjs +76 -0
  174. package/scripts/topo-hub-digit-ls-test.mjs +119 -0
  175. package/scripts/topo-hub-halo-glow-test.mjs +96 -0
  176. package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
  177. package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
  178. package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
  179. package/scripts/topo-hub-highlight-r-test.mjs +112 -0
  180. package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
  181. package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
  182. package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
  183. package/scripts/topo-hub-idle-breath-test.mjs +109 -0
  184. package/scripts/topo-hub-recede-test.mjs +124 -0
  185. package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
  186. package/scripts/topo-layout-hover-fw-test.mjs +98 -0
  187. package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
  188. package/scripts/topo-legend-label-fw-test.mjs +107 -0
  189. package/scripts/topo-legend-swatch-glow-test.mjs +109 -0
  190. package/scripts/topo-minimap-hover-glow-test.mjs +109 -0
  191. package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
  192. package/scripts/topo-orphan-box-dash-test.mjs +89 -0
  193. package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
  194. package/scripts/topo-orphan-label-italic-test.mjs +90 -0
  195. package/scripts/topo-pill-x-rotate-test.mjs +96 -0
  196. package/scripts/topo-pinned-aspect-test.mjs +89 -0
  197. package/scripts/topo-pressure-seg-glow-test.mjs +92 -0
  198. package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
  199. package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
  200. package/scripts/topo-recent-more-fw-test.mjs +126 -0
  201. package/scripts/topo-recent-row-fw-test.mjs +115 -0
  202. package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
  203. package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
  204. package/scripts/topo-starfield-hue-test.mjs +109 -0
  205. package/scripts/topo-vendor-activelinks-press-test.mjs +100 -0
  206. package/scripts/topo-vendor-chip-glow-test.mjs +97 -0
  207. package/scripts/topo-vendor-pill-glow-test.mjs +98 -0
  208. package/scripts/topo-watermark-breath-test.mjs +100 -0
  209. package/scripts/topo-watermark-recede-test.mjs +114 -0
  210. package/scripts/topo-zoom-level-color-test.mjs +105 -0
  211. package/.next/static/chunks/018~fceya_6uk.css +0 -2
  212. package/.next/static/chunks/0ic678xqvd4ys.js +0 -4
  213. package/.next/static/chunks/0swbhc-5l4rz9.js +0 -1
  214. package/.next/static/chunks/17v63m4g4.i5h.js +0 -1
  215. /package/.next/static/{FpDDygQl1AAn4qwXBn3mt → htr2G1aFLFScIt2YRzHa7}/_buildManifest.js +0 -0
  216. /package/.next/static/{FpDDygQl1AAn4qwXBn3mt → htr2G1aFLFScIt2YRzHa7}/_clientMiddlewareManifest.js +0 -0
  217. /package/.next/static/{FpDDygQl1AAn4qwXBn3mt → htr2G1aFLFScIt2YRzHa7}/_ssgManifest.js +0 -0
package/app/globals.css CHANGED
@@ -863,6 +863,29 @@ body {
863
863
  transform-box: fill-box;
864
864
  }
865
865
 
866
+ /* Round 498 — recent-signal row hot-count subtle pulse (信息密度 +
867
+ 呼吸感 themes). When a row's edge count crosses the hot threshold
868
+ (≥ 10), the existing R127/R320/R445 amber-fill + fw-700 typography
869
+ already calls attention; R498 adds a slow 3s opacity breath (0.85 ↔
870
+ 1.0) on the digit so the hot tspans gently pulse — at-a-glance scan
871
+ of the panel reads "high-traffic lane right here" with motion in
872
+ addition to color+weight. 3s cycle is deliberate: faster (1-2s)
873
+ would compete with the working-status halo cadence; slower (5s+)
874
+ would lose the "alive" signal. Amplitude 15% (matches R497 hub-
875
+ highlight breath idiom).
876
+ prefers-reduced-motion handled by the R29 blanket override
877
+ (animation-duration: 0.001ms !important) — no per-class guard
878
+ needed. The component-side gate (`!reducedMotion && isHot`)
879
+ ensures the className is only applied when both conditions hold,
880
+ so even without the blanket the no-motion preference is respected. */
881
+ @keyframes anet-recent-hot-pulse-kf {
882
+ 0%, 100% { opacity: 0.85; }
883
+ 50% { opacity: 1; }
884
+ }
885
+ .anet-recent-hot-pulse {
886
+ animation: anet-recent-hot-pulse-kf 3s ease-in-out infinite;
887
+ }
888
+
866
889
  /* Round 36 — current-step ring pulse on TaskDrawer timeline. Halo gently
867
890
  breathes around the active step's dot so users can spot "task is here". */
868
891
  @keyframes anet-current-step-pulse-kf {
@@ -926,15 +949,40 @@ body {
926
949
  all preserved) plus `outline-color` for R490. All at 200ms ease-
927
950
  out, matching the chip-row's existing Tailwind duration-200 ease-
928
951
  out so visible animations on the chip are unchanged.
929
- Trade-off: chips with inline `style.transition` (e.g. R210
930
- pressure-bar segments at 220ms width / 150ms boxShadow / 150ms
931
- filter, line 2338 in TopoGraph.tsx) keep their inline value
932
- because inline style wins over class rules regardless of
933
- !important. That's intentional those custom timings were
934
- deliberate and stay scoped to their element. */
952
+
953
+ Round 524 / Loop CORRECTION + bug fix.
954
+ R490's original 'trade-off' note read:
955
+ 'chips with inline style.transition (R210 pressure-bar
956
+ segments at 220ms width / 150ms boxShadow / 150ms filter)
957
+ keep their inline value because inline style wins over class
958
+ rules regardless of !important.'
959
+ That was WRONG about CSS cascade. Per the spec, `!important`
960
+ author declarations DO override normal-priority inline-style
961
+ declarations. So R490's transition-property list silently
962
+ dropped every inline transition that targeted a property NOT
963
+ in this class's list — including R165's width 220ms tween on
964
+ the pressure-bar segments AND R210's filter brightness(1.2)
965
+ hover on those same segments. Both motion polishes have been
966
+ silently broken (snapping instead of easing) since R490
967
+ landed; banked as test-craft lesson in R523.
968
+ R524 fixes by extending the transition-property list to
969
+ include `width` and `filter` so the pressure-bar segments
970
+ (and any future chip-focus surface with width/filter motion)
971
+ animate per the class's unified 200ms ease-out. The cadence
972
+ shift (R165's 220ms → 200ms, R210's 150ms → 200ms) loses 20-
973
+ 50ms of intent precision but slots into the dashboard's
974
+ uniform 200ms motion vocabulary the rest of the chip family
975
+ already uses — net visible improvement (motion was DEAD,
976
+ now eases at 200ms; that's a strictly better state than
977
+ snapping).
978
+ Other chip-focus surfaces (filter pills, online/working
979
+ chips, minimap container, "+N more" footer's <g>) don't
980
+ change width or filter at runtime, so adding these
981
+ properties to the list is a no-op for them. */
935
982
  transition-property:
936
983
  outline-color, color, background-color, border-color,
937
- text-decoration-color, fill, stroke, opacity, box-shadow, transform
984
+ text-decoration-color, fill, stroke, opacity, box-shadow, transform,
985
+ width, filter
938
986
  !important;
939
987
  transition-duration: 200ms !important;
940
988
  transition-timing-function: ease-out !important;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.3-preview.6",
4
- "description": "Agent Network Dashboard Web UI for managing AI Agent networks",
3
+ "version": "0.5.3-preview.61",
4
+ "description": "Agent Network Dashboard \u2014 Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
7
7
  "build": "next build",
8
8
  "start": "next start",
9
9
  "lint": "eslint",
10
- "prepublishOnly": "[ -f .next/BUILD_ID ] || (echo 'prepublishOnly: .next/BUILD_ID missing run npm run build first (see commit 05c1ebf body for R224 chunk-500 root cause)' >&2 && exit 1)"
10
+ "prepublishOnly": "[ -f .next/BUILD_ID ] || (echo 'prepublishOnly: .next/BUILD_ID missing \u2014 run npm run build first (see commit 05c1ebf body for R224 chunk-500 root cause)' >&2 && exit 1)"
11
11
  },
12
12
  "bin": {
13
13
  "agent-network-dashboard": "./bin/start.js"
@@ -44,4 +44,4 @@
44
44
  "tailwindcss": "^4",
45
45
  "typescript": "^5"
46
46
  }
47
- }
47
+ }
@@ -0,0 +1,95 @@
1
+ /* #157 fix verification (v0.10.8 lean ship — Fix #1 only per 通信龙 5573).
2
+ *
3
+ * Pre-fix the Servers panel showed:
4
+ * "agent rollup pending hub ≥ 0.8.2-preview"
5
+ * "disk metric pending hub ≥ 0.8.2-preview"
6
+ * commhub-server@0.8.2 is LIVE on prod but still doesn't ship `agents[]`
7
+ * or `disk_*` — the version-pinned text was misleading.
8
+ *
9
+ * Post-fix:
10
+ * "agent rollup not reported by hub"
11
+ * "disk metric not reported by hub"
12
+ * + data-server-agents-missing + data-server-disk-missing test attrs.
13
+ *
14
+ * Test:
15
+ * 1. Open dashboard, expand Servers drawer (localStorage flag)
16
+ * 2. Expand first server card (localStorage flag)
17
+ * 3. Assert visible copy lacks "0.8.2-preview"
18
+ * 4. Assert test-surface attrs present
19
+ * 5. Source-side regex confirms new copy + attrs wired
20
+ */
21
+ import { chromium } from 'playwright';
22
+ import { readFileSync } from 'node:fs';
23
+
24
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
25
+
26
+ const browser = await chromium.launch({ headless: true });
27
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
28
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
29
+ await ctx.addInitScript(() => {
30
+ try {
31
+ localStorage.setItem('anet-theme', 'cyber');
32
+ sessionStorage.setItem('anet_v3_auth', '1');
33
+ // Force servers drawer open
34
+ localStorage.setItem('anet-servers-drawer', '1');
35
+ } catch {}
36
+ });
37
+ const page = await ctx.newPage();
38
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
39
+ await page.waitForTimeout(3500); // wait for SWR fetch + servers to render
40
+
41
+ // Expand first server card by clicking it
42
+ const firstCard = await page.$('[data-server-host], [data-server-card], button:has-text("iZ")');
43
+ // Simpler: just probe the drawer body text after expansion attempt
44
+ await page.evaluate(() => {
45
+ const buttons = Array.from(document.querySelectorAll('button'));
46
+ // Find server card expand toggles by their host text
47
+ const candidates = buttons.filter(b => /iZ|elaine/.test(b.textContent || ''));
48
+ if (candidates[0]) candidates[0].click();
49
+ });
50
+ await page.waitForTimeout(800);
51
+
52
+ const probe = await page.evaluate(() => {
53
+ // Probe drawer body for the placeholder text
54
+ const drawer = document.querySelector('[data-servers-body]') || document.body;
55
+ const text = drawer.textContent || '';
56
+ const agentsMissing = document.querySelectorAll('[data-server-agents-missing="true"]');
57
+ const diskMissing = document.querySelectorAll('[data-server-disk-missing="true"]');
58
+ return {
59
+ body_text_excerpt: text.slice(0, 1500),
60
+ has_stale_copy: /0\.8\.2-preview/.test(text),
61
+ has_new_agents_copy: /agent rollup not reported by hub/.test(text),
62
+ has_new_disk_copy: /disk metric not reported by hub/.test(text),
63
+ agents_missing_count: agentsMissing.length,
64
+ disk_missing_count: diskMissing.length,
65
+ };
66
+ });
67
+
68
+ await browser.close();
69
+
70
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/ServersDrawer.tsx', 'utf8');
71
+ // The OLD strings should be gone from rendered text. The comment block keeps the
72
+ // historical "0.8.2-preview" reference for audit — that's OK because comments
73
+ // don't render. So source check: look for the NEW visible strings AND the test
74
+ // attrs, not for the absence of "0.8.2-preview".
75
+ const sourceNewAgentsCopy = /agent rollup not reported by hub/.test(src);
76
+ const sourceNewDiskCopy = /disk metric not reported by hub/.test(src);
77
+ const sourceAgentsAttr = /data-server-agents-missing="true"/.test(src);
78
+ const sourceDiskAttr = /data-server-disk-missing="true"/.test(src);
79
+
80
+ const results = {
81
+ dom_no_stale_copy: !probe.has_stale_copy,
82
+ dom_new_agents_copy: probe.has_new_agents_copy,
83
+ dom_new_disk_copy: probe.has_new_disk_copy,
84
+ agents_attr_present: probe.agents_missing_count > 0,
85
+ disk_attr_present: probe.disk_missing_count > 0,
86
+ source_new_agents: sourceNewAgentsCopy,
87
+ source_new_disk: sourceNewDiskCopy,
88
+ source_agents_attr: sourceAgentsAttr,
89
+ source_disk_attr: sourceDiskAttr,
90
+ };
91
+ const ok = Object.values(results).every(Boolean);
92
+ console.log(`${ok ? '✅' : '❌'} #157 servers copy fix:`, JSON.stringify(results),
93
+ '\n agents_missing_count:', probe.agents_missing_count, ' disk_missing_count:', probe.disk_missing_count,
94
+ '\n body_excerpt:', probe.body_text_excerpt.replace(/\s+/g, ' ').slice(0, 400));
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,121 @@
1
+ /* Round 500 verification: node alias text gains a status-coloured
2
+ * drop-shadow on hover (extends R476-R481 drop-shadow visual-polish
3
+ * family to 7th anchor). Pre-R500 hover triggered card-lift + alias
4
+ * letter-spacing; post-R500 the alias glyph itself glows.
5
+ *
6
+ * Test scenarios:
7
+ * 1. Rest state — no hover: data-node-alias-glow='false', no filter
8
+ * 2. Hover state — after synthetic pointerenter dispatch on g[data-node]
9
+ * descendant: glow attr 'true', computed filter contains drop-shadow
10
+ * 3. reducedMotion: glow attr 'false' regardless of hover (a11y)
11
+ * 4. Source-side regex confirms wiring
12
+ */
13
+ import { chromium } from 'playwright';
14
+ import { readFileSync } from 'node:fs';
15
+
16
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
17
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
18
+
19
+ async function probe({ reducedMotion }) {
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({
22
+ viewport: { width: 1500, height: 1200 },
23
+ reducedMotion: reducedMotion ? 'reduce' : 'no-preference',
24
+ });
25
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
26
+ await ctx.addInitScript(() => {
27
+ try {
28
+ localStorage.setItem('anet-theme', 'cyber');
29
+ localStorage.setItem('anet-topo-layout', 'ring');
30
+ sessionStorage.setItem('anet_v3_auth', '1');
31
+ } catch {}
32
+ });
33
+ await ctx.route('**/api/hub/status*', async (route) => {
34
+ const r = await route.fetch();
35
+ const b = await r.json();
36
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
37
+ const mk = (alias, status) => ({
38
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
39
+ network_id: nid, project_dir: null,
40
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
41
+ });
42
+ await route.fulfill({ response: r, json: { ...b, sessions: [
43
+ mk('alpha·a1', 'working'),
44
+ mk('alpha·a2', 'idle'),
45
+ ] } });
46
+ });
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
48
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
49
+ const page = await ctx.newPage();
50
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
51
+ await page.waitForSelector('g[data-node]', { timeout: 15000 });
52
+ await page.waitForTimeout(1500);
53
+
54
+ // Phase 1: rest state
55
+ const rest = await page.evaluate(() => {
56
+ const text = document.querySelector('[data-node-alias-text]');
57
+ if (!text) return null;
58
+ return {
59
+ glow_attr: text.getAttribute('data-node-alias-glow'),
60
+ computed_filter: window.getComputedStyle(text).filter,
61
+ };
62
+ });
63
+
64
+ // Phase 2: hover state — synthetic pointerenter (R488 banked recipe)
65
+ const aliasAfterHover = await page.evaluate(() => {
66
+ const g = document.querySelector('g[data-node]');
67
+ if (!g) return null;
68
+ const alias = g.getAttribute('data-node');
69
+ const target = g.querySelector('circle, image, rect') || g;
70
+ ['pointerenter', 'pointerover', 'mouseenter', 'mouseover'].forEach((t) => {
71
+ target.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
72
+ });
73
+ return alias;
74
+ });
75
+ await page.waitForTimeout(400);
76
+ const hover = await page.evaluate(() => {
77
+ const text = document.querySelector('[data-node-alias-text]');
78
+ if (!text) return null;
79
+ return {
80
+ glow_attr: text.getAttribute('data-node-alias-glow'),
81
+ computed_filter: window.getComputedStyle(text).filter,
82
+ hovered_attr: text.getAttribute('data-node-alias-hovered'),
83
+ };
84
+ });
85
+
86
+ await browser.close();
87
+ return { rest, hover, alias: aliasAfterHover };
88
+ }
89
+
90
+ const motion = await probe({ reducedMotion: false });
91
+ const a11y = await probe({ reducedMotion: true });
92
+
93
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
94
+ const sourceFilter = /filter: !reducedMotion && hoveredAlias === session\.alias\s*\?\s*`drop-shadow\(0 0 2px \$\{status\.text\}80\)`\s*:\s*undefined/.test(src);
95
+ const sourceAttr = /data-node-alias-glow=\{!reducedMotion && hoveredAlias === session\.alias \? 'true' : 'false'\}/.test(src);
96
+ const sourceTransition = /transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out'/.test(src);
97
+
98
+ const results = {
99
+ // Motion fixture, rest: attr='false', filter='none'
100
+ motion_rest_attr_false: motion.rest && motion.rest.glow_attr === 'false',
101
+ motion_rest_filter_none: motion.rest && motion.rest.computed_filter === 'none',
102
+ // Motion fixture, hover: attr='true', filter contains drop-shadow
103
+ motion_alias_resolved: !!motion.alias,
104
+ motion_hover_attr_true: motion.hover && motion.hover.glow_attr === 'true',
105
+ motion_hover_filter_drop:motion.hover && /drop-shadow/.test(motion.hover.computed_filter || ''),
106
+ // a11y fixture: attr='false' even after hover (component-side gate)
107
+ a11y_rest_attr_false: a11y.rest && a11y.rest.glow_attr === 'false',
108
+ a11y_hover_attr_false: a11y.hover && a11y.hover.glow_attr === 'false',
109
+ a11y_hover_filter_none: a11y.hover && a11y.hover.computed_filter === 'none',
110
+ // Source
111
+ source_filter_wired: sourceFilter,
112
+ source_attr_wired: sourceAttr,
113
+ source_transition_wired: sourceTransition,
114
+ };
115
+ const ok = Object.values(results).every(Boolean);
116
+ console.log(`${ok ? '✅' : '❌'} R500 node alias glow:`, JSON.stringify(results),
117
+ '\n motion rest :', JSON.stringify(motion.rest),
118
+ '\n motion hover:', JSON.stringify(motion.hover),
119
+ '\n a11y rest :', JSON.stringify(a11y.rest),
120
+ '\n a11y hover:', JSON.stringify(a11y.hover));
121
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,116 @@
1
+ /* Round 501 verification: vendor avatar <image> inside node circles
2
+ * gains a hover-gated `filter: brightness(1.15)`. Closes the per-node
3
+ * hover-affordance arc — every per-node element type now has a hover
4
+ * treatment.
5
+ *
6
+ * Test fixture: claude-code-cli runtime → vendor.logo path renders the
7
+ * <image> branch (vs monogram fallback). Verifies:
8
+ * 1. rest: data-node-avatar-hovered='false', no brightness filter
9
+ * 2. hover (synthetic pointerenter): attr='true', computed filter
10
+ * contains brightness(1.15)
11
+ * 3. a11y: attr='false' even after hover (component gate)
12
+ * 4. source-side regex confirms wiring
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+
20
+ async function probe({ reducedMotion }) {
21
+ const browser = await chromium.launch({ headless: true });
22
+ const ctx = await browser.newContext({
23
+ viewport: { width: 1500, height: 1200 },
24
+ reducedMotion: reducedMotion ? 'reduce' : 'no-preference',
25
+ });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript(() => {
28
+ try {
29
+ localStorage.setItem('anet-theme', 'cyber');
30
+ localStorage.setItem('anet-topo-layout', 'ring');
31
+ sessionStorage.setItem('anet_v3_auth', '1');
32
+ } catch {}
33
+ });
34
+ await ctx.route('**/api/hub/status*', async (route) => {
35
+ const r = await route.fetch();
36
+ const b = await r.json();
37
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
38
+ const mk = (alias, status) => ({
39
+ // claude-code-cli renders the avatar <image> branch
40
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
41
+ network_id: nid, project_dir: null,
42
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
+ });
44
+ await route.fulfill({ response: r, json: { ...b, sessions: [
45
+ mk('alpha·a1', 'working'),
46
+ mk('alpha·a2', 'idle'),
47
+ ] } });
48
+ });
49
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
50
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
51
+ const page = await ctx.newPage();
52
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
53
+ await page.waitForSelector('g[data-node]', { timeout: 15000 });
54
+ await page.waitForTimeout(1500);
55
+
56
+ const rest = await page.evaluate(() => {
57
+ const img = document.querySelector('[data-node-avatar]');
58
+ if (!img) return { found: false };
59
+ return {
60
+ found: true,
61
+ hovered_attr: img.getAttribute('data-node-avatar-hovered'),
62
+ computed_filter: window.getComputedStyle(img).filter,
63
+ };
64
+ });
65
+
66
+ // Synthetic hover via R488 banked recipe
67
+ await page.evaluate(() => {
68
+ const g = document.querySelector('g[data-node]');
69
+ if (!g) return;
70
+ const target = g.querySelector('circle, image, rect') || g;
71
+ ['pointerenter', 'pointerover', 'mouseenter', 'mouseover'].forEach((t) => {
72
+ target.dispatchEvent(new Event(t, { bubbles: true, cancelable: true }));
73
+ });
74
+ });
75
+ await page.waitForTimeout(400);
76
+
77
+ const hover = await page.evaluate(() => {
78
+ const img = document.querySelector('[data-node-avatar]');
79
+ if (!img) return { found: false };
80
+ return {
81
+ found: true,
82
+ hovered_attr: img.getAttribute('data-node-avatar-hovered'),
83
+ computed_filter: window.getComputedStyle(img).filter,
84
+ };
85
+ });
86
+
87
+ await browser.close();
88
+ return { rest, hover };
89
+ }
90
+
91
+ const motion = await probe({ reducedMotion: false });
92
+ const a11y = await probe({ reducedMotion: true });
93
+
94
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
95
+ const sourceGate = /const isAvatarHovered = !reducedMotion && hoveredAlias === session\.alias;/.test(src);
96
+ const sourceFilter = /filter: isAvatarHovered \? 'brightness\(1\.15\)' : undefined/.test(src);
97
+ const sourceAttr = /data-node-avatar-hovered=\{isAvatarHovered \? 'true' : 'false'\}/.test(src);
98
+
99
+ const results = {
100
+ motion_rest_found: motion.rest.found,
101
+ motion_rest_attr_false: motion.rest.found && motion.rest.hovered_attr === 'false',
102
+ motion_rest_filter_none: motion.rest.found && motion.rest.computed_filter === 'none',
103
+ motion_hover_attr_true: motion.hover.found && motion.hover.hovered_attr === 'true',
104
+ motion_hover_filter_bright: motion.hover.found && /brightness\(1\.15\)/.test(motion.hover.computed_filter || ''),
105
+ a11y_rest_attr_false: a11y.rest.found && a11y.rest.hovered_attr === 'false',
106
+ a11y_hover_attr_false: a11y.hover.found && a11y.hover.hovered_attr === 'false',
107
+ a11y_hover_filter_none: a11y.hover.found && a11y.hover.computed_filter === 'none',
108
+ source_gate_wired: sourceGate,
109
+ source_filter_wired: sourceFilter,
110
+ source_attr_wired: sourceAttr,
111
+ };
112
+ const ok = Object.values(results).every(Boolean);
113
+ console.log(`${ok ? '✅' : '❌'} R501 avatar brightness:`, JSON.stringify(results),
114
+ '\n motion:', JSON.stringify(motion),
115
+ '\n a11y :', JSON.stringify(a11y));
116
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,135 @@
1
+ /* Round 539 verification: chip-row digit (working/online/active-links)
2
+ * gains group-hover:tracking-wide alongside existing R362 group-hover:
3
+ * font-bold. Hover-letter-spacing family 12th anchor across 3 siblings.
4
+ *
5
+ * Test phases:
6
+ * 1. rest each chip-digit: computed letterSpacing='normal' (=0)
7
+ * 2. hover the chip's parent (has `group` class — sets group-hover):
8
+ * digit's letterSpacing = ~0.025em
9
+ * 3. source-side regex confirms all 3 digits have the new class
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import { readFileSync } from 'node:fs';
13
+
14
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
15
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
16
+
17
+ const browser = await chromium.launch({ headless: true });
18
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
19
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
20
+ await ctx.addInitScript(() => {
21
+ try {
22
+ localStorage.setItem('anet-theme', 'cyber');
23
+ localStorage.setItem('anet-topo-layout', 'ring');
24
+ sessionStorage.setItem('anet_v3_auth', '1');
25
+ } catch {}
26
+ });
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias, status) => ({
32
+ alias, status, model: 'claude-opus-4', runtime: 'claude-code-cli',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ mk('a·1', 'working'), mk('a·2', 'idle'),
38
+ ] } });
39
+ });
40
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: {
41
+ messages: [{ id: 'm1', from_alias: 'a·1', to_alias: 'a·2', content: 't', created_at: fresh }]
42
+ } }));
43
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
44
+ const page = await ctx.newPage();
45
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
46
+ await page.waitForSelector('[data-working-chip-digit]', { timeout: 15000 });
47
+ await page.waitForTimeout(800);
48
+
49
+ async function probe(digitSel, chipParentSel) {
50
+ const rest = await page.evaluate((s) => {
51
+ const el = document.querySelector(s);
52
+ if (!el) return null;
53
+ return { ls: getComputedStyle(el).letterSpacing };
54
+ }, digitSel);
55
+ await page.hover(chipParentSel);
56
+ await page.waitForTimeout(350);
57
+ const hover = await page.evaluate((s) => {
58
+ const el = document.querySelector(s);
59
+ if (!el) return null;
60
+ return { ls: getComputedStyle(el).letterSpacing };
61
+ }, digitSel);
62
+ // move pointer away for next probe
63
+ await page.mouse.move(50, 50);
64
+ await page.waitForTimeout(200);
65
+ return { rest, hover };
66
+ }
67
+
68
+ // The chip-row chip <span class="group ...">; each has parent with `group`.
69
+ // Hover the chip's parent span — its child digit gets group-hover effects.
70
+ // The chip parent is the closest ancestor with `group` class. Easier: find
71
+ // the digit and walk up to nearest `.group` element via JS.
72
+ async function hoverChipDigit(sel) {
73
+ const handle = await page.evaluateHandle((s) => {
74
+ const el = document.querySelector(s);
75
+ if (!el) return null;
76
+ let p = el.parentElement;
77
+ while (p && !p.classList.contains('group')) p = p.parentElement;
78
+ return p;
79
+ }, sel);
80
+ return handle;
81
+ }
82
+
83
+ async function probeChip(digitSel) {
84
+ const rest = await page.evaluate((s) => {
85
+ const el = document.querySelector(s);
86
+ if (!el) return null;
87
+ return { ls: getComputedStyle(el).letterSpacing };
88
+ }, digitSel);
89
+ const groupHandle = await hoverChipDigit(digitSel);
90
+ await groupHandle.asElement()?.hover();
91
+ await page.waitForTimeout(350);
92
+ const hover = await page.evaluate((s) => {
93
+ const el = document.querySelector(s);
94
+ if (!el) return null;
95
+ return { ls: getComputedStyle(el).letterSpacing };
96
+ }, digitSel);
97
+ await page.mouse.move(50, 50);
98
+ await page.waitForTimeout(200);
99
+ return { rest, hover };
100
+ }
101
+
102
+ const working = await probeChip('[data-working-chip-digit]');
103
+ const online = await probeChip('[data-online-chip-digit]');
104
+ const activeLinks = await probeChip('[data-active-links-chip-digit]');
105
+
106
+ await browser.close();
107
+
108
+ // Source regex — all 3 chip digits should carry the new class string
109
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
110
+ const sourceWorking = /class[Nn]ame="font-semibold transition-\[font-weight,letter-spacing\] duration-200 group-hover:font-bold group-hover:tracking-wide" data-working-chip-digit/.test(src);
111
+ const sourceOnline = /class[Nn]ame="font-semibold transition-\[font-weight,letter-spacing\] duration-200 group-hover:font-bold group-hover:tracking-wide" data-online-chip-digit/.test(src);
112
+ const sourceAL = /class[Nn]ame="font-semibold transition-\[font-weight,letter-spacing\] duration-200 group-hover:font-bold group-hover:tracking-wide" data-active-links-chip-digit/.test(src);
113
+
114
+ const isRestLS = (r) => r?.ls === 'normal' || r?.ls === '0px';
115
+ // tracking-wide = 0.025em; on text-xs (12px) digit ≈ 0.3px (varies with computed font-size)
116
+ const isHoverLS = (r) => r?.ls && r.ls !== 'normal' && r.ls !== '0px' && parseFloat(r.ls) > 0.1;
117
+
118
+ const results = {
119
+ working_rest_normal: isRestLS(working?.rest),
120
+ working_hover_wide: isHoverLS(working?.hover),
121
+ online_rest_normal: isRestLS(online?.rest),
122
+ online_hover_wide: isHoverLS(online?.hover),
123
+ active_rest_normal: isRestLS(activeLinks?.rest),
124
+ active_hover_wide: isHoverLS(activeLinks?.hover),
125
+ source_working: sourceWorking,
126
+ source_online: sourceOnline,
127
+ source_active_links: sourceAL,
128
+ };
129
+ const ok = Object.values(results).every(Boolean);
130
+ console.log(`${ok ? '✅' : '❌'} R539 chip-row digit hover-tracking:`,
131
+ JSON.stringify(results, null, 2),
132
+ '\n working:', JSON.stringify(working),
133
+ '\n online:', JSON.stringify(online),
134
+ '\n active-links:', JSON.stringify(activeLinks));
135
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,93 @@
1
+ /* Round 494 verification: chip-row working + online chips gain
2
+ * `active:scale-95` press feedback, gated on the clickable branch
3
+ * (workingCount > 0 / onlineNodes.length > 0). Extends the chrome-
4
+ * strip press family (R492 Ring/Grid + R493 5 chrome buttons) into
5
+ * the chip-row scope.
6
+ *
7
+ * Verifies per chip:
8
+ * - DOM element resolvable (data-working-chip / data-online-chip)
9
+ * - className contains `active:scale-95` (since fixture has both chips
10
+ * with > 0 count → clickable branch active)
11
+ * - computed transition-property includes `transform`
12
+ * - source-file regex confirms class string wired
13
+ */
14
+ import { chromium } from 'playwright';
15
+ import { readFileSync } from 'node:fs';
16
+
17
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
18
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
19
+
20
+ const browser = await chromium.launch({ headless: true });
21
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1200 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try {
25
+ localStorage.setItem('anet-theme', 'cyber');
26
+ localStorage.setItem('anet-topo-layout', 'ring');
27
+ sessionStorage.setItem('anet_v3_auth', '1');
28
+ } 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
+ // Need both working and online > 0 so both chip variants enter clickable branch
40
+ await route.fulfill({ response: r, json: { ...b, sessions: [
41
+ mk('alpha·a1', 'working'),
42
+ mk('alpha·a2', 'idle'),
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
+ const page = await ctx.newPage();
48
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'networkidle' });
49
+ await page.waitForSelector('[data-working-chip]', { timeout: 15000 });
50
+ await page.waitForTimeout(1000);
51
+
52
+ const probe = async (sel) => {
53
+ return await page.evaluate((s) => {
54
+ const el = document.querySelector(s);
55
+ if (!el) return null;
56
+ const cs = window.getComputedStyle(el);
57
+ return {
58
+ cls: el.className || '',
59
+ cls_has_scale95: /active:scale-95/.test(el.className || ''),
60
+ cls_has_translate: /hover:-translate-y-px/.test(el.className || ''),
61
+ tp: cs.transitionProperty,
62
+ td: cs.transitionDuration,
63
+ tp_has_transform: /transform/i.test(cs.transitionProperty || ''),
64
+ };
65
+ }, sel);
66
+ };
67
+
68
+ const wInfo = await probe('[data-working-chip]');
69
+ const oInfo = await probe('[data-online-chip]');
70
+
71
+ await browser.close();
72
+
73
+ const src = readFileSync('/home/vansin/agent-network-dashboard/app/components/TopoGraph.tsx', 'utf8');
74
+ const sWorking = /workingCount > 0\s*\?\s*'bg-green-500\/10 text-green-300 border-green-500\/20 hover:bg-green-500\/15 hover:border-green-500\/30 hover:-translate-y-px active:scale-95'/.test(src);
75
+ const sOnline = /onlineNodes\.length > 0\s*\?\s*'bg-cyan-500\/10 text-cyan-300 border-cyan-500\/20 hover:bg-cyan-500\/15 hover:border-cyan-500\/30 hover:-translate-y-px active:scale-95'/.test(src);
76
+
77
+ const results = {
78
+ working_dom_found: !!wInfo,
79
+ working_has_scale95: wInfo && wInfo.cls_has_scale95,
80
+ working_has_lift: wInfo && wInfo.cls_has_translate,
81
+ working_tp_transform: wInfo && wInfo.tp_has_transform,
82
+ online_dom_found: !!oInfo,
83
+ online_has_scale95: oInfo && oInfo.cls_has_scale95,
84
+ online_has_lift: oInfo && oInfo.cls_has_translate,
85
+ online_tp_transform: oInfo && oInfo.tp_has_transform,
86
+ source_working_wired: sWorking,
87
+ source_online_wired: sOnline,
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} chip-row working+online active:scale-95 (R494):`, JSON.stringify(results),
91
+ '\n working tp:', wInfo && wInfo.tp,
92
+ '\n online tp:', oInfo && oInfo.tp);
93
+ process.exit(ok ? 0 : 1);