@sleep2agi/agent-network-dashboard 0.5.3-preview.1 → 0.5.3-preview.11

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 (168) 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 +7 -7
  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/[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/middleware-build-manifest.js +3 -3
  140. package/.next/server/pages/404.html +2 -2
  141. package/.next/server/pages/500.html +1 -1
  142. package/.next/static/chunks/03tmeg87bxqs3.js +4 -0
  143. package/.next/static/chunks/{0ce~u5hn~wege.js → 06kqj2vd..gul.js} +1 -1
  144. package/.next/static/chunks/09t2h9c0b1xjg.js +1 -0
  145. package/.next/static/chunks/0m.1mvl~t.avc.css +2 -0
  146. package/.next/static/chunks/0x3fl360u~cjf.js +1 -0
  147. package/.next/trace +2 -2
  148. package/.next/trace-build +1 -1
  149. package/app/components/TopoGraph.tsx +161 -21
  150. package/app/globals.css +91 -6
  151. package/package.json +1 -1
  152. package/scripts/topo-chip-row-press-test.mjs +93 -0
  153. package/scripts/topo-chrome-press-fullstrip-test.mjs +105 -0
  154. package/scripts/topo-chrome-press-scale-test.mjs +100 -0
  155. package/scripts/topo-filter-pills-press-test.mjs +96 -0
  156. package/scripts/topo-focus-outline-transition-test.mjs +107 -0
  157. package/scripts/topo-hover-ring-duration-test.mjs +87 -0
  158. package/scripts/topo-hub-idle-breath-test.mjs +104 -0
  159. package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
  160. package/scripts/topo-svg-focus-transition-test.mjs +105 -0
  161. package/scripts/topo-vendor-activelinks-press-test.mjs +100 -0
  162. package/.next/static/chunks/0a4hmfvj-81x5.css +0 -2
  163. package/.next/static/chunks/14m8prv3qgm45.js +0 -4
  164. package/.next/static/chunks/17fq.aa.hsdd..js +0 -1
  165. package/.next/static/chunks/17~las5t-t.kj.js +0 -1
  166. /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → hbG6dNDvul_c04Di9Ydwt}/_buildManifest.js +0 -0
  167. /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → hbG6dNDvul_c04Di9Ydwt}/_clientMiddlewareManifest.js +0 -0
  168. /package/.next/static/{cBZM18sgaGtUo-xccD_a3 → hbG6dNDvul_c04Di9Ydwt}/_ssgManifest.js +0 -0
@@ -1943,8 +1943,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1943
1943
  // doesn't list letter-spacing, so without this the
1944
1944
  // hover:tracking-wide would snap. Sibling change on
1945
1945
  // the Grid button below.
1946
- className={`px-2.5 py-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}
1947
- style={{ transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out' }}
1946
+ // Round 492 / Loop add `active:scale-95` press feedback
1947
+ // alongside R196's `active:bg-cyan-500/25` color-deepen.
1948
+ // Pre-R492 the chrome-strip Ring/Grid buttons had color
1949
+ // tactile (deeper cyan on mouse-down) + R249 chrome-pop
1950
+ // on release, but no transform during the press itself —
1951
+ // the button stayed planted between mouse-down and pop.
1952
+ // Adding `active:scale-95` (5% compression) on the
1953
+ // pressed pseudo-state, with `transform 150ms ease-out`
1954
+ // bundled into the inline transition list, gives haptic-
1955
+ // like push-back feedback. The press-down (down to 95%
1956
+ // scale) eases in over 150ms in sync with the bg/color
1957
+ // deepen; the release auto-springs back to scale-100 via
1958
+ // the same transition, then R249's anet-chrome-pop class
1959
+ // overlays the release-pop. Matching `transform-gpu`
1960
+ // promotes the layer so the scale doesn't trigger
1961
+ // layout/paint thrash. Sibling change on Grid below.
1962
+ className={`px-2.5 py-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide active:scale-95 transform-gpu ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}
1963
+ style={{ transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out, transform 150ms ease-out' }}
1948
1964
  >
1949
1965
  Ring
1950
1966
  </button>
@@ -1963,7 +1979,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1963
1979
  // all chrome buttons.
1964
1980
  // R351 sibling — Grid button picks up hover:tracking-wide
1965
1981
  // + inline transition spec. Same vocabulary as Ring.
1966
- className={`px-2.5 py-1 border-l focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}
1982
+ // R492 sibling Grid button picks up active:scale-95
1983
+ // press feedback + transform in transition list. Same
1984
+ // vocabulary as Ring above.
1985
+ className={`px-2.5 py-1 border-l focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide active:scale-95 transform-gpu ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}
1967
1986
  /* Round 268 / Loop: Grid button's left border (the
1968
1987
  internal divider between Ring and Grid) picks up
1969
1988
  pal.containerBorder, matching the wrapper change at
@@ -1973,8 +1992,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1973
1992
  transition list into the inline spec below so the
1974
1993
  letter-spacing tween rides alongside without snapping
1975
1994
  the border-color flip — border-color 200ms ease-out
1976
- keeps R268's theme-toggle smoothness intact. */
1977
- style={{ borderColor: pal.containerBorder, transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out' }}
1995
+ keeps R268's theme-toggle smoothness intact.
1996
+ R492 adds `transform 150ms ease-out` so active:scale-95
1997
+ eases smoothly. */
1998
+ style={{ borderColor: pal.containerBorder, transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out, transform 150ms ease-out' }}
1978
1999
  >
1979
2000
  Grid
1980
2001
  </button>
@@ -2091,9 +2112,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2091
2112
  // to R355 filter pin pill inner-span hover-brighten.
2092
2113
  // Hover-brighten family extends from filter pills to
2093
2114
  // chip-row chips at the inner-span scope.
2115
+ // Round 494 / Loop — chip-row working chip joins the
2116
+ // active:scale-95 press-feedback family (R492 Ring/Grid +
2117
+ // R493 chrome-strip rest). Gated on the clickable branch
2118
+ // (workingCount > 0) — when the chip is a placeholder
2119
+ // at count=0, scale-95 stays off to match the existing
2120
+ // R398 hover-lift conditional. Composes with hover:-
2121
+ // translate-y-px for the same lift-and-compress
2122
+ // tactile signature R493 brought to reset/fullscreen.
2094
2123
  className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
2095
2124
  workingCount > 0
2096
- ? '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'
2125
+ ? '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'
2097
2126
  : 'bg-green-500/10 text-green-300 border-green-500/20'
2098
2127
  }`}
2099
2128
  data-chip-hover-lift={workingCount > 0 ? 'true' : 'false'}
@@ -2193,9 +2222,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2193
2222
  same digit-jitter physics on count crossings). */
2194
2223
  // R398: hover translate-y lift on clickable variant — see working chip above.
2195
2224
  // R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
2225
+ // R494 sibling — online chip joins the active:scale-95 press
2226
+ // family (gated on onlineNodes.length > 0 clickable branch,
2227
+ // same conditional pattern as the working chip above).
2196
2228
  className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
2197
2229
  onlineNodes.length > 0
2198
- ? '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'
2230
+ ? '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'
2199
2231
  : 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'
2200
2232
  }`}
2201
2233
  data-chip-hover-lift={onlineNodes.length > 0 ? 'true' : 'false'}
@@ -2470,7 +2502,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2470
2502
  // R355: `group` lets the inner opacity-70 spans (prefix
2471
2503
  // `filter:` + count `· N`) brighten to 100 % on pill hover.
2472
2504
  // Sibling treatment on group + vendor pills below.
2473
- className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
2505
+ // R495 filter pills (3 sibling `group` variants) join the
2506
+ // active:scale-95 press-feedback family. R490's !important
2507
+ // transition list on .anet-topo-chip-focus already covers
2508
+ // transform, so just appending active:scale-95 to the
2509
+ // className wires the press tactile in one token. Compound
2510
+ // with R400-era hover:-translate-y-px gives lift-and-compress.
2511
+ className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
2474
2512
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
2475
2513
  onClick={() => setPinnedStatus(null)}
2476
2514
  style={{
@@ -2534,7 +2572,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2534
2572
  data-filter-match-count={matchCount}
2535
2573
  data-filter-match-aliases={matchAliases.join(',')}
2536
2574
  // R355 sibling — `group` parent + group-hover on inner spans.
2537
- className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
2575
+ // R495 filter pills (3 sibling `group` variants) join the
2576
+ // active:scale-95 press-feedback family. R490's !important
2577
+ // transition list on .anet-topo-chip-focus already covers
2578
+ // transform, so just appending active:scale-95 to the
2579
+ // className wires the press tactile in one token. Compound
2580
+ // with R400-era hover:-translate-y-px gives lift-and-compress.
2581
+ className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
2538
2582
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
2539
2583
  onClick={() => setPinnedGroup(null)}
2540
2584
  style={{
@@ -2600,7 +2644,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2600
2644
  data-filter-match-count={matchCount}
2601
2645
  data-filter-match-aliases={matchAliases.join(',')}
2602
2646
  // R355 sibling — `group` parent + group-hover on inner spans.
2603
- className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
2647
+ // R495 filter pills (3 sibling `group` variants) join the
2648
+ // active:scale-95 press-feedback family. R490's !important
2649
+ // transition list on .anet-topo-chip-focus already covers
2650
+ // transform, so just appending active:scale-95 to the
2651
+ // className wires the press tactile in one token. Compound
2652
+ // with R400-era hover:-translate-y-px gives lift-and-compress.
2653
+ className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
2604
2654
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
2605
2655
  onClick={() => setPinnedVendor(null)}
2606
2656
  style={{
@@ -2663,7 +2713,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2663
2713
  data-filter-match-count={link.count}
2664
2714
  data-filter-match-aliases={`${link.from},${link.to}`}
2665
2715
  data-active-filter-edge-hot={isHot ? 'true' : 'false'}
2666
- className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
2716
+ // R495 sibling 4th filter pill (no `group` prefix variant)
2717
+ // joins active:scale-95 press family alongside the 3 group
2718
+ // variants above. Same recipe.
2719
+ className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
2667
2720
  title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
2668
2721
  onClick={() => setPinnedEdgeKey(null)}
2669
2722
  style={{
@@ -3013,7 +3066,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
3013
3066
  // — sibling to R355 filter-pill prefix/suffix + R414
3014
3067
  // chip-row unit brighten. Closes the inner-span
3015
3068
  // hover-brighten family at the vendor chip surface.
3016
- className="group tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu hover:-translate-y-px"
3069
+ // R496 vendor letter chip joins active:scale-95 press
3070
+ // family. Last vendor-row clickable joining the family
3071
+ // R495 cashed via R490's transition-cascade dividend.
3072
+ // Same compound w/ R401 hover-lift idiom — lift-and-
3073
+ // compress on press, springs back on release.
3074
+ className="group tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu hover:-translate-y-px active:scale-95"
3017
3075
  data-vendor-letter={v.initial}
3018
3076
  data-vendor-letter-count={v.count}
3019
3077
  data-vendor-letter-hover-lift="true"
@@ -3251,9 +3309,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
3251
3309
  data-chip-hover-lift attr exposes the lift surface
3252
3310
  state ('true' clickable, 'false' empty) for tests. */
3253
3311
  // R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
3312
+ // R496 — active-links chip joins active:scale-95 press
3313
+ // family. Sibling to working+online chips (R494). Gated
3314
+ // on `isInteractive` (flowLinks.length > 0) — same R399
3315
+ // conditional pattern used for hover-lift.
3254
3316
  className={`group tabular-nums font-medium hidden sm:inline px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu ${
3255
3317
  isInteractive
3256
- ? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30 hover:-translate-y-px'
3318
+ ? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95'
3257
3319
  : 'bg-gray-500/10 text-gray-400 border-gray-500/20'
3258
3320
  }`}
3259
3321
  data-chip-hover-lift={isInteractive ? 'true' : 'false'}
@@ -6537,11 +6599,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6537
6599
  data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}
6538
6600
  data-topo-hub-highlight-radius="5.5"
6539
6601
  data-topo-hub-highlight-opacity={workingCount > 0 ? 0 : 0.95}
6602
+ data-topo-hub-highlight-breath={!reducedMotion && workingCount === 0 ? 'true' : 'false'}
6540
6603
  style={{
6541
6604
  pointerEvents: 'none',
6542
6605
  transition: 'opacity 300ms ease-out',
6543
6606
  }}
6544
- />
6607
+ >
6608
+ {/* Round 497 / Loop — idle-state breath (呼吸感 theme pivot
6609
+ from the R492-R496 press-family arc). Pre-R497 the hub
6610
+ idle highlight read as a static dim disc — present but
6611
+ motionless, visually mute. R497 adds a 4s opacity breath
6612
+ (0.85 ↔ 1.0 ↔ 0.85) so the hub reads "alive but quiet"
6613
+ instead of "frozen", giving the empty-fleet state a
6614
+ subtle living signature.
6615
+ Gates:
6616
+ - !reducedMotion (R29 a11y blanket) — reducedMotion
6617
+ users see static 0.95 disc, no animate
6618
+ - workingCount === 0 — when fleet is busy, the
6619
+ highlight is invisible (opacity=0) so the animate
6620
+ would waste paint cycles. Gating saves work.
6621
+ SMIL <animate> overrides the static opacity={0.95}
6622
+ during its run; falls back to 0.95 when reducedMotion
6623
+ flips on (the animate node simply doesn't render).
6624
+ 4s cycle is long enough to feel like ambient breath
6625
+ rather than a pulse, matching the "quiet" semantic.
6626
+ data-topo-hub-highlight-breath attr exposes the
6627
+ resolved gate state for tests. */}
6628
+ {!reducedMotion && workingCount === 0 && (
6629
+ <animate attributeName="opacity" values="0.85;1;0.85" dur="4s" repeatCount="indefinite" />
6630
+ )}
6631
+ </circle>
6545
6632
  {/* R115 / Loop: hover hint ring. Stroke-only circle at r=14
6546
6633
  that fades in when the hub is hovered — the same idea
6547
6634
  R44 used for node avatars (group-hover stroke). r=14
@@ -6884,7 +6971,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6884
6971
  in when the cursor enters the node, signalling clickability
6885
6972
  (real-user feedback for the chat-popover open). Pure CSS via
6886
6973
  Tailwind group-hover, so it costs nothing per frame and
6887
- respects prefers-reduced-motion via the global media query. */}
6974
+ respects prefers-reduced-motion via the global media query.
6975
+ Round 489 / Loop — duration harmonized from 150ms → 200ms
6976
+ to join the Hero D #147 motion-coherence stack (R459-R475
6977
+ cluster surfaces + cadence-sync family). R2 originally
6978
+ picked 150ms for a "snappier feel" before the 200ms ease-
6979
+ out vocabulary was banked as the canvas-wide motion
6980
+ default. Bringing this ring into the family means hover-
6981
+ in / hover-out / cluster cadence / pip-strip transitions
6982
+ all settle on the same timing — the canvas now reads as
6983
+ one motion vocabulary instead of two competing tempos.
6984
+ 11th surface in the motion-coherence stack. */}
6888
6985
  <circle
6889
6986
  cx={pos.x}
6890
6987
  cy={pos.y}
@@ -6896,7 +6993,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6896
6993
  // exact widths and would mis-count this invisible hover
6897
6994
  // ring as a node footprint.
6898
6995
  strokeWidth="2"
6899
- className="opacity-0 group-hover:opacity-70 transition-opacity duration-150"
6996
+ className="opacity-0 group-hover:opacity-70 transition-opacity duration-200"
6900
6997
  style={{ pointerEvents: 'none' }}
6901
6998
  />
6902
6999
  {/* Round 11 / Loop: chat-focus ring — when the ChatPopover is
@@ -9368,12 +9465,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9368
9465
  under the same R320 fill cadence. data-
9369
9466
  recent-row-count-pinned attr exposes the
9370
9467
  pin gate for tests. */}
9468
+ {/* Round 498 / Loop — hot-count subtle pulse. Pre-
9469
+ R498 the hot row count signaled via color (R127
9470
+ amber fill) + weight (R320 fw-700) + (R445 pin
9471
+ lift) but stayed visually motionless. R498 adds
9472
+ a 3s opacity breath (0.85↔1.0) on the digit when
9473
+ isHot && !reducedMotion — gentle "alive" signal
9474
+ on the lane carrying ≥ 10 messages, drawing
9475
+ glance without becoming noisy. Sibling of R497
9476
+ hub-idle-breath in the 呼吸感 theme arc; same
9477
+ 0.85↔1.0 amplitude. Class adds an animation-
9478
+ only paint axis; no layout / bbox change. R29
9479
+ blanket also catches `animation-duration` for
9480
+ reducedMotion users, but the component-side
9481
+ gate makes the intent explicit and avoids
9482
+ a node tree thrash for those users (className
9483
+ stays absent rather than present-but-paused). */}
9371
9484
  <tspan
9372
9485
  fill={isHot ? hotStroke : undefined}
9373
9486
  fontWeight={(isHot || isRowPinned) ? '700' : '600'}
9487
+ className={isHot && !reducedMotion ? 'anet-recent-hot-pulse' : undefined}
9374
9488
  data-recent-row-count
9375
9489
  data-recent-row-count-pinned={isRowPinned ? 'true' : 'false'}
9376
9490
  data-recent-row-count-font-weight={(isHot || isRowPinned) ? '700' : '600'}
9491
+ data-recent-row-count-hot-pulse={isHot && !reducedMotion ? 'true' : 'false'}
9377
9492
  {...(isHot ? { 'data-recent-row-count-hot': 'true' } : {})}
9378
9493
  style={{
9379
9494
  transition: 'fill 300ms ease-out, font-weight 200ms ease-out',
@@ -10957,7 +11072,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
10957
11072
  / fullscreen) preview their active state on hover.
10958
11073
  Pure actions (zoom -/+, reset) stay white — they
10959
11074
  aren't toggles, have no active state to preview. */
10960
- className={`px-2 py-1 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}
11075
+ // Round 493 / Loop extends R492 chrome-strip press-feedback
11076
+ // family to nodeSize S/M/L buttons. Adds active:scale-95
11077
+ // alongside the existing color-deepen (R196) + chrome-pop
11078
+ // (R249). transition-transform + duration-200 + ease-out
11079
+ // + transform-gpu added since this className previously had
11080
+ // transition-colors only — without the transform transition,
11081
+ // active:scale-95 would hard-cut. transform-gpu promotes the
11082
+ // layer so scale doesn't trigger paint thrash.
11083
+ className={`px-2 py-1 transition-colors transition-transform duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}
10961
11084
  style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}
10962
11085
  >
10963
11086
  {lbl}
@@ -10999,7 +11122,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
10999
11122
  // → white/10) so mouse-down has a tactile dim before the
11000
11123
  // R186 icon pop fires on release.
11001
11124
  // R352: `group` lets the inner svg respond via group-hover.
11002
- className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
11125
+ // R493 zoom +/− buttons join the chrome-strip active:scale-95
11126
+ // press-feedback family (R492 + nodeSize above). transition-
11127
+ // transform + duration-200 + ease-out + transform-gpu added
11128
+ // since the className had only transition-colors.
11129
+ className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors transition-transform duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
11003
11130
  style={{ color: pal.legendText }}
11004
11131
  aria-label="Zoom out"
11005
11132
  title="Zoom out (−)"
@@ -11139,7 +11266,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
11139
11266
  data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}
11140
11267
  // R196: press-state (mirror of zoom-out above).
11141
11268
  // R352: `group` lets the inner svg respond via group-hover.
11142
- className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
11269
+ // R493 zoom +/− buttons join the chrome-strip active:scale-95
11270
+ // press-feedback family (R492 + nodeSize above). transition-
11271
+ // transform + duration-200 + ease-out + transform-gpu added
11272
+ // since the className had only transition-colors.
11273
+ className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors transition-transform duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
11143
11274
  style={{ color: pal.legendText }}
11144
11275
  aria-label="Zoom in"
11145
11276
  title="Zoom in (+)"
@@ -11191,7 +11322,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
11191
11322
  Every standalone interactive HTML surface in TopoGraph
11192
11323
  now lifts on hover. data-topo-chrome-reset-hover-lift
11193
11324
  attr surfaces the lift for tests. */
11194
- className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
11325
+ // R493 reset button joins the chrome-strip active:scale-95
11326
+ // press-feedback family. The button already has transition-
11327
+ // transform + transform-gpu (R350 reset spin + R400 hover lift),
11328
+ // so just appending active:scale-95 plugs straight in. Compound
11329
+ // active state during press = hover-lift (-1px) + scale-95
11330
+ // composes as translateY(-1px) scale(0.95) — lift-and-compress
11331
+ // for tactile click feel.
11332
+ className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px active:scale-95 transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
11195
11333
  data-topo-chrome-reset-hover-lift="true"
11196
11334
  style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
11197
11335
  aria-label="Reset view"
@@ -11284,7 +11422,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
11284
11422
  // fullscreen now all carry an icon-level hover gesture in
11285
11423
  // addition to the bg hover).
11286
11424
  // R400: hover translateY(-1px) lift — see reset button above for family doc.
11287
- className={`group p-1.5 rounded-md border hover:-translate-y-px transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
11425
+ // R493 fullscreen joins active:scale-95 press family (same as
11426
+ // reset above: lift-and-compress compound transform on press).
11427
+ className={`group p-1.5 rounded-md border hover:-translate-y-px active:scale-95 transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
11288
11428
  isFullscreen
11289
11429
  ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'
11290
11430
  : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'
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 {
@@ -900,10 +923,47 @@ body {
900
923
  and doesn't fight box-shadow, so we use it here. `currentColor`
901
924
  inherits the chip's own accent (green for working, cyan for online,
902
925
  per-vendor for letters, etc.). Only fires on :focus-visible
903
- (keyboard) — mouse focus stays unstyled. */
904
- .anet-topo-chip-focus:focus-visible {
905
- outline: 2px solid currentColor;
926
+ (keyboard) — mouse focus stays unstyled.
927
+ Round 490 / Loop — focus outline transitions on color rather than
928
+ hard-cutting. Pre-R490 the keyboard focus ring snapped in/out
929
+ instantly (no `transition` declaration), while every other hover/
930
+ pin state on the same chip eased through the Hero D 200ms ease-out
931
+ vocabulary (R459-R475 cluster + R489 hover ring). Keyboard users
932
+ tabbing through chips saw discontinuous focus jumps — pointer
933
+ users saw smooth motion. R490 unifies the two by holding a
934
+ permanent transparent outline (no painted footprint, no layout
935
+ shift — `outline` is drawn outside the border-box and doesn't
936
+ trigger reflow) and transitioning ONLY outline-color when focus
937
+ arrives/leaves. The cyber-theme dark canvas keyboard nav now
938
+ reads as one motion vocabulary with pointer hover. */
939
+ .anet-topo-chip-focus {
940
+ outline: 2px solid transparent;
906
941
  outline-offset: 1px;
942
+ /* R490 (revised) — Tailwind utility classes on the chips (transition-
943
+ colors / transition-transform / duration-200) set their own
944
+ `transition-property` list at the same selector specificity (0,1,0),
945
+ and since Tailwind's stylesheet loads after globals.css they win
946
+ the cascade — without `!important` my outline-color transition is
947
+ ignored. The unified list below replaces Tailwind's narrower list
948
+ with a superset (color, bg, border, opacity, box-shadow, transform
949
+ all preserved) plus `outline-color` for R490. All at 200ms ease-
950
+ out, matching the chip-row's existing Tailwind duration-200 ease-
951
+ out so visible animations on the chip are unchanged.
952
+ Trade-off: chips with inline `style.transition` (e.g. R210
953
+ pressure-bar segments at 220ms width / 150ms boxShadow / 150ms
954
+ filter, line 2338 in TopoGraph.tsx) keep their inline value
955
+ because inline style wins over class rules regardless of
956
+ !important. That's intentional — those custom timings were
957
+ deliberate and stay scoped to their element. */
958
+ transition-property:
959
+ outline-color, color, background-color, border-color,
960
+ text-decoration-color, fill, stroke, opacity, box-shadow, transform
961
+ !important;
962
+ transition-duration: 200ms !important;
963
+ transition-timing-function: ease-out !important;
964
+ }
965
+ .anet-topo-chip-focus:focus-visible {
966
+ outline-color: currentColor;
907
967
  }
908
968
 
909
969
  /* R156 — focus-visible outline for SVG-side TopoGraph interactives.
@@ -914,10 +974,35 @@ body {
914
974
  R144/R151/R152). Browser default focus outline on SVG is hard
915
975
  to spot against the canvas; explicit cyan-300 ring matches the
916
976
  dashboard's legendAccent. 2-px outline-offset gives breathing
917
- room around the painted bounding box. */
918
- .anet-topo-svg-focus:focus-visible {
919
- outline: 2px solid #67e8f9;
977
+ room around the painted bounding box.
978
+ Round 491 / Loop — SVG-side counterpart to R490's chip-focus
979
+ outline-color transition. Pre-R491 keyboard focus on SVG g
980
+ elements (recent rows, legend rows, group labels, edge badges,
981
+ nodes, "+N more") snapped instantly; HTML chips (post-R490) now
982
+ ease through 200ms ease-out but SVG g still hard-cut. Same
983
+ baseline-transparent + transition-outline-color recipe brings
984
+ the SVG canvas into the unified keyboard motion vocabulary —
985
+ keyboard users tabbing between chip-row and SVG canvas see
986
+ ONE smooth fade timing instead of HTML-smooth-then-SVG-snap.
987
+ The cyan-300 (#67e8f9) target color matches R156's original
988
+ choice (legendAccent visual identity); only the transition
989
+ timing is new.
990
+ Note on cascade: SVG g elements rarely carry Tailwind
991
+ transition-* utility classes (Tailwind defaults are HTML-
992
+ centric), so the (0,1,0) specificity of .anet-topo-svg-focus
993
+ typically wins without !important. Adding !important defensively
994
+ anyway since the React component may add future inline styles
995
+ or Tailwind classes. Same trade-off as R490 (inline
996
+ style.transition still wins if added). */
997
+ .anet-topo-svg-focus {
998
+ outline: 2px solid transparent;
920
999
  outline-offset: 2px;
1000
+ transition-property: outline-color !important;
1001
+ transition-duration: 200ms !important;
1002
+ transition-timing-function: ease-out !important;
1003
+ }
1004
+ .anet-topo-svg-focus:focus-visible {
1005
+ outline-color: #67e8f9;
921
1006
  }
922
1007
 
923
1008
  /* Round 46 — animated dashed spokes flowing from hub outward. Each spoke
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.3-preview.1",
3
+ "version": "0.5.3-preview.11",
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 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);