@sleep2agi/agent-network-dashboard 0.5.1-preview.2 → 0.5.1-preview.21

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 (173) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +6 -6
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +2 -2
  14. package/.next/server/app/_not-found.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/admin.html +2 -2
  23. package/.next/server/app/admin.rsc +2 -2
  24. package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
  25. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
  27. package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
  28. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  30. package/.next/server/app/index.html +2 -2
  31. package/.next/server/app/index.rsc +3 -3
  32. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  33. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  34. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  35. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  36. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  37. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  38. package/.next/server/app/login.html +2 -2
  39. package/.next/server/app/login.rsc +3 -3
  40. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  41. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/_index.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  44. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  45. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  46. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  47. package/.next/server/app/logs.html +2 -2
  48. package/.next/server/app/logs.rsc +2 -2
  49. package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
  50. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  52. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  53. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  54. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  55. package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
  56. package/.next/server/app/messages.html +2 -2
  57. package/.next/server/app/messages.rsc +2 -2
  58. package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
  59. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  60. package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
  61. package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
  62. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  63. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  64. package/.next/server/app/node/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app/node.html +2 -2
  66. package/.next/server/app/node.rsc +2 -2
  67. package/.next/server/app/node.segments/_full.segment.rsc +2 -2
  68. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  69. package/.next/server/app/node.segments/_index.segment.rsc +2 -2
  70. package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
  71. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  72. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  73. package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/nodes.html +2 -2
  75. package/.next/server/app/nodes.rsc +2 -2
  76. package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
  77. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  78. package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
  79. package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
  80. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  81. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  82. package/.next/server/app/page_client-reference-manifest.js +1 -1
  83. package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/server-logs.html +2 -2
  85. package/.next/server/app/server-logs.rsc +2 -2
  86. package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
  87. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  88. package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
  89. package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
  90. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  91. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app/settings/networks.html +2 -2
  94. package/.next/server/app/settings/networks.rsc +2 -2
  95. package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
  96. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  97. package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
  98. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
  99. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  100. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  101. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  102. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  103. package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
  104. package/.next/server/app/settings/tokens.html +2 -2
  105. package/.next/server/app/settings/tokens.rsc +2 -2
  106. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
  109. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
  110. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  111. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  112. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  113. package/.next/server/app/settings.html +2 -2
  114. package/.next/server/app/settings.rsc +3 -3
  115. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  116. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  117. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  118. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  119. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  120. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  121. package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
  122. package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
  123. package/.next/server/app/tasks.html +2 -2
  124. package/.next/server/app/tasks.rsc +2 -2
  125. package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
  126. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  127. package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
  128. package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
  129. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  130. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  131. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  132. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  133. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  134. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  135. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  136. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  137. package/.next/server/middleware-build-manifest.js +3 -3
  138. package/.next/server/pages/404.html +2 -2
  139. package/.next/server/pages/500.html +1 -1
  140. package/.next/static/chunks/0lh3wu1zlx_a5.js +1 -0
  141. package/.next/static/chunks/0mjn4oij3zu6y.js +4 -0
  142. package/.next/static/chunks/0tvbbhnjh61u8.js +1 -0
  143. package/.next/static/chunks/0~oj~gm3ck7o8.css +2 -0
  144. package/.next/trace +2 -2
  145. package/.next/trace-build +1 -1
  146. package/app/components/TopoGraph.tsx +383 -43
  147. package/package.json +1 -1
  148. package/scripts/topo-active-links-freshness-color-test.mjs +86 -0
  149. package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
  150. package/scripts/topo-filter-pill-weight-test.mjs +75 -0
  151. package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
  152. package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
  153. package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
  154. package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
  155. package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
  156. package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
  157. package/scripts/topo-minimap-viewport-hover-test.mjs +109 -0
  158. package/scripts/topo-more-flows-hover-letterspacing-test.mjs +98 -0
  159. package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
  160. package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
  161. package/scripts/topo-panel-title-hover-letterspacing-test.mjs +97 -0
  162. package/scripts/topo-recent-pip-radius-test.mjs +76 -0
  163. package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
  164. package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
  165. package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
  166. package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
  167. package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
  168. package/.next/static/chunks/0ap~cvzkt4_.w.js +0 -1
  169. package/.next/static/chunks/0bu~3a_lb2vr1.js +0 -4
  170. package/.next/static/chunks/14d3w289h5vr0.js +0 -1
  171. /package/.next/static/{U2ie3REU1St9mnDvCTXzE → OLsgiMVJR1MAtUHtDD9yq}/_buildManifest.js +0 -0
  172. /package/.next/static/{U2ie3REU1St9mnDvCTXzE → OLsgiMVJR1MAtUHtDD9yq}/_clientMiddlewareManifest.js +0 -0
  173. /package/.next/static/{U2ie3REU1St9mnDvCTXzE → OLsgiMVJR1MAtUHtDD9yq}/_ssgManifest.js +0 -0
@@ -1026,6 +1026,42 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1026
1026
  // navigation footer. Drives the on-hover opacity boost + underline
1027
1027
  // that signals interactivity, mirroring the hoveredHub idiom above.
1028
1028
  const [hoveredRecentMore, setHoveredRecentMore] = useState(false);
1029
+ // Round 346 / Loop: minimap-container hover affordance. The minimap
1030
+ // is a click-target (role=button at line ~7810, recenter-on-click +
1031
+ // Enter→resetView) but pre-R346 nothing visually changed on hover —
1032
+ // the only hint was the `cursor: crosshair` style. R346 lifts the
1033
+ // viewport rect (strokeWidth 1.5 → 1.75 + opacity 0.9 → 1.0) when
1034
+ // the user enters the minimap, marking "this is the recenter target
1035
+ // and it's alive". Sibling polish to the R332 minimap rounded-md →
1036
+ // rounded-lg corner family — that round refined geometry, this one
1037
+ // gives the viewport indicator inside the geometry a hover state.
1038
+ // 280ms ease-out transition list matches R199 smoothView vocabulary
1039
+ // so the visual joins the existing rhythm on the same rect.
1040
+ const [hoveredMinimap, setHoveredMinimap] = useState(false);
1041
+ // Round 347 / Loop: zoom-level readout hover-state letter-spacing
1042
+ // tween (0 → 0.5 px). The readout sandwiched between zoom-out /
1043
+ // zoom-in is a passive percent display — pre-R347 it had no hover
1044
+ // feedback at all (only a `title` tooltip). R347 extends the R344
1045
+ // (`+N more flows` footer) + R345 (panel titles) hover-letter-
1046
+ // spacing family from panel/footer surfaces into the HTML chrome
1047
+ // strip. Hovering the readout spreads its digits 0.5 px, signalling
1048
+ // "this is alive". tabular-nums + minWidth: 46 from R225 still lock
1049
+ // the column so the tween doesn't shove neighbouring controls.
1050
+ // 200ms ease-out joins the existing R264 color/border transition
1051
+ // list on the same span.
1052
+ const [hoveredZoomLevel, setHoveredZoomLevel] = useState(false);
1053
+ // Round 350 / Loop: reset-button icon hover-rotate preview of the
1054
+ // R184 click-spin. Pre-R350 hovering the reset button only changed
1055
+ // the button bg (white/5); the icon inside stayed perfectly still.
1056
+ // R350 nudges the icon -8° on hover — a tactile hint that this
1057
+ // button rotates the icon on click. When the click fires, the
1058
+ // R184 anet-reset-spin keyframe animation overrides the hover
1059
+ // transform for its 450 ms run (CSS animations win over transitions
1060
+ // on the same property); when the animation ends + React removes
1061
+ // the className, the inline transform eases back to whatever the
1062
+ // hover state says — either -8° (still hovering) or 0 (mouse left).
1063
+ // 350th-round milestone polish.
1064
+ const [hoveredReset, setHoveredReset] = useState(false);
1029
1065
  // R135: panel-wide hover-elevation. The recent-signal + legend
1030
1066
  // panels both already host clickable rows (R56/R116 recent rows,
1031
1067
  // R55/R61 legend rows) and a clickable footer (R133), so the
@@ -1826,7 +1862,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1826
1862
  weight; cyan-400/60 + ring-inset retained. The
1827
1863
  R163/R196 hover/active deeps + R249 chrome-pop
1828
1864
  click feedback continue unchanged. */
1829
- className={`px-2.5 py-1 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${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' : ''}`}
1865
+ // R351: hover:tracking-wide extends the R344/R345/R347
1866
+ // hover-letter-spacing family to a 4th surface (chrome-
1867
+ // strip Ring/Grid pair). transition-colors className
1868
+ // dropped in favour of an inline transition spec that
1869
+ // bundles bg/color (150ms ease) + letter-spacing
1870
+ // (200ms ease-out) — Tailwind's transition-colors
1871
+ // doesn't list letter-spacing, so without this the
1872
+ // hover:tracking-wide would snap. Sibling change on
1873
+ // the Grid button below.
1874
+ 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' : ''}`}
1875
+ style={{ transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out' }}
1830
1876
  >
1831
1877
  Ring
1832
1878
  </button>
@@ -1843,14 +1889,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
1843
1889
  // Round 306 / Loop: focus-visible:ring-2 → ring-1 sibling
1844
1890
  // change to Ring above — unifies focus-ring width across
1845
1891
  // all chrome buttons.
1846
- className={`px-2.5 py-1 border-l transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${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' : ''}`}
1892
+ // R351 sibling Grid button picks up hover:tracking-wide
1893
+ // + inline transition spec. Same vocabulary as Ring.
1894
+ 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' : ''}`}
1847
1895
  /* Round 268 / Loop: Grid button's left border (the
1848
1896
  internal divider between Ring and Grid) picks up
1849
1897
  pal.containerBorder, matching the wrapper change at
1850
1898
  line ~1460 and the chrome strip's segmented borders
1851
- (nodeSize, zoom). transition-colors className covers
1852
- the border-color eased on theme toggle. */
1853
- style={{ borderColor: pal.containerBorder }}
1899
+ (nodeSize, zoom). The R268 transition-colors className
1900
+ used to carry the border-color ease; R351 unfolds the
1901
+ transition list into the inline spec below so the
1902
+ letter-spacing tween rides alongside without snapping
1903
+ the border-color flip — border-color 200ms ease-out
1904
+ keeps R268's theme-toggle smoothness intact. */
1905
+ style={{ borderColor: pal.containerBorder, transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out' }}
1854
1906
  >
1855
1907
  Grid
1856
1908
  </button>
@@ -2259,7 +2311,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2259
2311
  data-active-filter="status"
2260
2312
  data-filter-match-count={matchCount}
2261
2313
  data-filter-match-aliases={matchAliases.join(',')}
2262
- 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"
2314
+ // R355: `group` lets the inner opacity-70 spans (prefix
2315
+ // `filter:` + count `· N`) brighten to 100 % on pill hover.
2316
+ // Sibling treatment on group + vendor pills below.
2317
+ 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"
2263
2318
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
2264
2319
  onClick={() => setPinnedStatus(null)}
2265
2320
  style={{
@@ -2273,12 +2328,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2273
2328
  cursor: 'pointer',
2274
2329
  }}
2275
2330
  >
2276
- <span><span className="hidden sm:inline opacity-70" data-filter-prefix>filter: </span>{pinnedStatus}<span className="opacity-70 tabular-nums" data-filter-pill-count> · {matchCount}</span></span>
2331
+ <span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span>{pinnedStatus}<span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
2277
2332
  <button
2278
2333
  type="button"
2279
2334
  aria-label={`Clear ${pinnedStatus} filter`}
2280
2335
  onClick={(e) => { e.stopPropagation(); setPinnedStatus(null); }}
2281
- className="ml-0.5 leading-none hover:opacity-70"
2336
+ /* Round 356 / Loop: filter pin pill × buttons gain
2337
+ hover:scale-110 (Tailwind 4 modern CSS `scale` property,
2338
+ not legacy transform). Sibling polish to R354 vendor
2339
+ letter glyph + R350/R352/R353 chrome icon hover-scales.
2340
+ Pre-R356 the × had only hover:opacity-70 — the target
2341
+ dimmed under cursor but didn't lift. R356 adds a 10 %
2342
+ scale on hover so the click-target reads as "press me"
2343
+ alongside the dim. transform-gpu hint promotes the
2344
+ button to its own compositor layer for crisper edges
2345
+ during the scale tween. transition-transform duration-
2346
+ 200 matches the chrome icon hover-scale timing family.
2347
+ inline-block is default for <button> so no display
2348
+ tweak needed. replace_all covers all 4 filter pin
2349
+ pills (status / group / vendor / edge) at once. */
2350
+ className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
2282
2351
  style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
2283
2352
  >×</button>
2284
2353
  </span>
@@ -2297,7 +2366,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2297
2366
  data-active-filter="group"
2298
2367
  data-filter-match-count={matchCount}
2299
2368
  data-filter-match-aliases={matchAliases.join(',')}
2300
- 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"
2369
+ // R355 sibling `group` parent + group-hover on inner spans.
2370
+ 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"
2301
2371
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
2302
2372
  onClick={() => setPinnedGroup(null)}
2303
2373
  style={{
@@ -2307,12 +2377,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2307
2377
  cursor: 'pointer',
2308
2378
  }}
2309
2379
  >
2310
- <span><span className="hidden sm:inline opacity-70" data-filter-prefix>filter: </span>{pinnedGroup}<span className="opacity-70 tabular-nums" data-filter-pill-count> · {matchCount}</span></span>
2380
+ <span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span>{pinnedGroup}<span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
2311
2381
  <button
2312
2382
  type="button"
2313
2383
  aria-label={`Clear group filter ${pinnedGroup}`}
2314
2384
  onClick={(e) => { e.stopPropagation(); setPinnedGroup(null); }}
2315
- className="ml-0.5 leading-none hover:opacity-70"
2385
+ /* Round 356 / Loop: filter pin pill × buttons gain
2386
+ hover:scale-110 (Tailwind 4 modern CSS `scale` property,
2387
+ not legacy transform). Sibling polish to R354 vendor
2388
+ letter glyph + R350/R352/R353 chrome icon hover-scales.
2389
+ Pre-R356 the × had only hover:opacity-70 — the target
2390
+ dimmed under cursor but didn't lift. R356 adds a 10 %
2391
+ scale on hover so the click-target reads as "press me"
2392
+ alongside the dim. transform-gpu hint promotes the
2393
+ button to its own compositor layer for crisper edges
2394
+ during the scale tween. transition-transform duration-
2395
+ 200 matches the chrome icon hover-scale timing family.
2396
+ inline-block is default for <button> so no display
2397
+ tweak needed. replace_all covers all 4 filter pin
2398
+ pills (status / group / vendor / edge) at once. */
2399
+ className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
2316
2400
  style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
2317
2401
  >×</button>
2318
2402
  </span>
@@ -2347,7 +2431,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2347
2431
  data-active-filter="vendor"
2348
2432
  data-filter-match-count={matchCount}
2349
2433
  data-filter-match-aliases={matchAliases.join(',')}
2350
- 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"
2434
+ // R355 sibling `group` parent + group-hover on inner spans.
2435
+ 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"
2351
2436
  title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
2352
2437
  onClick={() => setPinnedVendor(null)}
2353
2438
  style={{
@@ -2357,12 +2442,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2357
2442
  cursor: 'pointer',
2358
2443
  }}
2359
2444
  >
2360
- <span><span className="hidden sm:inline opacity-70" data-filter-prefix>filter: </span>{pinnedVendor}<span className="opacity-70 tabular-nums" data-filter-pill-count> · {matchCount}</span></span>
2445
+ <span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span>{pinnedVendor}<span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
2361
2446
  <button
2362
2447
  type="button"
2363
2448
  aria-label={`Clear vendor filter ${pinnedVendor}`}
2364
2449
  onClick={(e) => { e.stopPropagation(); setPinnedVendor(null); }}
2365
- className="ml-0.5 leading-none hover:opacity-70"
2450
+ /* Round 356 / Loop: filter pin pill × buttons gain
2451
+ hover:scale-110 (Tailwind 4 modern CSS `scale` property,
2452
+ not legacy transform). Sibling polish to R354 vendor
2453
+ letter glyph + R350/R352/R353 chrome icon hover-scales.
2454
+ Pre-R356 the × had only hover:opacity-70 — the target
2455
+ dimmed under cursor but didn't lift. R356 adds a 10 %
2456
+ scale on hover so the click-target reads as "press me"
2457
+ alongside the dim. transform-gpu hint promotes the
2458
+ button to its own compositor layer for crisper edges
2459
+ during the scale tween. transition-transform duration-
2460
+ 200 matches the chrome icon hover-scale timing family.
2461
+ inline-block is default for <button> so no display
2462
+ tweak needed. replace_all covers all 4 filter pin
2463
+ pills (status / group / vendor / edge) at once. */
2464
+ className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
2366
2465
  style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
2367
2466
  >×</button>
2368
2467
  </span>
@@ -2395,7 +2494,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2395
2494
  data-filter-match-count={link.count}
2396
2495
  data-filter-match-aliases={`${link.from},${link.to}`}
2397
2496
  data-active-filter-edge-hot={isHot ? 'true' : 'false'}
2398
- 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"
2497
+ 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"
2399
2498
  title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
2400
2499
  onClick={() => setPinnedEdgeKey(null)}
2401
2500
  style={{
@@ -2439,7 +2538,21 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2439
2538
  type="button"
2440
2539
  aria-label={`Clear edge filter ${link.from} → ${link.to}`}
2441
2540
  onClick={(e) => { e.stopPropagation(); setPinnedEdgeKey(null); }}
2442
- className="ml-0.5 leading-none hover:opacity-70"
2541
+ /* Round 356 / Loop: filter pin pill × buttons gain
2542
+ hover:scale-110 (Tailwind 4 modern CSS `scale` property,
2543
+ not legacy transform). Sibling polish to R354 vendor
2544
+ letter glyph + R350/R352/R353 chrome icon hover-scales.
2545
+ Pre-R356 the × had only hover:opacity-70 — the target
2546
+ dimmed under cursor but didn't lift. R356 adds a 10 %
2547
+ scale on hover so the click-target reads as "press me"
2548
+ alongside the dim. transform-gpu hint promotes the
2549
+ button to its own compositor layer for crisper edges
2550
+ during the scale tween. transition-transform duration-
2551
+ 200 matches the chrome icon hover-scale timing family.
2552
+ inline-block is default for <button> so no display
2553
+ tweak needed. replace_all covers all 4 filter pin
2554
+ pills (status / group / vendor / edge) at once. */
2555
+ className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
2443
2556
  style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
2444
2557
  >×</button>
2445
2558
  </span>
@@ -2750,7 +2863,40 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2750
2863
  }
2751
2864
  }}
2752
2865
  >
2753
- <span style={{ color: v.color }}>{v.initial}</span>
2866
+ {/* Round 354 / Loop: vendor letter glyph scales
2867
+ 1.0 → 1.1 on hover. R88 already dims OTHER
2868
+ vendors on hover via canvas-wide opacity
2869
+ masking; R202 added a chip-level bg tint
2870
+ (color-mix 12 % alpha) so the chip itself
2871
+ responds. R354 closes the trio with a glyph-
2872
+ level lift: the focused vendor LETTER actively
2873
+ rises (transform scale) rather than the chip
2874
+ merely changing colour. Three layers of positive
2875
+ feedback on the hovered vendor + canvas-wide
2876
+ negative feedback on the others — a clean
2877
+ figure/ground separation.
2878
+
2879
+ display: inline-block is required for transform
2880
+ to apply (inline elements ignore transform).
2881
+ transformOrigin: 'center' so the glyph pivots
2882
+ around its centre instead of arcing from the
2883
+ baseline anchor. transition rides the existing
2884
+ Tailwind 4 transform/scale list (no new
2885
+ property — Tailwind already lists transform in
2886
+ the default transition-property set). 200ms
2887
+ matches the R202 chip bg-tint timing so the
2888
+ glyph lift and chip background ease in concert. */}
2889
+ <span
2890
+ data-vendor-letter-glyph={v.initial}
2891
+ data-vendor-letter-glyph-hover={hoveredVendor === v.initial ? 'true' : 'false'}
2892
+ style={{
2893
+ color: v.color,
2894
+ display: 'inline-block',
2895
+ transform: hoveredVendor === v.initial ? 'scale(1.1)' : 'scale(1)',
2896
+ transformOrigin: 'center',
2897
+ transition: 'transform 200ms ease-out',
2898
+ }}
2899
+ >{v.initial}</span>
2754
2900
  {/* Round 333 / Loop: vendor count suffix `:{N}` joins
2755
2901
  the R317 subordinate-text-lift family (gray-500 →
2756
2902
  gray-400) plus picks up tabular-nums for digit
@@ -2909,14 +3055,46 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
2909
3055
  const alpha = ageSec <= 30
2910
3056
  ? 1
2911
3057
  : ageSec <= 300
2912
- ? 1 - ((ageSec - 30) / 270) * 0.75
2913
- : 0.25;
3058
+ ? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
3059
+ : 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
2914
3060
  // Cyan dark / teal light to match palette legendAccent.
2915
3061
  const dotColor = isLight
2916
3062
  ? `rgba(13, 148, 136, ${alpha.toFixed(2)})`
2917
3063
  : `rgba(34, 211, 238, ${alpha.toFixed(2)})`;
3064
+ // Round 342 / Loop: active-links chip freshness suffix
3065
+ // wrapper text-gray-500 → text-gray-400 (R317
3066
+ // subordinate-text-lift family applied to chrome
3067
+ // inactive Layout toggle + R333 vendor count suffix).
3068
+ // The "last 5s ago" suffix is chip-subordinate
3069
+ // metadata; gray-500 sat near-invisible against the
3070
+ // chip's outer color, gray-400 lifts it into the band
3071
+ // where the eye reads it as deliberate freshness
3072
+ // annotation. The freshness DOT keeps its own inline
3073
+ // color: dotColor — the lift only affects the trailing
3074
+ // literal "last {rel}" text.
2918
3075
  return (
2919
- <span className="text-gray-500">
3076
+ // Round 357 / Loop: active-links chip freshness
3077
+ // suffix wrapper picks up `tabular-nums` for digit
3078
+ // width-lock. Pre-R357 the literal "last {rel}"
3079
+ // text (e.g. "last 5s ago", "last 10s ago",
3080
+ // "last 1m ago") had natural-figure digits — the
3081
+ // freshness ticker updates every second, so the
3082
+ // 9→10 boundary on the seconds counter and the
3083
+ // 59→60s → 1m flip both jittered ~1-2 px of glyph
3084
+ // width which propagated through the chip-row's
3085
+ // inline-flex layout, nudging the freshness DOT
3086
+ // and the chip's left edge. Tabular-nums on the
3087
+ // wrapper applies to all descendant digits only
3088
+ // (letters render at natural widths) so the
3089
+ // ticker stays planted across every count cross.
3090
+ // Joins the R224-R232 info-density tabular-nums
3091
+ // sweep at the chip-row freshness scope. Pure
3092
+ // paint-level change, no geometry shift on rest.
3093
+ // The R342 text-gray-400 lift + R161 dot freshness
3094
+ // alpha ramp + R317 subordinate-text-lift family
3095
+ // all preserved. data-active-links-freshness-
3096
+ // wrapper attr exposes the wrapper for tests.
3097
+ <span className="text-gray-400 tabular-nums" data-active-links-freshness-wrapper>
2920
3098
  <span
2921
3099
  data-active-links-freshness-dot
2922
3100
  data-active-links-freshness-alpha={alpha.toFixed(2)}
@@ -4842,11 +5020,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
4842
5020
  textAnchor="middle"
4843
5021
  dy="0.36em"
4844
5022
  fill={isLight ? '#d1fae5' : '#ecfdf5'}
4845
- fontSize="11"
5023
+ /* Round 360 / Loop: hub working-count digit fontSize 11
5024
+ → 12. The hub is the canvas's focal point — its digit
5025
+ is the most-read scalar on the whole topology. R130
5026
+ sized it at 11 (well inside the r=10 / 20-px core);
5027
+ R360 nudges it to 12 (~13 px wide × 12 px tall, still
5028
+ well inside the 20-px diameter) for ~9 % more presence.
5029
+ Sibling visual-weight bump family:
5030
+ R287 minimap viewport stroke 1 → 1.5
5031
+ R295 legend swatch base radius 5.5 → 6
5032
+ R359 recent-row pip radius 1.6 → 1.8
5033
+ R360 hub digit fontSize 11 → 12 (this round)
5034
+ The R209 scale-1.08-on-hub-hover, R225 tabular-nums,
5035
+ R253 fill transition, R213 always-mount opacity gate
5036
+ all preserved. data-topo-hub-working-count-font-size
5037
+ attr exposes the value for tests. */
5038
+ fontSize="12"
4846
5039
  fontFamily="monospace"
4847
5040
  fontWeight="700"
4848
5041
  opacity={workingCount > 0 ? 1 : 0}
4849
5042
  data-topo-hub-working-count={workingCount}
5043
+ data-topo-hub-working-count-font-size="12"
4850
5044
  data-topo-hub-working-count-hovered={hoveredHub ? 'true' : 'false'}
4851
5045
  data-topo-hub-working-count-visible={workingCount > 0 ? 'true' : 'false'}
4852
5046
  // Round 209 / Loop: hub workingCount digit scales 1.0 →
@@ -6213,7 +6407,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6213
6407
  x="0" y="0" width="230" height="88" rx="10"
6214
6408
  fill={pal.legendBox.fill}
6215
6409
  stroke={pal.legendBox.stroke}
6216
- opacity={isLight ? 0.97 : 0.92}
6410
+ // Round 348 / Loop: recent-signal panel rect opacity hover-
6411
+ // state bump — joins the panel-hover cue stack (R135 drop-
6412
+ // shadow boost + R345 title letter-spacing tween 0.3 → 0.4
6413
+ // + R266 fill theme-flip). Cyber 0.92 → 0.97, light 0.97 →
6414
+ // 1.0 on hoveredPanel === 'recent'. The panel "solidifies"
6415
+ // on hover — pure paint-level change, geometry-safe (bbox
6416
+ // unchanged so topo-overlap-test invariants hold). The
6417
+ // R247 transition list already includes `opacity 200ms
6418
+ // ease-out` so the value tween is automatic. Sibling
6419
+ // change at legend panel rect below (~line 7222).
6420
+ opacity={hoveredPanel === 'recent' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}
6217
6421
  style={{
6218
6422
  /* R135: drop-shadow intensifies on panel hover. Base
6219
6423
  shadow (2px / 6px blur) signals card elevation
@@ -6265,7 +6469,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6265
6469
  title (line ~6556) — both panels share the same
6266
6470
  editorial-text-spacing convention. data-recent-panel-
6267
6471
  title handle unchanged so R266 test still resolves. */}
6268
- <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight="700" letterSpacing="0.3" style={{ transition: 'fill 200ms ease-out' }} data-recent-panel-title>recent signal</text>
6472
+ {/* Round 345 / Loop: recent-signal panel title gains
6473
+ letter-spacing tween 0.3 → 0.4 on panel hover.
6474
+ hoveredPanel === 'recent' is set by the panel <g>
6475
+ wrapper's onMouseEnter (line ~6263 area). Sibling to
6476
+ R344 hover-letter-spacing applied to the +N more
6477
+ flows footer — same gesture vocabulary at a panel-
6478
+ title scope: hovering the panel chrome spreads the
6479
+ title 0.1 px, signalling "this is a coherent unit
6480
+ you're entering". transition list extends letter-
6481
+ spacing 200ms ease-out alongside existing fill 200ms. */}
6482
+ <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight="700" letterSpacing={hoveredPanel === 'recent' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out' }} data-recent-panel-title>recent signal</text>
6269
6483
  {/* R96: header count now matches what the rows show. Pre-R96
6270
6484
  this read "X msgs" off the raw messages array, but the
6271
6485
  rows below render DEDUPED flowLinks — so a fleet with 10
@@ -6326,8 +6540,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6326
6540
  const alpha = ageSec <= 30
6327
6541
  ? 1
6328
6542
  : ageSec <= 300
6329
- ? 1 - ((ageSec - 30) / 270) * 0.75
6330
- : 0.25;
6543
+ ? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
6544
+ : 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
6331
6545
  // Dark cyan-400 / light teal-600 with alpha — same
6332
6546
  // palette as R161's chip bullet so the two scopes
6333
6547
  // visually align even side-by-side.
@@ -6340,6 +6554,21 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6340
6554
  textAnchor="end"
6341
6555
  fontSize="10"
6342
6556
  fontFamily="monospace"
6557
+ // Round 349 / Loop: editorial letter-spacing 0.2 on the
6558
+ // recent-signal panel header count. Sits one tier below
6559
+ // the R301 panel title letterSpacing="0.3" so the panel
6560
+ // header reads as a 2-step hierarchy (title 0.3 / count
6561
+ // 0.2). Sibling change on the legend panel count below
6562
+ // closes the panel-pair editorial symmetry. Joins the
6563
+ // R285 / R289 / R301 / R302 / R304 / R325 editorial-
6564
+ // letterspacing tier at the panel-summary scope. The
6565
+ // R162 freshness fill, R225 tabular-nums, R311 fw=600,
6566
+ // R336 unit-tspan opacity-0.7 split all preserved —
6567
+ // the tier propagates to all descendant tspans via
6568
+ // SVG inheritance. data-recent-panel-count-letter-
6569
+ // spacing exposes the value for tests.
6570
+ letterSpacing="0.2"
6571
+ data-recent-panel-count-letter-spacing="0.2"
6343
6572
  >
6344
6573
  {/* Round 225 / Loop: tabular-nums on the panel-header
6345
6574
  flow-count tspan. The "{N} flows" string lives in
@@ -6808,17 +7037,38 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6808
7037
  const alpha = ageSec <= 30
6809
7038
  ? 1
6810
7039
  : ageSec <= 300
6811
- ? 1 - ((ageSec - 30) / 270) * 0.75
6812
- : 0.25;
7040
+ ? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
7041
+ : 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
6813
7042
  return (
6814
7043
  <circle
6815
7044
  cx={10}
6816
7045
  cy={38 + index * 16 - 3}
6817
- r={1.6}
7046
+ /* Round 359 / Loop: recency pip base radius
7047
+ 1.6 → 1.8. Sibling lift to R358's freshness-
7048
+ floor bump (alpha 0.25 → 0.30) — pre-R358/
7049
+ R359 the stale pip painted at r=1.6 + α=0.25
7050
+ which read as near-invisible chrome. R358
7051
+ gave it more alpha; R359 gives it more area
7052
+ (1.8² / 1.6² ≈ 1.27, so ~27 % more glyph)
7053
+ so the pip stays distinguishable across the
7054
+ freshness ramp. Geometry: 1.8-radius dot
7055
+ centred at (10, row_y - 3) is bbox 3.6×3.6,
7056
+ still well inside the 7-px left margin
7057
+ (x=6 rect-start → x=13 text-start) the R160
7058
+ pip was placed in. Overlap-test reads the
7059
+ parent row rect's bbox, not this pip's, so
7060
+ grid+ring invariants hold. Matches the same
7061
+ 1.6 → 1.8 visual-weight bump R295 applied
7062
+ to the legend swatch (5.5 → 6 base radius)
7063
+ and R287 to the minimap viewport stroke
7064
+ (1 → 1.5). data-recent-row-freshness-radius
7065
+ attr exposes the value for tests. */
7066
+ r={1.8}
6818
7067
  fill={pal.legendAccent}
6819
7068
  opacity={alpha}
6820
7069
  data-recent-row-freshness={link.key}
6821
7070
  data-recent-row-freshness-alpha={alpha.toFixed(2)}
7071
+ data-recent-row-freshness-radius="1.8"
6822
7072
  style={{ pointerEvents: 'none', transition: 'opacity 200ms ease-out' }}
6823
7073
  />
6824
7074
  );
@@ -7100,6 +7350,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7100
7350
  preserved for the <title> tooltip — only the SVG
7101
7351
  render splits. data-recent-panel-more-unit exposes
7102
7352
  the unit tspan for tests. */}
7353
+ {/* Round 344 / Loop: footer hover gains letter-spacing
7354
+ tween 0.2 → 0.3. R325 set rest letter-spacing
7355
+ 0.2 to join the editorial-spacing family; R344
7356
+ adds a 0.1px hover spread that layers on top of
7357
+ R195 cyan fill + R325 spacing + R133 underline
7358
+ so the footer reads "lit up and spaced" on
7359
+ hover — sibling to R218/R219/R220 pin-signature
7360
+ letter-spacing family applied to a hover-only
7361
+ surface. transition list extends letter-spacing
7362
+ 200ms ease-out alongside the existing opacity/
7363
+ fill easings. */}
7103
7364
  <text
7104
7365
  x="115" y="82"
7105
7366
  textAnchor="middle"
@@ -7107,12 +7368,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7107
7368
  fontSize="9"
7108
7369
  fontFamily="monospace"
7109
7370
  fontStyle="italic"
7110
- letterSpacing="0.2"
7371
+ letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}
7111
7372
  opacity={hoveredRecentMore ? 0.85 : 0.55}
7112
7373
  textDecoration={hoveredRecentMore ? 'underline' : 'none'}
7113
7374
  data-recent-panel-more={moreCount}
7114
7375
  data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}
7115
- style={{ transition: 'opacity 150ms ease-out, fill 200ms ease-out' }}
7376
+ style={{ transition: 'opacity 150ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out' }}
7116
7377
  >
7117
7378
  {`+ ${moreCount}`}
7118
7379
  <tspan opacity="0.7" data-recent-panel-more-unit>{` more flow${moreCount === 1 ? '' : 's'}`}</tspan>
@@ -7164,7 +7425,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7164
7425
  x="0" y="0" width="224" height="88" rx="10"
7165
7426
  fill={pal.legendBox.fill}
7166
7427
  stroke={pal.legendBox.stroke}
7167
- opacity={isLight ? 0.97 : 0.92}
7428
+ // R348 sibling legend panel rect opacity hover-state
7429
+ // bump 0.92 → 0.97 (cyber) / 0.97 → 1 (light) on
7430
+ // hoveredPanel === 'legend'. Pairs with the recent-signal
7431
+ // panel rect above so the two corner panels' hover cues
7432
+ // stay symmetric. Geometry-safe (paint-only).
7433
+ opacity={hoveredPanel === 'legend' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}
7168
7434
  style={{
7169
7435
  filter: hoveredPanel === 'legend'
7170
7436
  ? (isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.14))'
@@ -7187,7 +7453,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7187
7453
  surrounding chrome eased; R266 closes both at once. */}
7188
7454
  {/* R301: sibling to recent-signal panel title above —
7189
7455
  same letterSpacing 0.3 for editorial parity. */}
7190
- <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight="700" letterSpacing="0.3" style={{ transition: 'fill 200ms ease-out' }} data-legend-panel-title>legend</text>
7456
+ {/* R345 sibling legend panel title same hover letter-
7457
+ spacing tween 0.3 → 0.4 on panel hover. */}
7458
+ <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight="700" letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out' }} data-legend-panel-title>legend</text>
7191
7459
  {/* Round 257 / Loop: legend panel header count picks up the
7192
7460
  symmetric 13L/13R inner-padding pattern from the recent-
7193
7461
  signal panel. Pre-R257 the legend header was 13px from
@@ -7251,7 +7519,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7251
7519
  <text
7252
7520
  x="211" y="21" textAnchor="end"
7253
7521
  fill={pal.legendAccent} fontSize="10" fontFamily="monospace" fontWeight="600"
7522
+ // R349 sibling — legend panel header count picks up
7523
+ // letterSpacing="0.2", one tier below the R301 panel
7524
+ // title 0.3. Pairs with the recent-signal panel count
7525
+ // letter-spacing above so the two corner panels' header
7526
+ // typography stays editorially symmetric.
7527
+ letterSpacing="0.2"
7254
7528
  data-legend-panel-count
7529
+ data-legend-panel-count-letter-spacing="0.2"
7255
7530
  style={{
7256
7531
  transition: 'fill 200ms ease-out',
7257
7532
  fontVariantNumeric: 'tabular-nums',
@@ -7793,7 +8068,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7793
8068
  resetView();
7794
8069
  }
7795
8070
  }}
8071
+ // R346: viewport rect hover affordance driven by parent.
8072
+ onMouseEnter={() => setHoveredMinimap(true)}
8073
+ onMouseLeave={() => setHoveredMinimap(false)}
8074
+ onFocus={() => setHoveredMinimap(true)}
8075
+ onBlur={() => setHoveredMinimap(false)}
7796
8076
  data-topo-minimap
8077
+ data-topo-minimap-hovered={hoveredMinimap ? 'true' : 'false'}
7797
8078
  >
7798
8079
  <svg width={MW} height={MH} viewBox={`0 0 ${MW} ${MH}`} style={{ display: 'block' }}>
7799
8080
  {/* Round 198 / Loop: minimap dots gain smooth status
@@ -7872,13 +8153,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7872
8153
  x={Math.max(0, rectX)} y={Math.max(0, rectY)}
7873
8154
  width={Math.max(0, Math.min(MW - Math.max(0, rectX), rectW))}
7874
8155
  height={Math.max(0, Math.min(MH - Math.max(0, rectY), rectH))}
7875
- fill="none" stroke={pal.legendAccent} strokeWidth="1.5" opacity="0.9"
8156
+ fill="none" stroke={pal.legendAccent}
8157
+ // R346: strokeWidth + opacity tween on container hover.
8158
+ strokeWidth={hoveredMinimap ? '1.75' : '1.5'}
8159
+ opacity={hoveredMinimap ? '1' : '0.9'}
7876
8160
  data-topo-minimap-viewport
7877
8161
  data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}
8162
+ data-topo-minimap-viewport-hover={hoveredMinimap ? 'true' : 'false'}
7878
8163
  style={{
7879
8164
  transition: smoothView
7880
- ? 'x 280ms ease-out, y 280ms ease-out, width 280ms ease-out, height 280ms ease-out'
7881
- : 'none',
8165
+ ? 'x 280ms ease-out, y 280ms ease-out, width 280ms ease-out, height 280ms ease-out, stroke-width 200ms ease-out, opacity 200ms ease-out'
8166
+ : 'stroke-width 200ms ease-out, opacity 200ms ease-out',
7882
8167
  } as React.CSSProperties}
7883
8168
  />
7884
8169
  </svg>
@@ -8031,7 +8316,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8031
8316
  // R196: press-state deepens bg one tier above hover (white/5
8032
8317
  // → white/10) so mouse-down has a tactile dim before the
8033
8318
  // R186 icon pop fires on release.
8034
- className="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"
8319
+ // R352: `group` lets the inner svg respond via group-hover.
8320
+ 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"
8035
8321
  style={{ color: pal.legendText }}
8036
8322
  aria-label="Zoom out"
8037
8323
  title="Zoom out (−)"
@@ -8039,11 +8325,25 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8039
8325
  {/* R186: icon pop on click. CSS animation runs once;
8040
8326
  React removes the class after 240ms so a quick
8041
8327
  re-click can replay. */}
8328
+ {/* Round 352 / Loop: zoom-out icon picks up group-hover:
8329
+ scale-110 — sibling to R350 reset hover-rotate. Pre-
8330
+ R352 hovering the zoom button only changed the bg
8331
+ (white/5); the icon inside stayed perfectly still.
8332
+ R352 lifts the icon 10% on hover for a tactile "this
8333
+ button does something" cue. The R186 anet-chrome-pop
8334
+ keyframe (220ms scale 1→1.06→1) still owns transform
8335
+ during click via CSS-animation precedence over
8336
+ transition-transform; after the pop ends + className
8337
+ is removed, the group-hover scale-110 picks up
8338
+ smoothly. `transform-gpu` hint promotes the svg to
8339
+ its own compositor layer for crisper edges during
8340
+ the scale tween. Sibling change on zoom-in icon
8341
+ below. */}
8042
8342
  <svg
8043
8343
  width="12" height="12" viewBox="0 0 24 24"
8044
8344
  fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
8045
8345
  aria-hidden
8046
- className={chromePopping === 'zoom-out' ? 'anet-chrome-pop' : undefined}
8346
+ className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-out' ? ' anet-chrome-pop' : ''}`}
8047
8347
  data-topo-chrome-zoom-out-icon
8048
8348
  ><path d="M5 12h14" /></svg>
8049
8349
  </button>
@@ -8089,11 +8389,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8089
8389
  chromePopping === 'zoom-in' || chromePopping === 'zoom-out'
8090
8390
  ? 'true' : 'false'
8091
8391
  }
8392
+ data-topo-chrome-zoom-level-hover={hoveredZoomLevel ? 'true' : 'false'}
8393
+ onMouseEnter={() => setHoveredZoomLevel(true)}
8394
+ onMouseLeave={() => setHoveredZoomLevel(false)}
8092
8395
  style={{
8093
8396
  color: pal.legendText,
8094
8397
  borderColor: pal.containerBorder,
8095
8398
  minWidth: 46,
8096
8399
  display: 'inline-block',
8400
+ // R347: letter-spacing hover tween — extends R344/R345
8401
+ // hover-letter-spacing family into the chrome strip.
8402
+ letterSpacing: hoveredZoomLevel ? '0.5px' : '0',
8097
8403
  /* Round 264 / Loop: zoom level readout gains theme-toggle
8098
8404
  transition. The span has theme-driven color (pal.
8099
8405
  legendText) + border-x (pal.containerBorder via the
@@ -8102,7 +8408,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8102
8408
  on theme flip while siblings eased. Sibling treatment
8103
8409
  to the nodeSize + zoom wrapper transitions added this
8104
8410
  round. */
8105
- transition: 'color 200ms ease-out, border-color 200ms ease-out',
8411
+ transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out',
8106
8412
  }}
8107
8413
  title="Current zoom level"
8108
8414
  >
@@ -8113,18 +8419,22 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8113
8419
  data-topo-chrome-zoom-in
8114
8420
  data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}
8115
8421
  // R196: press-state (mirror of zoom-out above).
8116
- className="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"
8422
+ // R352: `group` lets the inner svg respond via group-hover.
8423
+ 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"
8117
8424
  style={{ color: pal.legendText }}
8118
8425
  aria-label="Zoom in"
8119
8426
  title="Zoom in (+)"
8120
8427
  >
8121
8428
  {/* R186: icon pop on click. Same one-shot CSS animation
8122
8429
  as zoom-out; React removes the class after 240ms. */}
8430
+ {/* R352 sibling — zoom-in icon picks up the same
8431
+ group-hover:scale-110 family. Mirror change at
8432
+ the zoom-out icon above. */}
8123
8433
  <svg
8124
8434
  width="12" height="12" viewBox="0 0 24 24"
8125
8435
  fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
8126
8436
  aria-hidden
8127
- className={chromePopping === 'zoom-in' ? 'anet-chrome-pop' : undefined}
8437
+ className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-in' ? ' anet-chrome-pop' : ''}`}
8128
8438
  data-topo-chrome-zoom-in-icon
8129
8439
  ><path d="M12 5v14M5 12h14" /></svg>
8130
8440
  </button>
@@ -8133,6 +8443,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8133
8443
  onClick={() => { armResetSpin(); resetView(); }}
8134
8444
  data-topo-chrome-reset
8135
8445
  data-topo-chrome-reset-spinning={resetSpinning ? 'true' : 'false'}
8446
+ data-topo-chrome-reset-hover={hoveredReset ? 'true' : 'false'}
8447
+ // R350: hover state drives the icon transform below.
8448
+ onMouseEnter={() => setHoveredReset(true)}
8449
+ onMouseLeave={() => setHoveredReset(false)}
8450
+ onFocus={() => setHoveredReset(true)}
8451
+ onBlur={() => setHoveredReset(false)}
8136
8452
  // R196: press-state deepens before R184 reset-spin fires on
8137
8453
  // release — mouse-down dim then 450ms spin = full handshake.
8138
8454
  className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
@@ -8162,6 +8478,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8162
8478
  aria-hidden
8163
8479
  className={resetSpinning ? 'anet-reset-spin' : undefined}
8164
8480
  data-topo-chrome-reset-icon
8481
+ // R350: hover-rotate preview of the R184 click-spin.
8482
+ // Gated on !resetSpinning so the anet-reset-spin keyframe
8483
+ // owns transform during its 450ms run. transformOrigin
8484
+ // 'center' so rotation pivots around the icon's centre
8485
+ // (default would be top-left and the icon would arc).
8486
+ style={{
8487
+ transform: hoveredReset && !resetSpinning ? 'rotate(-8deg)' : 'rotate(0deg)',
8488
+ transformOrigin: 'center',
8489
+ transition: 'transform 200ms ease-out',
8490
+ }}
8491
+ data-topo-chrome-reset-icon-hover={hoveredReset && !resetSpinning ? 'true' : 'false'}
8165
8492
  >
8166
8493
  <path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8" />
8167
8494
  <path d="M3 3v5h5" />
@@ -8195,7 +8522,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8195
8522
  its inactive state benefits from the same "hover previews
8196
8523
  active state" idiom R163 designed. Sibling treatment to
8197
8524
  the nodeSize buttons at line ~6711. */
8198
- className={`p-1.5 rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
8525
+ // R353: `group` lets the inner svg respond via group-hover
8526
+ // sibling to R352 zoom buttons. Closes the chrome-strip per-
8527
+ // icon hover-affordance arc (zoom-out / zoom-in / reset /
8528
+ // fullscreen now all carry an icon-level hover gesture in
8529
+ // addition to the bg hover).
8530
+ className={`group p-1.5 rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
8199
8531
  isFullscreen
8200
8532
  ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'
8201
8533
  : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'
@@ -8214,12 +8546,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8214
8546
  at the reset icon above. data-topo-chrome-fullscreen-
8215
8547
  icon attribute exposes BOTH variants (entered / exited)
8216
8548
  for the round's stroke-width regression probe. */}
8549
+ {/* Round 353 / Loop: fullscreen icon (both enter + exit
8550
+ variants) picks up the R352 family group-hover:scale-110.
8551
+ Pre-R353 hovering the button only changed the bg; the
8552
+ icon stayed still. R353 lifts the icon 10 % on hover —
8553
+ same gesture vocabulary as the zoom buttons. transform-
8554
+ gpu hint promotes the svg to its own compositor layer
8555
+ for crisper edges during the scale tween. Closes the
8556
+ chrome-strip per-icon hover-affordance arc. */}
8217
8557
  {isFullscreen ? (
8218
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden data-topo-chrome-fullscreen-icon="exit">
8558
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu" data-topo-chrome-fullscreen-icon="exit">
8219
8559
  <path d="M8 3v4a1 1 0 0 1-1 1H3M21 8h-4a1 1 0 0 1-1-1V3M3 16h4a1 1 0 0 1 1 1v4M16 21v-4a1 1 0 0 1 1-1h4" />
8220
8560
  </svg>
8221
8561
  ) : (
8222
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden data-topo-chrome-fullscreen-icon="enter">
8562
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu" data-topo-chrome-fullscreen-icon="enter">
8223
8563
  <path d="M3 8V5a2 2 0 0 1 2-2h3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M21 16v3a2 2 0 0 1-2 2h-3" />
8224
8564
  </svg>
8225
8565
  )}