@sleep2agi/agent-network-dashboard 0.5.2-preview.2 → 0.5.2-preview.25

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 (161) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +7 -7
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/admin.html +1 -1
  21. package/.next/server/app/admin.rsc +1 -1
  22. package/.next/server/app/admin.segments/_full.segment.rsc +1 -1
  23. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  24. package/.next/server/app/admin.segments/_index.segment.rsc +1 -1
  25. package/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  27. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  28. package/.next/server/app/index.html +2 -2
  29. package/.next/server/app/index.rsc +2 -2
  30. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  31. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  32. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  33. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  34. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  35. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/login.html +2 -2
  37. package/.next/server/app/login.rsc +2 -2
  38. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  39. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  40. package/.next/server/app/login.segments/_index.segment.rsc +1 -1
  41. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  44. package/.next/server/app/logs.html +1 -1
  45. package/.next/server/app/logs.rsc +1 -1
  46. package/.next/server/app/logs.segments/_full.segment.rsc +1 -1
  47. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  48. package/.next/server/app/logs.segments/_index.segment.rsc +1 -1
  49. package/.next/server/app/logs.segments/_tree.segment.rsc +1 -1
  50. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  52. package/.next/server/app/messages.html +1 -1
  53. package/.next/server/app/messages.rsc +1 -1
  54. package/.next/server/app/messages.segments/_full.segment.rsc +1 -1
  55. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  56. package/.next/server/app/messages.segments/_index.segment.rsc +1 -1
  57. package/.next/server/app/messages.segments/_tree.segment.rsc +1 -1
  58. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  59. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  60. package/.next/server/app/node.html +1 -1
  61. package/.next/server/app/node.rsc +1 -1
  62. package/.next/server/app/node.segments/_full.segment.rsc +1 -1
  63. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  64. package/.next/server/app/node.segments/_index.segment.rsc +1 -1
  65. package/.next/server/app/node.segments/_tree.segment.rsc +1 -1
  66. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  67. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  68. package/.next/server/app/nodes.html +1 -1
  69. package/.next/server/app/nodes.rsc +1 -1
  70. package/.next/server/app/nodes.segments/_full.segment.rsc +1 -1
  71. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  72. package/.next/server/app/nodes.segments/_index.segment.rsc +1 -1
  73. package/.next/server/app/nodes.segments/_tree.segment.rsc +1 -1
  74. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  75. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  76. package/.next/server/app/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app/server-logs.html +1 -1
  78. package/.next/server/app/server-logs.rsc +1 -1
  79. package/.next/server/app/server-logs.segments/_full.segment.rsc +1 -1
  80. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  81. package/.next/server/app/server-logs.segments/_index.segment.rsc +1 -1
  82. package/.next/server/app/server-logs.segments/_tree.segment.rsc +1 -1
  83. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  84. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  85. package/.next/server/app/settings/networks.html +1 -1
  86. package/.next/server/app/settings/networks.rsc +1 -1
  87. package/.next/server/app/settings/networks.segments/_full.segment.rsc +1 -1
  88. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  89. package/.next/server/app/settings/networks.segments/_index.segment.rsc +1 -1
  90. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +1 -1
  91. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  93. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  94. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/settings/tokens.html +1 -1
  96. package/.next/server/app/settings/tokens.rsc +1 -1
  97. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +1 -1
  98. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  99. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +1 -1
  100. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +1 -1
  101. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  102. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  103. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  104. package/.next/server/app/settings.html +2 -2
  105. package/.next/server/app/settings.rsc +2 -2
  106. package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  109. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  110. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  111. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  112. package/.next/server/app/tasks.html +1 -1
  113. package/.next/server/app/tasks.rsc +1 -1
  114. package/.next/server/app/tasks.segments/_full.segment.rsc +1 -1
  115. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  116. package/.next/server/app/tasks.segments/_index.segment.rsc +1 -1
  117. package/.next/server/app/tasks.segments/_tree.segment.rsc +1 -1
  118. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  119. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  120. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
  121. package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -1
  122. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
  123. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  124. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  125. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  126. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  127. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  128. package/.next/server/middleware-build-manifest.js +3 -3
  129. package/.next/server/pages/404.html +1 -1
  130. package/.next/server/pages/500.html +1 -1
  131. package/.next/static/chunks/0n06tze_y9dlf.js +1 -0
  132. package/.next/static/chunks/0u-~k4hsu~8wq.js +4 -0
  133. package/.next/static/chunks/{0slrhg-~r8l06.js → 0zil_-x~u.a7r.js} +1 -1
  134. package/.next/static/chunks/13l51qkvb6a09.js +1 -0
  135. package/.next/trace +2 -2
  136. package/.next/trace-build +1 -1
  137. package/app/components/TopoGraph.tsx +405 -37
  138. package/package.json +1 -1
  139. package/screenshots/v0.10.4-150-orphans/after.png +0 -0
  140. package/scripts/p150-orphan-layout-screenshot.mjs +82 -0
  141. package/scripts/topo-edge-badge-hot-glow-test.mjs +101 -0
  142. package/scripts/topo-edge-particle-opacity-lift-test.mjs +109 -0
  143. package/scripts/topo-group-label-glow-test.mjs +104 -0
  144. package/scripts/topo-hub-digit-glow-test.mjs +105 -0
  145. package/scripts/topo-legend-panel-title-fw-test.mjs +95 -0
  146. package/scripts/topo-legend-pin-ring-glow-test.mjs +105 -0
  147. package/scripts/topo-legend-row-text-cadence-test.mjs +93 -0
  148. package/scripts/topo-legend-row-tint-cadence-test.mjs +92 -0
  149. package/scripts/topo-minimap-dot-lift-test.mjs +115 -0
  150. package/scripts/topo-minimap-zoom-glow-test.mjs +86 -0
  151. package/scripts/topo-recent-panel-title-fw-test.mjs +106 -0
  152. package/scripts/topo-recent-row-freshness-glow-test.mjs +100 -0
  153. package/scripts/topo-recent-row-text-cadence-test.mjs +99 -0
  154. package/scripts/topo-recent-row-tint-cadence-test.mjs +96 -0
  155. package/scripts/topo-recent-row-ts-lift-test.mjs +97 -0
  156. package/.next/static/chunks/08ja1js.s74hj.js +0 -1
  157. package/.next/static/chunks/0cstcv2xh_f0p.js +0 -1
  158. package/.next/static/chunks/137a8ptkr6xg1.js +0 -4
  159. /package/.next/static/{i-jEoAS_A1mp9AfWPF-GU → 1r_vZ0kOyyWXIRiRstneR}/_buildManifest.js +0 -0
  160. /package/.next/static/{i-jEoAS_A1mp9AfWPF-GU → 1r_vZ0kOyyWXIRiRstneR}/_clientMiddlewareManifest.js +0 -0
  161. /package/.next/static/{i-jEoAS_A1mp9AfWPF-GU → 1r_vZ0kOyyWXIRiRstneR}/_ssgManifest.js +0 -0
@@ -759,28 +759,38 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
759
759
  else runs.push({ key: gk, members: [s] });
760
760
  }
761
761
 
762
- // Pass 1 — assign each run to a band: a multi-member group owns its
763
- // rows (left-aligned, so its bounding box is a tidy rect); contiguous
764
- // singletons pack into shared rows (centred). Collect total row count.
765
- type Band = { members: Session[]; startRow: number; centred: boolean; isGroup: boolean };
762
+ // Pass 1 — assign each run to a band.
763
+ //
764
+ // v0.10.4 #150 (Vincent /goal 5453): "不是一起的落单的怎么散落在中间了".
765
+ // Pre-#150 algo interleaved singletons between real groups as
766
+ // centred bands, so orphan nodes appeared scattered in the middle
767
+ // between cluster boxes. Vincent screenshot called this out as
768
+ // "layout 算法一点都不好". Fix: bundle ALL singletons into ONE
769
+ // band at the bottom of the grid + render an "其他" cluster box
770
+ // around them. Multi-member prefix groups still go first in
771
+ // alias order (existing #83/#111 behaviour). Net effect:
772
+ // row 0..N-1: real prefix groups (left-aligned, own cluster box)
773
+ // row N..M: single "其他" band collecting all orphans
774
+ // (left-aligned, single cluster box at bottom)
775
+ // No orphans → no orphan band → behaviour identical to pre-#150
776
+ // for fleets where every node has a prefix-group match.
777
+ type Band = { members: Session[]; startRow: number; centred: boolean; isGroup: boolean; isOrphan?: boolean };
766
778
  const bands: Band[] = [];
767
779
  let row = 0;
768
- let i = 0;
769
- while (i < runs.length) {
770
- if (runs[i].members.length >= 2) {
771
- bands.push({ members: runs[i].members, startRow: row, centred: false, isGroup: true });
772
- row += Math.ceil(runs[i].members.length / cols);
773
- i++;
780
+ const orphanMembers: Session[] = [];
781
+ for (const run of runs) {
782
+ if (run.members.length >= 2) {
783
+ bands.push({ members: run.members, startRow: row, centred: false, isGroup: true });
784
+ row += Math.ceil(run.members.length / cols);
774
785
  } else {
775
- const singles: Session[] = [];
776
- while (i < runs.length && runs[i].members.length < 2) {
777
- singles.push(runs[i].members[0]);
778
- i++;
779
- }
780
- bands.push({ members: singles, startRow: row, centred: true, isGroup: false });
781
- row += Math.ceil(singles.length / cols);
786
+ // single-member run collect for the bottom orphan band
787
+ orphanMembers.push(...run.members);
782
788
  }
783
789
  }
790
+ if (orphanMembers.length > 0) {
791
+ bands.push({ members: orphanMembers, startRow: row, centred: false, isGroup: true, isOrphan: true });
792
+ row += Math.ceil(orphanMembers.length / cols);
793
+ }
784
794
  const totalRows = Math.max(1, row);
785
795
  // #112: the group label sits in a band ABOVE the topmost node, so the
786
796
  // band must clear the node radius — GROUP_TOP is node-relative, never
@@ -852,8 +862,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
852
862
  else if (isOn) i++;
853
863
  else o++;
854
864
  }
865
+ // v0.10.4 #150 — orphan band (singletons bundled at bottom)
866
+ // renders with a "其他" cluster box; the box-key drives the
867
+ // R63 label render + R86 hover-pin keying + #99 tooltip
868
+ // member listing, so all the existing group-box machinery
869
+ // applies uniformly to the orphan bucket too.
855
870
  return {
856
- key: band.members.length ? groupKeys[band.members[0].alias] : '',
871
+ key: band.isOrphan
872
+ ? '其他'
873
+ : band.members.length
874
+ ? groupKeys[band.members[0].alias]
875
+ : '',
857
876
  count: band.members.length,
858
877
  statuses: { working: w, idle: i, offline: o },
859
878
  x: minX - GROUP_PAD,
@@ -4761,10 +4780,35 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
4761
4780
  opacity={isPinned || isHovered ? 1 : 0.55}
4762
4781
  data-group-label-hovered={isHovered && !isPinned ? 'true' : 'false'}
4763
4782
  data-group-label-font-weight={isPinned ? '800' : '700'}
4783
+ /* Round 479 / Loop — extend drop-shadow visual-polish
4784
+ family to a 4th anchor: group-label parent text
4785
+ on isPinned. Continues the R476/R477/R478 arc:
4786
+ R476 hub digit hover-gated emerald
4787
+ R477 legend pin-ring pin-gated row.fill
4788
+ R478 recent-row pip freshness-gated cyan
4789
+ R479 group-label text pin-gated cyan
4790
+ Hue: pal.legendAccent at 0x80 alpha (≈50%) — same
4791
+ accent family R107/R477 use for tint surfaces. 3px
4792
+ blur reads as a soft cyan halo around the locked
4793
+ cluster name. Stacks with the R432 letter-spacing
4794
+ spread + R457 fw lift + R63 fill brighten + R142
4795
+ drop-shadow on the parent rect — pin signature on
4796
+ group label scope now spans typography + chroma +
4797
+ paint + container-lift + text-glow.
4798
+ Filter is paint-only; bbox unchanged; overlap-test
4799
+ invariants hold (R51 selector gated to g[data-node]
4800
+ descendants, this label is invisible to the probe).
4801
+ transition list extends to include 'filter 200ms
4802
+ ease-out' alongside the existing fill/ls/fw/opacity
4803
+ 200ms tweens. */
4804
+ data-group-label-glow={isPinned ? 'true' : 'false'}
4764
4805
  style={{
4765
- transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out',
4806
+ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
4766
4807
  letterSpacing: isPinned ? '0.5px' :
4767
4808
  isHovered ? '0.25px' : '0px',
4809
+ filter: isPinned
4810
+ ? `drop-shadow(0 0 3px ${pal.legendAccent}80)`
4811
+ : undefined,
4768
4812
  }}
4769
4813
  data-group-label={box.key}
4770
4814
  data-group-label-pinned={isPinned ? 'true' : 'false'}
@@ -5338,10 +5382,32 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
5338
5382
  r={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}
5339
5383
  fill={pal.flowParticle}
5340
5384
  filter={isLight ? undefined : 'url(#topo-glow)'}
5341
- opacity={Math.min(1, fresh * edgeOpacityMul)}
5385
+ /* Round 485 / Loop — extends R484's "inspection
5386
+ overrides encoding" pattern to a 2nd anchor:
5387
+ edge particle opacity lifts to 1.0 on
5388
+ isHoveredEdge OR isEndpointHoveredEdge (user
5389
+ hovering the edge directly OR hovering one
5390
+ of its endpoint nodes). Pre-R485 the particle
5391
+ inherited freshness × edgeOpacityMul decay
5392
+ so a stale edge's particle painted near the
5393
+ 0.30 floor even when the operator was
5394
+ inspecting it; R485 lifts to 1.0 on attention.
5395
+ data-recent-row-ts-alpha-attribute analog —
5396
+ freshness encoding preserved on rest tier,
5397
+ opacity override engages only on inspection.
5398
+ Sibling lift family — inspection-overrides-
5399
+ encoding pattern, now 2 anchors:
5400
+ R484 recent-row timestamp freshness → 1.0
5401
+ R485 edge particle freshness → 1.0 (this)
5402
+ data-edge-particle-opacity-lifted attr exposes
5403
+ the override gate; data-edge-particle-opacity-
5404
+ rest preserves the freshness reading. */
5405
+ opacity={(isHoveredEdge || isEndpointHoveredEdge) ? 1 : Math.min(1, fresh * edgeOpacityMul)}
5342
5406
  data-edge-particle={link.key}
5343
5407
  data-edge-particle-radius={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}
5344
5408
  data-edge-particle-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
5409
+ data-edge-particle-opacity-rest={Math.min(1, fresh * edgeOpacityMul).toFixed(2)}
5410
+ data-edge-particle-opacity-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
5345
5411
  style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out, r 200ms ease-out' }}
5346
5412
  >
5347
5413
  <animateMotion
@@ -5790,6 +5856,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
5790
5856
  via the new -opacity-active attr; the
5791
5857
  legacy -opacity-hover attr kept for R395
5792
5858
  test compatibility. */}
5859
+ {/* Round 480 / Loop — 5th anchor in the drop-shadow
5860
+ visual-polish family. Gates on isHot (link.
5861
+ count >= 10, R129 hot-lane threshold) so the
5862
+ badge gets a warm-amber halo when its edge
5863
+ crosses the high-traffic boundary.
5864
+ Drop-shadow family ledger now:
5865
+ R476 hub digit hover-gated emerald
5866
+ R477 legend pin-ring pin-gated row.fill
5867
+ R478 freshness pip freshness-gated cyan
5868
+ R479 group label pin-gated cyan
5869
+ R480 edge badge hot-lane-gated amber ← this round
5870
+ 5th gate type — traffic volume — joins hover,
5871
+ pin, freshness, pin. Each polish anchor uses
5872
+ a distinct semantic gate but the same paint
5873
+ vocabulary. Hue: hotStroke (amber-tinted
5874
+ palette member) at 0x80 alpha — picks up the
5875
+ R126/R188 hot-edge accent colour family so
5876
+ the glow reads as a chromatic extension of
5877
+ the existing hot-lane stroke. 3-px blur
5878
+ radius reads as soft heat rather than
5879
+ emergency klaxon.
5880
+ R51 sentinel safety: badge sw=2 only matters
5881
+ when the overlap probe runs on g[data-node]
5882
+ descendants, which this edge-internal badge
5883
+ is not. Filter is paint-only, bbox unchanged.
5884
+ transition list extends to include 'filter
5885
+ 200ms ease-out' so the heat halo eases on
5886
+ the count-crosses-threshold flip. */}
5793
5887
  <circle
5794
5888
  cx={badgeX} cy={badgeY}
5795
5889
  r={isHoveredEdge || isPinned ? 10.5 : 9}
@@ -5804,7 +5898,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
5804
5898
  data-edge-badge-opacity-rest={isLight ? 0.95 : 0.85}
5805
5899
  data-edge-badge-opacity-hover="1"
5806
5900
  data-edge-badge-opacity-active="1"
5807
- style={{ transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out' }}
5901
+ data-edge-badge-glow={isHot ? 'true' : 'false'}
5902
+ style={{
5903
+ filter: isHot
5904
+ ? `drop-shadow(0 0 3px ${hotStroke}80)`
5905
+ : undefined,
5906
+ transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
5907
+ }}
5808
5908
  />
5809
5909
  {/* Round 224 / Loop: edge badge text gains the 4th
5810
5910
  pin-signature typography. Pre-R224 the digit
@@ -6303,15 +6403,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
6303
6403
  /* Round 253 / Loop: append fill 200ms to the hub
6304
6404
  digit transition list — theme toggle (cyber #ecfdf5
6305
6405
  ↔ light #d1fae5) was the last hub-area snap. */
6406
+ /* Round 476 / Loop — hub working-count digit gains a
6407
+ filter: drop-shadow glow on hub-hover. Stacks with
6408
+ the existing 4-axis hub-hover gesture stack on this
6409
+ element:
6410
+ R209 transform: scale(1.08) geometry
6411
+ R425 fontWeight 700 → 800 typography
6412
+ R253 fill ease-out chroma (theme)
6413
+ R213 opacity gate fade (count cross)
6414
+ R476 filter drop-shadow glow paint (this round)
6415
+ The glow uses the cyber emerald-400 (#34d399) /
6416
+ light emerald-500 (#10b981) hue family so the
6417
+ chroma stays inside the hub-area palette. Subtle
6418
+ 2-3 px blur radius at 0.6 opacity — visible but
6419
+ not loud, reads as "the focal digit lit up under
6420
+ attention".
6421
+ Reduced-motion users skip the filter via the
6422
+ !reducedMotion gate (R29 a11y blanket).
6423
+ Filter is a paint-only attribute — bbox stays
6424
+ the same, R51 overlap-test invariants hold.
6425
+ transition list extends to 'filter 200ms ease-out'
6426
+ so the glow eases under the same cadence as the
6427
+ scale + fw + fill axes. */
6428
+ data-topo-hub-working-count-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
6306
6429
  style={{
6307
6430
  pointerEvents: 'none',
6308
6431
  transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',
6309
6432
  transformBox: 'fill-box',
6310
6433
  transformOrigin: 'center',
6434
+ filter: !reducedMotion && hoveredHub
6435
+ ? (isLight
6436
+ ? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6))'
6437
+ : 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6))')
6438
+ : undefined,
6311
6439
  /* R425: font-weight 200ms appended so the hover fw
6312
6440
  bump 700 → 800 eases under the same cadence as
6313
- R209 scale + R253 fill + R213 opacity. */
6314
- transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out',
6441
+ R209 scale + R253 fill + R213 opacity.
6442
+ R476: filter 200ms appended so the new drop-
6443
+ shadow glow eases at the same cadence. */
6444
+ transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out',
6315
6445
  fontVariantNumeric: 'tabular-nums',
6316
6446
  }}
6317
6447
  >
@@ -8295,8 +8425,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8295
8425
  title scope: hovering the panel chrome spreads the
8296
8426
  title 0.1 px, signalling "this is a coherent unit
8297
8427
  you're entering". transition list extends letter-
8298
- spacing 200ms ease-out alongside existing fill 200ms. */}
8299
- <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>
8428
+ spacing 200ms ease-out alongside existing fill 200ms.
8429
+ Round 482 / Loop add 2nd typographic axis to the
8430
+ title: fontWeight 700 → 800 on activeEdgeKey (any
8431
+ row hover OR pin propagates from hoveredEdgeKey ??
8432
+ pinnedEdgeKey). Pre-R482 the title only responded
8433
+ to panel-chrome hover via R345 ls; when a specific
8434
+ row was hovered/pinned inside the panel, the title
8435
+ stayed flat. R482 closes the gap: when ANY row is
8436
+ active inside the panel, the title tightens
8437
+ typographically alongside the row's own R143 lift +
8438
+ R472 tint + R474 text spread. data tightens under
8439
+ attention pattern extension (panel-scope variant
8440
+ following R416/R424/R425/R426/R444/R445/R446/R457
8441
+ at the chip / panel / hub / edge / count / parent-
8442
+ label tiers).
8443
+ transition list extends to include 'font-weight
8444
+ 200ms ease-out' alongside R345's ls + R55's fill
8445
+ 200ms. data-recent-panel-title-fw exposes the
8446
+ resolved weight for tests. */}
8447
+ <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight={activeEdgeKey ? '800' : '700'} letterSpacing={hoveredPanel === 'recent' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out' }} data-recent-panel-title data-recent-panel-title-fw={activeEdgeKey ? '800' : '700'} data-recent-panel-title-active={activeEdgeKey ? 'true' : 'false'}>recent signal</text>
8300
8448
  {/* R96: header count now matches what the rows show. Pre-R96
8301
8449
  this read "X msgs" off the raw messages array, but the
8302
8450
  rows below render DEDUPED flowLinks — so a fleet with 10
@@ -8825,6 +8973,22 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8825
8973
  the chip-row pills. R116: pinned rows tint
8826
8974
  stronger than hovered ones so locked vs preview
8827
8975
  is discriminable. */}
8976
+ {/* Round 472 / Loop — cadence-sync follow-on to the
8977
+ R459/R460/R461/R464/R465/R470 200ms uniform
8978
+ motion stack established at the cluster scope.
8979
+ This R104 recent-signal row tint rect was still
8980
+ at the legacy 150ms cadence — when a user
8981
+ hovers/pins a recent-signal row, the tint
8982
+ snapped in 50ms ahead of the rest of the row's
8983
+ state-change cascade (R143 translateY,
8984
+ R220+R434 letter-spacing, R434 fill tween).
8985
+ R472 lifts to 200ms ease-out to match. Same
8986
+ sibling idiom R459 closed at the group-label
8987
+ hitbox tier; now applied at the recent-signal
8988
+ row tier. data-recent-row-tint-transition attr
8989
+ exposes the cadence for tests.
8990
+ Geometry/paint logic unchanged — purely the
8991
+ transition timing. */}
8828
8992
  <rect
8829
8993
  x="6" y={38 + index * 16 - 10}
8830
8994
  width="218" height="14" rx="3"
@@ -8832,7 +8996,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8832
8996
  opacity={isRowPinned ? (isLight ? 0.18 : 0.22)
8833
8997
  : isRowHovered ? (isLight ? 0.10 : 0.14)
8834
8998
  : 1}
8835
- style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}
8999
+ data-recent-row-tint={link.key}
9000
+ data-recent-row-tint-transition="200ms"
9001
+ style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out' }}
8836
9002
  />
8837
9003
  {/* Round 160 / Loop: recency pip. Canvas flow edges
8838
9004
  fade by freshness (R10: full intensity ≤30s →
@@ -8934,16 +9100,47 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
8934
9100
  to include 'r 200ms ease-out' matching the
8935
9101
  opacity cadence. data-recent-row-freshness-
8936
9102
  lifted attr exposes the gate for tests. */
9103
+ /* Round 478 / Loop — extend the R476/R477
9104
+ drop-shadow vocabulary to a third anchor:
9105
+ the recent-row freshness pip on `alpha
9106
+ > 0.7` (just-fired flow within ~30s per
9107
+ R10 freshness ramp). Gate is FRESHNESS-
9108
+ driven not pin/hover-driven, so the glow
9109
+ reads as "this signal is live" rather
9110
+ than "user is inspecting". As the alpha
9111
+ decays past 0.7 (≈45s after last fire),
9112
+ the glow eases off — natural breathing
9113
+ feel that tracks actual data freshness.
9114
+ Hue: pal.legendAccent at 0.5 alpha so
9115
+ the glow inherits the row's accent color
9116
+ family. 2.5-3px blur reads as soft
9117
+ radiance, not loud bloom.
9118
+ Drop-shadow visual-polish family now 3
9119
+ anchors:
9120
+ R476 hub digit hover-gated
9121
+ R477 legend pin-ring pin-gated
9122
+ R478 recent freshness freshness-gated
9123
+ Each anchor uses a different state gate
9124
+ but the same `filter: drop-shadow` paint
9125
+ vocabulary. Filter affects paint only —
9126
+ bbox unchanged, overlap-test invariants
9127
+ hold. Transition list extends to include
9128
+ 'filter 200ms ease-out' alongside
9129
+ R10/R447 opacity + r tweens. */
8937
9130
  fill={pal.legendAccent}
8938
9131
  opacity={alpha}
8939
9132
  data-recent-row-freshness={link.key}
8940
9133
  data-recent-row-freshness-alpha={alpha.toFixed(2)}
8941
9134
  data-recent-row-freshness-radius={(isRowHovered || isRowPinned) ? 2.5 : 2.0}
8942
9135
  data-recent-row-freshness-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}
9136
+ data-recent-row-freshness-glow={alpha > 0.7 ? 'true' : 'false'}
8943
9137
  style={{
8944
9138
  pointerEvents: 'none',
8945
9139
  r: `${(isRowHovered || isRowPinned) ? 2.5 : 2.0}px`,
8946
- transition: 'opacity 200ms ease-out, r 200ms ease-out',
9140
+ filter: alpha > 0.7
9141
+ ? `drop-shadow(0 0 3px ${pal.legendAccent}80)`
9142
+ : undefined,
9143
+ transition: 'opacity 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',
8947
9144
  } as React.CSSProperties}
8948
9145
  />
8949
9146
  );
@@ -9028,8 +9225,25 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9028
9225
  R427/R431/R432/R433/R434. R55 fill 150ms +
9029
9226
  R220 letter-spacing 150ms transition kept
9030
9227
  (additive conditional case, no new property). */
9228
+ /* Round 474 / Loop — cadence-sync follow-on to
9229
+ R472. R472 lifted the recent-row TINT RECT
9230
+ to 200ms but the row TEXT alongside still
9231
+ ran 150ms — same panel-row scope, two
9232
+ different rates. When a user hovered/pinned
9233
+ a row the rect background brightened in
9234
+ 200ms while the text fill + letter-spacing
9235
+ finished in 150ms. R474 closes that internal
9236
+ desync by lifting the text transitions to
9237
+ match. Whole recent-row state-flip now
9238
+ eases at 200ms ease-out across rect AND
9239
+ text. data-recent-row-text-transition='200ms'
9240
+ attr exposed for tests. R434 3-tier letter-
9241
+ spacing values unchanged; R363 fw + R55 fill
9242
+ brighten unchanged — only the timing axis
9243
+ shifts. */
9244
+ data-recent-row-text-transition="200ms"
9031
9245
  style={{
9032
- transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',
9246
+ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
9033
9247
  letterSpacing: isRowPinned ? '0.5px' :
9034
9248
  isRowHovered ? '0.25px' : '0px',
9035
9249
  }}
@@ -9146,6 +9360,18 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9146
9360
  {' · '}
9147
9361
  <tspan opacity="0.7" data-recent-row-content-tspan>{truncate(link.content, 8)}</tspan>
9148
9362
  </text>
9363
+ {/* Round 484 / Loop — recent-row timestamp opacity
9364
+ lifts to 1.0 when isRowHovered || isRowPinned,
9365
+ regardless of freshness alpha. R191 origin
9366
+ decays tsAlpha along with the row's freshness;
9367
+ pre-R484 hovering/pinning the row left the
9368
+ timestamp dim — user inspecting stale data
9369
+ fought the freshness encoding. R484 lifts to
9370
+ 1.0 on attention. Sibling to R472/R474 in the
9371
+ recent-row state-flip family. data-recent-row-
9372
+ ts-lifted attr exposes the gate; original
9373
+ data-recent-row-ts-alpha preserved as R191
9374
+ freshness reading. */}
9149
9375
  {lastAt ? (
9150
9376
  /* Round 321 / Loop: lastAt freshness timestamp picks
9151
9377
  up fontVariantNumeric tabular-nums. The string
@@ -9169,9 +9395,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9169
9395
  fill={pal.legendText}
9170
9396
  fontSize="8"
9171
9397
  fontFamily="monospace"
9172
- opacity={tsAlpha}
9398
+ opacity={(isRowHovered || isRowPinned) ? 1 : tsAlpha}
9173
9399
  data-recent-row-ts={link.key}
9174
9400
  data-recent-row-ts-alpha={tsAlpha.toFixed(2)}
9401
+ data-recent-row-ts-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}
9175
9402
  style={{
9176
9403
  pointerEvents: 'none',
9177
9404
  transition: 'opacity 200ms ease-out',
@@ -9446,9 +9673,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9446
9673
  surrounding chrome eased; R266 closes both at once. */}
9447
9674
  {/* R301: sibling to recent-signal panel title above —
9448
9675
  same letterSpacing 0.3 for editorial parity. */}
9676
+ {/* Round 483 / Loop — sibling to R482 (recent-signal panel
9677
+ title): legend panel title fontWeight 700 → 800 on
9678
+ pinnedStatus (any legend row pinned propagates to the
9679
+ panel title). Pre-R483 the title responded only to
9680
+ panel-chrome hover via R345 ls; the pinnedStatus row
9681
+ highlighted its own swatch + tint via R181/R477 but
9682
+ the title stayed flat — no upstream tightening to
9683
+ signal "panel context = inspecting".
9684
+ R483 closes the symmetry with R482: both panel titles
9685
+ (recent-signal + legend) now tighten typographically
9686
+ when ANY row inside them is in the active filter
9687
+ state. Same idiom, mirrored at the legend-row scope.
9688
+ data tightens family — now 10 anchors:
9689
+ R416/R424/R425/R426 chip/panel/hub/edge digits
9690
+ R444/R445/R446 group/recent/legend counts
9691
+ R457 group-label parent
9692
+ R482 recent-panel title
9693
+ R483 legend-panel title (this round)
9694
+ transition list extends to include 'font-weight 200ms
9695
+ ease-out' alongside R345's ls + R55's fill 200ms.
9696
+ data-legend-panel-title-fw + -active exposed for tests. */}
9449
9697
  {/* R345 sibling — legend panel title same hover letter-
9450
9698
  spacing tween 0.3 → 0.4 on panel hover. */}
9451
- <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>
9699
+ <text x="13" y="21" fill={pal.legendHeadline} fontSize="12" fontFamily="monospace" fontWeight={pinnedStatus ? '800' : '700'} letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out' }} data-legend-panel-title data-legend-panel-title-fw={pinnedStatus ? '800' : '700'} data-legend-panel-title-active={pinnedStatus ? 'true' : 'false'}>legend</text>
9452
9700
  {/* Round 257 / Loop: legend panel header count picks up the
9453
9701
  symmetric 13L/13R inner-padding pattern from the recent-
9454
9702
  signal panel. Pre-R257 the legend header was 13px from
@@ -9685,6 +9933,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9685
9933
  now ~1.25px below hitbox center (vs ~2.25px pre).
9686
9934
  No height change, no test ripple (other than this
9687
9935
  one), no R260/R268/R270 chrome regressions. */}
9936
+ {/* Round 473 / Loop — final cadence-sync follow-on,
9937
+ closing the legacy 150ms transition at the
9938
+ LEGEND-ROW tint scope. R459 (group-label hitbox)
9939
+ + R472 (recent-row hitbox) already lifted the
9940
+ two sibling panel-row hitboxes to 200ms; the
9941
+ legend-row was the last per-row tint still
9942
+ snapping at 150ms.
9943
+ After R473 the 200ms ease-out vocabulary is
9944
+ uniform across ALL three panel-row scopes —
9945
+ group-label, recent-signal, and legend — so
9946
+ hover/pin state-change cascades read coherently
9947
+ at every panel-tier surface. data-legend-row-
9948
+ tint-transition='200ms' attr exposed for tests.
9949
+ Geometry/paint unchanged. */}
9688
9950
  <rect
9689
9951
  x="6" y={row.y0 - 11}
9690
9952
  width="170" height="22" rx="3"
@@ -9693,7 +9955,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9693
9955
  : hoveredStatus === row.key ? (isLight ? 0.08 : 0.12)
9694
9956
  : 1}
9695
9957
  data-legend-row-tinted={isPinned ? 'pinned' : hoveredStatus === row.key ? 'hover' : 'none'}
9696
- style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}
9958
+ data-legend-row-tint-transition="200ms"
9959
+ style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out' }}
9697
9960
  />
9698
9961
  {/* Round 197 / Loop: swatch dot scales r 5.5 → 7 when its
9699
9962
  row is hovered or pinned. Pre-R197 the swatch was a
@@ -9778,6 +10041,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9778
10041
  + pointerEvents:none all preserved. data-legend-
9779
10042
  pin-ring-stroke-width attr exposes the value for
9780
10043
  tests. */}
10044
+ {/* Round 477 / Loop — legend pin-ring gains a filter:
10045
+ drop-shadow glow on isPinned. Extends R476's
10046
+ drop-shadow idiom from hub-digit (focal scope)
10047
+ to the legend-row pin-ring (sibling pin-state
10048
+ surface). When a status row is pinned, the
10049
+ concentric ring around the swatch now lights
10050
+ up with a colour-matched halo using row.fill,
10051
+ reinforcing "this filter is locked" via a
10052
+ glow layer above the R402 sw bump + R181
10053
+ opacity fade-in.
10054
+ Hue: row.fill at 0.55 alpha — picks up each
10055
+ status tier's signature colour (working green /
10056
+ idle teal / offline slate). 3px blur stays
10057
+ subtle but unmistakable when the row is locked.
10058
+ Reduced-motion users skip the filter via R29
10059
+ a11y blanket (transition-duration → 0.001ms
10060
+ so the glow appears/disappears instantly with
10061
+ pin toggle).
10062
+ Filter is paint-only — bbox unchanged, R51
10063
+ overlap-test gated to g[data-node] descendants
10064
+ so this legend-internal ring is invisible to
10065
+ the probe anyway. Transition list extends to
10066
+ include 'filter 200ms ease-out' so the glow
10067
+ eases under the same cadence as opacity. */}
9781
10068
  <circle
9782
10069
  cx="16" cy={row.y0} r="8"
9783
10070
  fill="none"
@@ -9787,9 +10074,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9787
10074
  data-legend-pin-ring={row.key}
9788
10075
  data-legend-pin-ring-pinned={isPinned ? 'true' : 'false'}
9789
10076
  data-legend-pin-ring-stroke-width="1.75"
10077
+ data-legend-pin-ring-glow={isPinned ? 'true' : 'false'}
9790
10078
  style={{
9791
10079
  pointerEvents: 'none',
9792
- transition: 'opacity 150ms ease-out',
10080
+ filter: isPinned
10081
+ ? `drop-shadow(0 0 3px ${row.fill}88)`
10082
+ : undefined,
10083
+ transition: 'opacity 150ms ease-out, filter 200ms ease-out',
9793
10084
  }}
9794
10085
  />
9795
10086
  {/* Round 219 / Loop: legend row text gains the same
@@ -9864,8 +10155,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
9864
10155
  group-label R432, legend-row R433). R55 fill
9865
10156
  150ms + R219 letter-spacing 150ms transition
9866
10157
  untouched — additive conditional case. */
10158
+ /* Round 475 / Loop — final closure of the panel-row
10159
+ text scope cadence-sync. R473 lifted the legend-
10160
+ row TINT RECT to 200ms; R474 lifted the recent-
10161
+ row TEXT to 200ms; R475 closes the matching
10162
+ legend-row text desync — fill + letter-spacing
10163
+ both 150 → 200ms ease-out. After R475 the 3-tier
10164
+ panel-row cadence family is fully 200ms across
10165
+ BOTH rect and text at every panel-row scope
10166
+ (group-label / recent-row / legend-row). Hover/
10167
+ pin state-flip at any panel-row tier reads as
10168
+ one motion-coherent unit. data-legend-row-
10169
+ label-transition='200ms' attr exposed for tests.
10170
+ R433 3-tier letter-spacing values (0/0.25/0.5)
10171
+ unchanged; R55 fill brighten unchanged — only
10172
+ the timing axis shifts. */
10173
+ data-legend-row-label-transition="200ms"
9867
10174
  style={{
9868
- transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',
10175
+ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
9869
10176
  letterSpacing: isPinned ? '0.5px' :
9870
10177
  hoveredStatus === row.key ? '0.25px' : '0px',
9871
10178
  }}
@@ -10285,12 +10592,41 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
10285
10592
  stale ones. data-topo-minimap-dot-opacity
10286
10593
  attr (R392) reflects the resolved hover-
10287
10594
  state value for tests. */
10595
+ /* Round 486 / Loop — 3rd anchor in the
10596
+ inspection-overrides-encoding pattern. Sibling
10597
+ to R484 (recent-row timestamp) + R485 (edge
10598
+ particle). When the operator hovers a node
10599
+ alias on the main canvas, the matching
10600
+ minimap dot lifts to opacity=1.0 regardless
10601
+ of the binary online/offline encoding —
10602
+ cross-reference cue between canvas focal
10603
+ and the minimap wayfinding overlay.
10604
+ Pre-R486 an offline node's minimap dot stayed
10605
+ at 0.6 even when the operator was inspecting
10606
+ it via canvas hover; R486 makes the
10607
+ inspection signal jump the minimap dot to
10608
+ full presence so the spatial reference is
10609
+ unambiguous.
10610
+ Encoding survives: data-topo-minimap-dot-
10611
+ online preserves the online/offline binary,
10612
+ data-topo-minimap-dot-opacity-rest preserves
10613
+ the would-be opacity. Only the LIVE painted
10614
+ opacity flips on inspection.
10615
+ inspection-overrides-encoding family — 3
10616
+ anchors now:
10617
+ R484 recent-row timestamp
10618
+ R485 edge particle
10619
+ R486 minimap dot ← this round
10620
+ data-topo-minimap-dot-lifted attr exposes
10621
+ the override gate. */
10288
10622
  r={isOn ? 1.9 : 1.2}
10289
10623
  fill={st.primary}
10290
- opacity={isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6}
10624
+ opacity={hoveredAlias === s.alias ? 1 : (isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6)}
10291
10625
  data-topo-minimap-dot={s.alias}
10292
10626
  data-topo-minimap-dot-online={isOn ? 'true' : 'false'}
10293
- data-topo-minimap-dot-opacity={isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6}
10627
+ data-topo-minimap-dot-opacity={hoveredAlias === s.alias ? 1 : (isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6)}
10628
+ data-topo-minimap-dot-opacity-rest={isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6}
10629
+ data-topo-minimap-dot-lifted={hoveredAlias === s.alias ? 'true' : 'false'}
10294
10630
  data-topo-minimap-dot-radius={isOn ? 1.9 : 1.2}
10295
10631
  style={{
10296
10632
  transition: 'opacity 200ms ease-out, fill 200ms ease-out, r 200ms ease-out',
@@ -10414,10 +10750,42 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
10414
10750
  data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}
10415
10751
  data-topo-minimap-viewport-hover={hoveredMinimap ? 'true' : 'false'}
10416
10752
  data-topo-minimap-viewport-linejoin="round"
10753
+ /* Round 481 / Loop — 6th anchor in the drop-shadow
10754
+ visual-polish family. New gate type: ZOOM STATE.
10755
+ When current canvas zoom > 1.5x (50% above the
10756
+ default 1.0 baseline), the minimap viewport rect
10757
+ gains a soft cyan halo signaling "you're zoomed
10758
+ in beyond default". The minimap viewport already
10759
+ shrinks as you zoom in (rectW = VIEWBOX_W /
10760
+ view.zoom * sx, so at zoom=2 it halves) — the
10761
+ glow tells you the wayfinding marker is now
10762
+ scaled-down rather than at canvas-default size.
10763
+ Drop-shadow family — 6 gate types covered:
10764
+ R476 hub digit hover-gated
10765
+ R477 legend pin-ring pin-gated
10766
+ R478 freshness pip freshness-gated
10767
+ R479 group label pin-gated
10768
+ R480 edge badge hot-lane-gated
10769
+ R481 minimap zoom-gated ← this round
10770
+ 6 distinct semantic gates (user interaction
10771
+ transient/sticky × 2, data freshness, data
10772
+ volume, canvas zoom state). Each anchor uses
10773
+ hue family appropriate to its semantic context.
10774
+ Hue: pal.legendAccent at 0x80 alpha — matches
10775
+ the existing R107 tint family and R478/R479
10776
+ cyan-tone choices. 2-px blur reads as subtle
10777
+ (the minimap viewport is small, ~120×82 px).
10778
+ Filter is paint-only — bbox unchanged. transition
10779
+ list extends to include 'filter 200ms ease-out'
10780
+ so the glow eases when zoom crosses 1.5x. */
10781
+ data-topo-minimap-viewport-glow={view.zoom > 1.5 ? 'true' : 'false'}
10417
10782
  style={{
10783
+ filter: view.zoom > 1.5
10784
+ ? `drop-shadow(0 0 2px ${pal.legendAccent}80)`
10785
+ : undefined,
10418
10786
  transition: smoothView
10419
- ? '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'
10420
- : 'stroke-width 200ms ease-out, opacity 200ms ease-out',
10787
+ ? '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, filter 200ms ease-out'
10788
+ : 'stroke-width 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
10421
10789
  } as React.CSSProperties}
10422
10790
  />
10423
10791
  </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.2-preview.2",
3
+ "version": "0.5.2-preview.25",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",