@sleep2agi/agent-network-dashboard 0.4.6-preview.161 → 0.4.6-preview.163

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 (157) 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 +4 -4
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +2 -2
  15. package/.next/server/app/_not-found.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/admin.html +2 -2
  24. package/.next/server/app/admin.rsc +2 -2
  25. package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
  26. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  27. package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  30. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  31. package/.next/server/app/index.html +2 -2
  32. package/.next/server/app/index.rsc +3 -3
  33. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  34. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  35. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  36. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  37. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  38. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  39. package/.next/server/app/login.html +2 -2
  40. package/.next/server/app/login.rsc +3 -3
  41. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  42. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  43. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  46. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  47. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/logs.html +2 -2
  49. package/.next/server/app/logs.rsc +2 -2
  50. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  51. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  52. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  55. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  56. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/messages.html +2 -2
  58. package/.next/server/app/messages.rsc +2 -2
  59. package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
  60. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  64. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  65. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app/node.html +2 -2
  67. package/.next/server/app/node.rsc +2 -2
  68. package/.next/server/app/node.segments/_full.segment.rsc +2 -2
  69. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  70. package/.next/server/app/node.segments/_index.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  72. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  73. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  74. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/nodes.html +2 -2
  76. package/.next/server/app/nodes.rsc +2 -2
  77. package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
  78. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  79. package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  81. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  82. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  83. package/.next/server/app/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/server-logs.html +2 -2
  86. package/.next/server/app/server-logs.rsc +2 -2
  87. package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
  88. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  89. package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
  90. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  91. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  92. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  93. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  94. package/.next/server/app/settings/networks.html +2 -2
  95. package/.next/server/app/settings/networks.rsc +2 -2
  96. package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
  97. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  98. package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
  99. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  100. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  101. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  102. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  103. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/settings/tokens.html +2 -2
  106. package/.next/server/app/settings/tokens.rsc +2 -2
  107. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
  108. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  109. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
  110. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  111. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  112. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  113. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  114. package/.next/server/app/settings.html +2 -2
  115. package/.next/server/app/settings.rsc +3 -3
  116. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  117. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  118. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  121. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  122. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/tasks.html +2 -2
  125. package/.next/server/app/tasks.rsc +2 -2
  126. package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
  127. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  128. package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  130. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  131. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  132. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  133. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  134. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  137. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  138. package/.next/server/middleware-build-manifest.js +3 -3
  139. package/.next/server/pages/404.html +2 -2
  140. package/.next/server/pages/500.html +1 -1
  141. package/.next/server/server-reference-manifest.js +1 -1
  142. package/.next/server/server-reference-manifest.json +1 -1
  143. package/.next/static/chunks/{10z.kc0g0jj8p.js → 0l1bmg_n.dezi.js} +1 -1
  144. package/.next/static/chunks/{032~4k7il7hnk.css → 0m7e_hszjk30d.css} +1 -1
  145. package/.next/static/chunks/{0oehx47rte9ek.js → 0zq~jtyqwejzx.js} +1 -1
  146. package/.next/static/chunks/11t1ercs8j0x1.js +4 -0
  147. package/.next/trace +2 -2
  148. package/.next/trace-build +1 -1
  149. package/app/components/TopoGraph.tsx +16 -10
  150. package/app/globals.css +29 -0
  151. package/package.json +1 -1
  152. package/scripts/topo-chip-focus-test.mjs +95 -0
  153. package/scripts/topo-svg-focus-test.mjs +112 -0
  154. package/.next/static/chunks/06fy8p2g0jbz1.js +0 -4
  155. /package/.next/static/{IJ5nEFbzhj38AtfV6zg14 → wpeRwhWZ_hD3osxjp5P7e}/_buildManifest.js +0 -0
  156. /package/.next/static/{IJ5nEFbzhj38AtfV6zg14 → wpeRwhWZ_hD3osxjp5P7e}/_clientMiddlewareManifest.js +0 -0
  157. /package/.next/static/{IJ5nEFbzhj38AtfV6zg14 → wpeRwhWZ_hD3osxjp5P7e}/_ssgManifest.js +0 -0
@@ -1363,7 +1363,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1363
1363
  return (
1364
1364
  <>
1365
1365
  <span
1366
- className="px-2.5 py-1 rounded-md bg-green-500/10 text-green-300 border border-green-500/20"
1366
+ className="px-2.5 py-1 rounded-md bg-green-500/10 text-green-300 border border-green-500/20 anet-topo-chip-focus"
1367
1367
  data-working-chip
1368
1368
  data-working-chip-aliases={workingAliases.join(',')}
1369
1369
  data-pin-mirror={pinnedStatus === 'working' ? 'true' : 'false'}
@@ -1401,7 +1401,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1401
1401
  {workingCount} working
1402
1402
  </span>
1403
1403
  <span
1404
- className="px-2.5 py-1 rounded-md bg-cyan-500/10 text-cyan-300 border border-cyan-500/20"
1404
+ className="px-2.5 py-1 rounded-md bg-cyan-500/10 text-cyan-300 border border-cyan-500/20 anet-topo-chip-focus"
1405
1405
  data-online-chip
1406
1406
  data-online-chip-aliases={onlineAliases.join(',')}
1407
1407
  data-pin-mirror={pinnedStatus === 'idle' ? 'true' : 'false'}
@@ -1485,6 +1485,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1485
1485
  role="button"
1486
1486
  tabIndex={0}
1487
1487
  aria-pressed={isPinned}
1488
+ className="anet-topo-chip-focus"
1488
1489
  title={`${n} ${label}\n${previewList}${suffix}\n${titleAction}`}
1489
1490
  style={{
1490
1491
  width: `${(n / total) * 100}%`,
@@ -1586,7 +1587,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1586
1587
  data-active-filter="status"
1587
1588
  data-filter-match-count={matchCount}
1588
1589
  data-filter-match-aliases={matchAliases.join(',')}
1589
- className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in"
1590
+ className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in anet-topo-chip-focus"
1590
1591
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
1591
1592
  onClick={() => setPinnedStatus(null)}
1592
1593
  style={{
@@ -1624,7 +1625,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1624
1625
  data-active-filter="group"
1625
1626
  data-filter-match-count={matchCount}
1626
1627
  data-filter-match-aliases={matchAliases.join(',')}
1627
- className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in"
1628
+ className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in anet-topo-chip-focus"
1628
1629
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
1629
1630
  onClick={() => setPinnedGroup(null)}
1630
1631
  style={{
@@ -1674,7 +1675,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1674
1675
  data-active-filter="vendor"
1675
1676
  data-filter-match-count={matchCount}
1676
1677
  data-filter-match-aliases={matchAliases.join(',')}
1677
- className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in"
1678
+ className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in anet-topo-chip-focus"
1678
1679
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
1679
1680
  onClick={() => setPinnedVendor(null)}
1680
1681
  style={{
@@ -1722,7 +1723,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1722
1723
  data-filter-match-count={link.count}
1723
1724
  data-filter-match-aliases={`${link.from},${link.to}`}
1724
1725
  data-active-filter-edge-hot={isHot ? 'true' : 'false'}
1725
- className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in"
1726
+ className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in anet-topo-chip-focus"
1726
1727
  title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
1727
1728
  onClick={() => setPinnedEdgeKey(null)}
1728
1729
  style={{
@@ -1836,7 +1837,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1836
1837
  data-pin-intersection-count={matchAliases.length}
1837
1838
  data-pin-intersection-empty={isEmpty ? 'true' : 'false'}
1838
1839
  data-pin-intersection-aliases={matchAliases.join(',')}
1839
- className="hidden sm:inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in"
1840
+ className="hidden sm:inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono text-xs border anet-fade-in anet-topo-chip-focus"
1840
1841
  title={tooltip}
1841
1842
  style={{
1842
1843
  background: isEmpty
@@ -1889,7 +1890,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1889
1890
  role="button"
1890
1891
  tabIndex={0}
1891
1892
  aria-pressed={isPinned}
1892
- className="inline-flex items-baseline gap-0.5 px-1 rounded"
1893
+ className="inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus"
1893
1894
  data-vendor-letter={v.initial}
1894
1895
  data-vendor-pinned={isPinned ? 'true' : 'false'}
1895
1896
  data-vendor-aliases={aliases.join(',')}
@@ -1959,7 +1960,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1959
1960
  : `${flowList}${flowSuffix} — hover brightens all · click to open /messages`;
1960
1961
  return (
1961
1962
  <span
1962
- className="hidden sm:inline px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20"
1963
+ className="hidden sm:inline px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 anet-topo-chip-focus"
1963
1964
  data-active-links-chip
1964
1965
  data-active-links-flow-count={flowLinks.length}
1965
1966
  data-active-links-clickable={isInteractive ? 'true' : 'false'}
@@ -2475,6 +2476,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2475
2476
  tabIndex={0}
2476
2477
  aria-pressed={pinnedGroup === box.key}
2477
2478
  data-group-label-hit={box.key}
2479
+ className="anet-topo-svg-focus"
2478
2480
  style={{ pointerEvents: 'all', cursor: 'pointer' }}
2479
2481
  onPointerDown={(e) => e.stopPropagation()}
2480
2482
  onClick={() => setPinnedGroup(prev => prev === box.key ? null : box.key)}
@@ -2856,6 +2858,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2856
2858
  role="button"
2857
2859
  tabIndex={0}
2858
2860
  aria-pressed={isPinned}
2861
+ className="anet-topo-svg-focus"
2859
2862
  style={{ pointerEvents: 'all', cursor: 'pointer' }}
2860
2863
  opacity={badgeOpacity}
2861
2864
  onPointerDown={(e) => e.stopPropagation()}
@@ -3133,7 +3136,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
3133
3136
  // an existing node don't re-trigger (React preserves the <g>
3134
3137
  // via the alias key), so status changes don't flicker. The
3135
3138
  // global prefers-reduced-motion sweep already neutralises it.
3136
- className="group transition-opacity anet-fade-in"
3139
+ className="group transition-opacity anet-fade-in anet-topo-svg-focus"
3137
3140
  style={{
3138
3141
  cursor: 'pointer',
3139
3142
  // Round 17 / Loop: offline nodes drop to 0.6 at rest so
@@ -3805,6 +3808,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
3805
3808
  data-recent-row-pinned={isRowPinned ? 'true' : 'false'}
3806
3809
  data-recent-row-hot={isHot ? 'true' : 'false'}
3807
3810
  data-recent-row-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}
3811
+ className="anet-topo-svg-focus"
3808
3812
  role="button"
3809
3813
  tabIndex={0}
3810
3814
  aria-pressed={isRowPinned}
@@ -3951,6 +3955,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
3951
3955
  data-recent-panel-more-nav
3952
3956
  role="link"
3953
3957
  tabIndex={0}
3958
+ className="anet-topo-svg-focus"
3954
3959
  style={{ cursor: 'pointer', pointerEvents: 'all' }}
3955
3960
  onPointerDown={(e) => e.stopPropagation()}
3956
3961
  onMouseEnter={() => setHoveredRecentMore(true)}
@@ -4051,6 +4056,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
4051
4056
  key={row.key}
4052
4057
  data-legend-status={row.key}
4053
4058
  data-legend-row-lifted={isLifted ? 'true' : 'false'}
4059
+ className="anet-topo-svg-focus"
4054
4060
  role="button"
4055
4061
  tabIndex={0}
4056
4062
  aria-pressed={isPinned}
package/app/globals.css CHANGED
@@ -856,6 +856,35 @@ body {
856
856
  animation-iteration-count: infinite;
857
857
  }
858
858
 
859
+ /* R155 — focus-visible outline for TopoGraph chip-row interactives.
860
+ Cyber theme's dark canvas often swallows the browser default focus
861
+ ring; R154 fixed it for chrome buttons via Tailwind's focus-visible:
862
+ ring (box-shadow). Chip-row chips reuse `box-shadow` for the R88 /
863
+ R139 / R140 pin-mirror inset — so a focus box-shadow would conflict
864
+ with the pinned-state shadow. `outline` is a separate CSS property
865
+ and doesn't fight box-shadow, so we use it here. `currentColor`
866
+ inherits the chip's own accent (green for working, cyan for online,
867
+ per-vendor for letters, etc.). Only fires on :focus-visible
868
+ (keyboard) — mouse focus stays unstyled. */
869
+ .anet-topo-chip-focus:focus-visible {
870
+ outline: 2px solid currentColor;
871
+ outline-offset: 1px;
872
+ }
873
+
874
+ /* R156 — focus-visible outline for SVG-side TopoGraph interactives.
875
+ Same idiom as R155's chip-row class but for SVG <g> elements
876
+ (recent-signal rows, legend rows, group labels, edge badges,
877
+ node bodies, "+N more" footer). Each <g> already has role=
878
+ "button"/"link" + tabIndex=0 + onKeyDown (R116/R121/R133/R143/
879
+ R144/R151/R152). Browser default focus outline on SVG is hard
880
+ to spot against the canvas; explicit cyan-300 ring matches the
881
+ dashboard's legendAccent. 2-px outline-offset gives breathing
882
+ room around the painted bounding box. */
883
+ .anet-topo-svg-focus:focus-visible {
884
+ outline: 2px solid #67e8f9;
885
+ outline-offset: 2px;
886
+ }
887
+
859
888
  /* Round 46 — animated dashed spokes flowing from hub outward. Each spoke
860
889
  gets a small animationDelay (set inline) so the dashes don't all flow
861
890
  in sync — reads as "command relay" pulse rather than a metronome.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.4.6-preview.161",
3
+ "version": "0.4.6-preview.163",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -0,0 +1,95 @@
1
+ /* Round 155 verification: chip-row interactives gain
2
+ * `.anet-topo-chip-focus` for keyboard focus-visible outline,
3
+ * matching R154's chrome-button treatment.
4
+ *
5
+ * Why `outline` not `box-shadow`: the R88 vendor letter / R139
6
+ * working chip / R140 online chip already use inline boxShadow
7
+ * for the pin-mirror inset. Adding focus-visible:ring (which
8
+ * Tailwind paints via box-shadow) would conflict — inline style
9
+ * wins specificity. `outline: 2px solid currentColor` is on a
10
+ * separate CSS property, no conflict; `currentColor` inherits
11
+ * the chip's own accent (green / cyan / per-vendor / etc.).
12
+ *
13
+ * Test: every chip-row interactive surface carries the class.
14
+ */
15
+ import { chromium } from 'playwright';
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
19
+ const browser = await chromium.launch({ headless: true });
20
+
21
+ const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
22
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
23
+ await ctx.addInitScript(() => {
24
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
25
+ });
26
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
27
+ await ctx.route('**/api/hub/status*', async (route) => {
28
+ const r = await route.fetch();
29
+ const b = await r.json();
30
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
31
+ const mk = (alias, status, model) => ({
32
+ alias, status, model, runtime: 'cli-claude-code',
33
+ network_id: nid, project_dir: null,
34
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
35
+ });
36
+ await route.fulfill({ response: r, json: { ...b, sessions: [
37
+ mk('alpha', 'working', 'claude-opus-4'),
38
+ mk('beta', 'idle', 'gpt-4o'),
39
+ ] } });
40
+ });
41
+
42
+ const now = Date.now();
43
+ // 1 message to populate flowLinks → active-links chip is interactive
44
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
45
+ { id: 'm0', from_alias: 'alpha', to_alias: 'beta', content: 'hi',
46
+ network_id: 'default', created_at: new Date(now - 10000).toISOString() },
47
+ ] } }));
48
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
49
+
50
+ const page = await ctx.newPage();
51
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
52
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 2, { timeout: 30000 });
53
+ await page.waitForTimeout(400);
54
+
55
+ // Pin status + vendor to surface filter pills + intersection chip
56
+ await page.evaluate(() => {
57
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'status', value: 'working' } }));
58
+ window.dispatchEvent(new CustomEvent('anet:topo-pin', { detail: { kind: 'vendor', value: 'A' } }));
59
+ });
60
+ await page.waitForTimeout(300);
61
+
62
+ const inspect = () => page.evaluate(() => {
63
+ const has = (sel) => {
64
+ const el = document.querySelector(sel);
65
+ return el && (el.className || '').includes('anet-topo-chip-focus');
66
+ };
67
+ return {
68
+ pressureSeg_working: has('[data-pressure-seg="working"]'),
69
+ workingChip: has('[data-working-chip]'),
70
+ onlineChip: has('[data-online-chip]'),
71
+ vendorLetter_A: has('[data-vendor-letter="A"]'),
72
+ activeLinks: has('[data-active-links-chip]'),
73
+ filterPill_status: has('[data-active-filter="status"]'),
74
+ filterPill_vendor: has('[data-active-filter="vendor"]'),
75
+ intersectionChip: has('[data-pin-intersection]'),
76
+ };
77
+ });
78
+
79
+ const result = await inspect();
80
+ await browser.close();
81
+
82
+ const results = {
83
+ pressureSeg_focusClass: result.pressureSeg_working === true,
84
+ workingChip_focusClass: result.workingChip === true,
85
+ onlineChip_focusClass: result.onlineChip === true,
86
+ vendorLetter_focusClass: result.vendorLetter_A === true,
87
+ activeLinks_focusClass: result.activeLinks === true,
88
+ filterPillStatus_focusClass: result.filterPill_status === true,
89
+ filterPillVendor_focusClass: result.filterPill_vendor === true,
90
+ intersectionChip_focusClass: result.intersectionChip === true,
91
+ };
92
+ const ok = Object.values(results).every(Boolean);
93
+ console.log(`${ok ? '✅' : '❌'} chip focus class:`, JSON.stringify(results),
94
+ `\n raw=`, result);
95
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,112 @@
1
+ /* Round 156 verification: SVG <g> interactives gain
2
+ * `.anet-topo-svg-focus` for keyboard focus outline.
3
+ *
4
+ * Mirrors R155's chip-row treatment at the SVG scope. 6 surfaces:
5
+ * data-node (R151 node body)
6
+ * data-group-label-hit (R63 group label)
7
+ * data-edge-count-badge (R121 edge badge)
8
+ * data-recent-row (R116 recent-signal row)
9
+ * data-recent-panel-more-nav (R133 "+N more" footer)
10
+ * data-legend-status (R61 legend row)
11
+ *
12
+ * Class `.anet-topo-svg-focus:focus-visible` paints outline 2px
13
+ * cyan-300 (#67e8f9) with 2px outline-offset. Browser default
14
+ * focus ring on SVG is hard to spot against the dashboard
15
+ * canvas; explicit cyan matches the legendAccent palette.
16
+ *
17
+ * Test: every SVG interactive surface carries the class.
18
+ */
19
+ import { chromium } from 'playwright';
20
+ import { readFileSync } from 'node:fs';
21
+
22
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
23
+ const browser = await chromium.launch({ headless: true });
24
+
25
+ const ctx = await browser.newContext({ viewport: { width: 1600, height: 1500 } });
26
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
27
+ await ctx.addInitScript(() => {
28
+ try {
29
+ localStorage.setItem('anet-theme', 'cyber');
30
+ sessionStorage.setItem('anet_v3_auth', '1');
31
+ // grid layout to surface group labels
32
+ localStorage.setItem('anet-topo-layout', 'grid');
33
+ } catch {}
34
+ });
35
+
36
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
37
+ await ctx.route('**/api/hub/status*', async (route) => {
38
+ const r = await route.fetch();
39
+ const b = await r.json();
40
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
41
+ const mk = (alias, status) => ({
42
+ alias, status, model: 'claude-opus-4', runtime: 'cli-claude-code',
43
+ network_id: nid, project_dir: null,
44
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
45
+ });
46
+ await route.fulfill({ response: r, json: { ...b, sessions: [
47
+ mk('agents-a1', 'working'), mk('agents-a2', 'working'),
48
+ mk('infra-b1', 'working'),
49
+ ] } });
50
+ });
51
+
52
+ const now = Date.now();
53
+ // Edge midpoint count badge (R100) only renders when link.count >= 3.
54
+ // Pair agents-a1 → agents-a2 gets 4 msgs to clear the threshold; add 4
55
+ // other singletons so flowLinks.length = 5 → R128 "+ 2 more flows"
56
+ // footer also renders.
57
+ const msgs = [];
58
+ for (let i = 0; i < 4; i++) {
59
+ msgs.push({
60
+ id: `a2a${i}`, from_alias: 'agents-a1', to_alias: 'agents-a2', content: 'hi',
61
+ network_id: 'default', created_at: new Date(now - (10000 + i * 500)).toISOString(),
62
+ });
63
+ }
64
+ msgs.push({ id: 'm1', from_alias: 'agents-a2', to_alias: 'infra-b1', content: 'hi',
65
+ network_id: 'default', created_at: new Date(now - 11000).toISOString() });
66
+ msgs.push({ id: 'm2', from_alias: 'infra-b1', to_alias: 'agents-a1', content: 'hi',
67
+ network_id: 'default', created_at: new Date(now - 12000).toISOString() });
68
+ msgs.push({ id: 'm3', from_alias: 'agents-a1', to_alias: 'infra-b1', content: 'hi',
69
+ network_id: 'default', created_at: new Date(now - 13000).toISOString() });
70
+ msgs.push({ id: 'm4', from_alias: 'agents-a2', to_alias: 'agents-a1', content: 'hi',
71
+ network_id: 'default', created_at: new Date(now - 14000).toISOString() });
72
+ await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
73
+ await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
74
+
75
+ const page = await ctx.newPage();
76
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
77
+ await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
78
+ await page.waitForSelector('[data-recent-row]', { timeout: 10000 });
79
+ await page.waitForSelector('[data-recent-panel-more-nav]', { timeout: 10000 });
80
+ await page.waitForSelector('[data-edge-count-badge]', { timeout: 10000 });
81
+ await page.waitForSelector('[data-group-label-hit]', { timeout: 10000 });
82
+ await page.waitForTimeout(400);
83
+
84
+ const out = await page.evaluate(() => {
85
+ const has = (sel) => {
86
+ const el = document.querySelector(sel);
87
+ return el && (el.getAttribute('class') || '').includes('anet-topo-svg-focus');
88
+ };
89
+ return {
90
+ nodeBody: has('g[data-node]'),
91
+ groupLabel: has('[data-group-label-hit]'),
92
+ edgeCountBadge: has('[data-edge-count-badge]'),
93
+ recentRow: has('[data-recent-row]'),
94
+ recentPanelMoreNav: has('[data-recent-panel-more-nav]'),
95
+ legendStatusRow: has('[data-legend-status="working"]'),
96
+ };
97
+ });
98
+
99
+ await browser.close();
100
+
101
+ const results = {
102
+ nodeBody_focus: out.nodeBody === true,
103
+ groupLabel_focus: out.groupLabel === true,
104
+ edgeCountBadge_focus: out.edgeCountBadge === true,
105
+ recentRow_focus: out.recentRow === true,
106
+ recentPanelMoreNav_focus: out.recentPanelMoreNav === true,
107
+ legendStatusRow_focus: out.legendStatusRow === true,
108
+ };
109
+ const ok = Object.values(results).every(Boolean);
110
+ console.log(`${ok ? '✅' : '❌'} SVG focus class:`, JSON.stringify(results),
111
+ `\n raw=`, out);
112
+ process.exit(ok ? 0 : 1);