@sleep2agi/agent-network-dashboard 0.5.1-preview.7 → 0.5.1-preview.71
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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +6 -6
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +2 -2
- package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +2 -2
- package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +2 -2
- package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +2 -2
- package/.next/server/app/node.segments/_full.segment.rsc +2 -2
- package/.next/server/app/node.segments/_head.segment.rsc +1 -1
- package/.next/server/app/node.segments/_index.segment.rsc +2 -2
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/node.segments/node.segment.rsc +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +2 -2
- package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +2 -2
- package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +3 -3
- package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +2 -2
- package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/04.j~y8e~sbs4.js +1 -0
- package/.next/static/chunks/06llamqb4jsu..js +4 -0
- package/.next/static/chunks/0_p8jkzdw5x2_.css +2 -0
- package/.next/static/chunks/12heglqfrp1bm.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +1426 -84
- package/package.json +1 -1
- package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
- package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
- package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
- package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
- package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
- package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
- package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
- package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
- package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
- package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
- package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
- package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
- package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
- package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
- package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
- package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
- package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
- package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
- package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
- package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
- package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
- package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
- package/scripts/topo-fullscreen-icon-hover-scale-test.mjs +91 -0
- package/scripts/topo-group-box-stroke-test.mjs +105 -0
- package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
- package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
- package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
- package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
- package/scripts/topo-hover-detail-rx-test.mjs +81 -0
- package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
- package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
- package/scripts/topo-hub-halo-radius-test.mjs +86 -0
- package/scripts/topo-hub-halo-trough-test.mjs +83 -0
- package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
- package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
- package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
- package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
- package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
- package/scripts/topo-layout-toggle-hover-tracking-test.mjs +109 -0
- package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
- package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
- package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
- package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
- package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
- package/scripts/topo-minimap-online-radius-test.mjs +85 -0
- package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
- package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
- package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
- package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
- package/scripts/topo-node-pulse-peak-test.mjs +89 -0
- package/scripts/topo-panel-count-letterspacing-test.mjs +89 -0
- package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
- package/scripts/topo-pressure-bar-height-test.mjs +92 -0
- package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
- package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
- package/scripts/topo-recent-pip-radius-test.mjs +76 -0
- package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
- package/scripts/topo-reset-hover-rotate-test.mjs +102 -0
- package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
- package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
- package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
- package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
- package/scripts/topo-zoom-icon-hover-scale-test.mjs +114 -0
- package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
- package/.next/static/chunks/0aauz~36q5n2a.css +0 -2
- package/.next/static/chunks/0bja1amnrg3li.js +0 -1
- package/.next/static/chunks/0k~uc0~~19hyy.js +0 -4
- package/.next/static/chunks/0wtq_6dnzems6.js +0 -1
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_buildManifest.js +0 -0
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → gaK6yNvVjshUCmKR9qrPn}/_ssgManifest.js +0 -0
|
@@ -194,7 +194,27 @@ function FreshnessChip({ sessions }: { sessions: unknown }) {
|
|
|
194
194
|
// tier (R312-R314 family); the amber bg/text/border still does
|
|
195
195
|
// the warning-state work, the weight just keeps the chip in the
|
|
196
196
|
// same data-typography ladder as its siblings.
|
|
197
|
-
|
|
197
|
+
// Round 377 / Loop: FreshnessChip baseClass picks up `tabular-nums`.
|
|
198
|
+
// The chip-row's last untouched chip joins the R224-R357 broader
|
|
199
|
+
// tabular-nums sweep:
|
|
200
|
+
// R224 edge badge digit
|
|
201
|
+
// R225 hub digit / panel header counts / recent row count
|
|
202
|
+
// R232 chip row counts (working / online / active-links)
|
|
203
|
+
// R321 recent row timestamp
|
|
204
|
+
// R322 recent panel hot count
|
|
205
|
+
// R323 filter pin pill counts
|
|
206
|
+
// R333 vendor count suffix
|
|
207
|
+
// R357 active-links freshness suffix wrapper
|
|
208
|
+
// R377 FreshnessChip body (this round)
|
|
209
|
+
// `font-mono` already gives equal-width glyphs but `tabular-nums`
|
|
210
|
+
// is the explicit-invariant the rest of the chip row carries.
|
|
211
|
+
// FreshnessChip body reads `lag · {sec}s` — the {sec} digit grows
|
|
212
|
+
// every second; tabular-nums explicitly locks digit width so the
|
|
213
|
+
// chip stays planted as the seconds counter ticks past 9 → 10 →
|
|
214
|
+
// 99 → 100. R187 transition-colors duration-300 + R275 stale-only
|
|
215
|
+
// render gate + R315 font-medium R313 family alignment all
|
|
216
|
+
// preserved.
|
|
217
|
+
const baseClass = "hidden sm:inline px-2.5 py-1 rounded-md font-mono font-medium tabular-nums border transition-colors duration-300";
|
|
198
218
|
const colorClass = stale
|
|
199
219
|
? "bg-amber-500/10 text-amber-300 border-amber-500/20"
|
|
200
220
|
: "bg-gray-500/10 text-gray-400 border-gray-500/20";
|
|
@@ -237,7 +257,21 @@ function FreshnessChip({ sessions }: { sessions: unknown }) {
|
|
|
237
257
|
duration-300 still eases the bg/color flip. Title (hover
|
|
238
258
|
tooltip) still spells out the full meaning in either
|
|
239
259
|
state. */}
|
|
240
|
-
{
|
|
260
|
+
{/* Round 410 / Loop: FreshnessChip body picks up the chip-
|
|
261
|
+
internal-hierarchy arc. Pre-R410 the body rendered as a
|
|
262
|
+
single text node `lag · {sec}s` with the parent's font-
|
|
263
|
+
medium (fw=500) applied uniformly. R410 splits the digit
|
|
264
|
+
and unit into separate spans so the chip's internal
|
|
265
|
+
typography mirrors the family pattern R333-R341/R362/R369/
|
|
266
|
+
R389 established for the chip row:
|
|
267
|
+
digit (fw=600) data tier
|
|
268
|
+
unit (fw=500 + opacity=0.7) label tier
|
|
269
|
+
The `lag` prefix stays at the chip's baseline (fw=500
|
|
270
|
+
from parent font-medium) — it labels the state, not a
|
|
271
|
+
data value. data-freshness-chip-digit / -unit attrs
|
|
272
|
+
surface the spans for tests. tabular-nums + transition-
|
|
273
|
+
colors + R275 stale-only gate all preserved. */}
|
|
274
|
+
{stale ? 'lag' : 'live'} · <span className="font-semibold" data-freshness-chip-digit>{sec}</span><span className="opacity-70" data-freshness-chip-unit>s</span>
|
|
241
275
|
</span>
|
|
242
276
|
);
|
|
243
277
|
}
|
|
@@ -1038,6 +1072,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1038
1072
|
// 280ms ease-out transition list matches R199 smoothView vocabulary
|
|
1039
1073
|
// so the visual joins the existing rhythm on the same rect.
|
|
1040
1074
|
const [hoveredMinimap, setHoveredMinimap] = useState(false);
|
|
1075
|
+
// Round 347 / Loop: zoom-level readout hover-state letter-spacing
|
|
1076
|
+
// tween (0 → 0.5 px). The readout sandwiched between zoom-out /
|
|
1077
|
+
// zoom-in is a passive percent display — pre-R347 it had no hover
|
|
1078
|
+
// feedback at all (only a `title` tooltip). R347 extends the R344
|
|
1079
|
+
// (`+N more flows` footer) + R345 (panel titles) hover-letter-
|
|
1080
|
+
// spacing family from panel/footer surfaces into the HTML chrome
|
|
1081
|
+
// strip. Hovering the readout spreads its digits 0.5 px, signalling
|
|
1082
|
+
// "this is alive". tabular-nums + minWidth: 46 from R225 still lock
|
|
1083
|
+
// the column so the tween doesn't shove neighbouring controls.
|
|
1084
|
+
// 200ms ease-out joins the existing R264 color/border transition
|
|
1085
|
+
// list on the same span.
|
|
1086
|
+
const [hoveredZoomLevel, setHoveredZoomLevel] = useState(false);
|
|
1087
|
+
// Round 350 / Loop: reset-button icon hover-rotate preview of the
|
|
1088
|
+
// R184 click-spin. Pre-R350 hovering the reset button only changed
|
|
1089
|
+
// the button bg (white/5); the icon inside stayed perfectly still.
|
|
1090
|
+
// R350 nudges the icon -8° on hover — a tactile hint that this
|
|
1091
|
+
// button rotates the icon on click. When the click fires, the
|
|
1092
|
+
// R184 anet-reset-spin keyframe animation overrides the hover
|
|
1093
|
+
// transform for its 450 ms run (CSS animations win over transitions
|
|
1094
|
+
// on the same property); when the animation ends + React removes
|
|
1095
|
+
// the className, the inline transform eases back to whatever the
|
|
1096
|
+
// hover state says — either -8° (still hovering) or 0 (mouse left).
|
|
1097
|
+
// 350th-round milestone polish.
|
|
1098
|
+
const [hoveredReset, setHoveredReset] = useState(false);
|
|
1041
1099
|
// R135: panel-wide hover-elevation. The recent-signal + legend
|
|
1042
1100
|
// panels both already host clickable rows (R56/R116 recent rows,
|
|
1043
1101
|
// R55/R61 legend rows) and a clickable footer (R133), so the
|
|
@@ -1799,12 +1857,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1799
1857
|
while honoring R328's wider baseline rhythm. data-topo-
|
|
1800
1858
|
chrome-layout-trailer attr unchanged — it still marks
|
|
1801
1859
|
the boundary surface for the gap probe. */}
|
|
1860
|
+
{/* Round 375 / Loop: Layout-toggle wrapper rounded-md → rounded-
|
|
1861
|
+
lg (6 → 8 px). Extends the corner-radius cascade family
|
|
1862
|
+
to the chrome-strip layout-toggle wrapper:
|
|
1863
|
+
R330 canvas wrapper rounded-xl 12 px
|
|
1864
|
+
R331 SVG panels rx=10 10 px
|
|
1865
|
+
R332 minimap container rounded-lg 8 px
|
|
1866
|
+
R375 Layout-toggle wrapper rounded-lg 8 px (this round)
|
|
1867
|
+
Pre-R375 the wrapper at rounded-md (6 px) was the only
|
|
1868
|
+
chrome-strip container still using the smaller corner
|
|
1869
|
+
radius — both R330 outer wrapper and R332 minimap sit at
|
|
1870
|
+
≥ 8 px, so the Layout toggle's 6 px stood out as a
|
|
1871
|
+
tighter corner against the family. R375 brings it into
|
|
1872
|
+
the rounded-lg tier where the minimap already lives.
|
|
1873
|
+
Pure paint change — overflow-hidden still clips the
|
|
1874
|
+
inner buttons' bg-cyan-500/15 tints; no layout shift.
|
|
1875
|
+
R268 border-color + 200ms transition + R329 mr-0.5 +
|
|
1876
|
+
data-topo-chrome-layout-trailer all preserved. */}
|
|
1802
1877
|
<div
|
|
1803
|
-
className="mr-0.5 inline-flex rounded-
|
|
1878
|
+
className="mr-0.5 inline-flex rounded-lg border overflow-hidden"
|
|
1804
1879
|
style={{ borderColor: pal.containerBorder, transition: 'border-color 200ms ease-out' }}
|
|
1805
1880
|
role="group"
|
|
1806
1881
|
aria-label="Topology layout"
|
|
1807
1882
|
data-topo-chrome-layout-trailer
|
|
1883
|
+
data-topo-chrome-layout-radius="rounded-lg"
|
|
1808
1884
|
>
|
|
1809
1885
|
<button
|
|
1810
1886
|
onClick={() => { popChrome('layout-ring'); if (layout !== 'ring') toggleLayout(); }}
|
|
@@ -1838,7 +1914,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1838
1914
|
weight; cyan-400/60 + ring-inset retained. The
|
|
1839
1915
|
R163/R196 hover/active deeps + R249 chrome-pop
|
|
1840
1916
|
click feedback continue unchanged. */
|
|
1841
|
-
|
|
1917
|
+
// R351: hover:tracking-wide extends the R344/R345/R347
|
|
1918
|
+
// hover-letter-spacing family to a 4th surface (chrome-
|
|
1919
|
+
// strip Ring/Grid pair). transition-colors className
|
|
1920
|
+
// dropped in favour of an inline transition spec that
|
|
1921
|
+
// bundles bg/color (150ms ease) + letter-spacing
|
|
1922
|
+
// (200ms ease-out) — Tailwind's transition-colors
|
|
1923
|
+
// doesn't list letter-spacing, so without this the
|
|
1924
|
+
// hover:tracking-wide would snap. Sibling change on
|
|
1925
|
+
// the Grid button below.
|
|
1926
|
+
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' : ''}`}
|
|
1927
|
+
style={{ transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out' }}
|
|
1842
1928
|
>
|
|
1843
1929
|
Ring
|
|
1844
1930
|
</button>
|
|
@@ -1855,14 +1941,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1855
1941
|
// Round 306 / Loop: focus-visible:ring-2 → ring-1 sibling
|
|
1856
1942
|
// change to Ring above — unifies focus-ring width across
|
|
1857
1943
|
// all chrome buttons.
|
|
1858
|
-
|
|
1944
|
+
// R351 sibling — Grid button picks up hover:tracking-wide
|
|
1945
|
+
// + inline transition spec. Same vocabulary as Ring.
|
|
1946
|
+
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' : ''}`}
|
|
1859
1947
|
/* Round 268 / Loop: Grid button's left border (the
|
|
1860
1948
|
internal divider between Ring and Grid) picks up
|
|
1861
1949
|
pal.containerBorder, matching the wrapper change at
|
|
1862
1950
|
line ~1460 and the chrome strip's segmented borders
|
|
1863
|
-
(nodeSize, zoom). transition-colors className
|
|
1864
|
-
the border-color
|
|
1865
|
-
|
|
1951
|
+
(nodeSize, zoom). The R268 transition-colors className
|
|
1952
|
+
used to carry the border-color ease; R351 unfolds the
|
|
1953
|
+
transition list into the inline spec below so the
|
|
1954
|
+
letter-spacing tween rides alongside without snapping
|
|
1955
|
+
the border-color flip — border-color 200ms ease-out
|
|
1956
|
+
keeps R268's theme-toggle smoothness intact. */
|
|
1957
|
+
style={{ borderColor: pal.containerBorder, transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out' }}
|
|
1866
1958
|
>
|
|
1867
1959
|
Grid
|
|
1868
1960
|
</button>
|
|
@@ -1949,11 +2041,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1949
2041
|
7th surface in the info-density tabular-nums
|
|
1950
2042
|
sweep — and the first on the HTML side
|
|
1951
2043
|
(previous 6 were SVG <text>/<tspan>). */
|
|
1952
|
-
|
|
2044
|
+
/* Round 398 / Loop: chip-row chips gain hover translateY
|
|
2045
|
+
(-1px) lift on the CLICKABLE variant only (workingCount
|
|
2046
|
+
> 0 here / onlineNodes.length > 0 below / activeLinks
|
|
2047
|
+
> 0 deeper). Pre-R398 the chips brightened bg + border
|
|
2048
|
+
on hover (R201) but didn't lift — only their clickable
|
|
2049
|
+
siblings (filter pin pills R397, recent rows R143,
|
|
2050
|
+
legend rows R144) acknowledged cursor entry with a
|
|
2051
|
+
translate-y. R398 closes the chip-row by extending
|
|
2052
|
+
the same gesture to the static header chips, gated
|
|
2053
|
+
on the clickable role so empty chips (which have
|
|
2054
|
+
no role="button") stay planted at their R205
|
|
2055
|
+
opacity-50 receded paint. transition-transform
|
|
2056
|
+
+ duration-200 + ease-out + transform-gpu added
|
|
2057
|
+
alongside existing transition-colors so the lift
|
|
2058
|
+
and the color tween share rhythm.
|
|
2059
|
+
Gesture-vocabulary table (post-R398):
|
|
2060
|
+
recent-signal row -1 px (R143)
|
|
2061
|
+
legend row -1 px (R144)
|
|
2062
|
+
group cluster box fill+sw lift (R142)
|
|
2063
|
+
filter pin pills -1 px (R397)
|
|
2064
|
+
chip-row chips -1 px (R398, this round)
|
|
2065
|
+
Empty chips: no lift. Pin-mirror chips: no
|
|
2066
|
+
conflict (R180 inset double-ring is a box-shadow
|
|
2067
|
+
not a transform). new data-chip-hover-lift attr
|
|
2068
|
+
surfaces the lift surface for tests. */
|
|
2069
|
+
className={`tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
|
|
1953
2070
|
workingCount > 0
|
|
1954
|
-
? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30'
|
|
2071
|
+
? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30 hover:-translate-y-px'
|
|
1955
2072
|
: 'bg-green-500/10 text-green-300 border-green-500/20'
|
|
1956
2073
|
}`}
|
|
2074
|
+
data-chip-hover-lift={workingCount > 0 ? 'true' : 'false'}
|
|
1957
2075
|
data-working-chip
|
|
1958
2076
|
data-working-chip-aliases={workingAliases.join(',')}
|
|
1959
2077
|
data-pin-mirror={pinnedStatus === 'working' ? 'true' : 'false'}
|
|
@@ -2023,7 +2141,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2023
2141
|
pattern: small label spans demote, value stays
|
|
2024
2142
|
prominent. data-working-chip-unit exposes the
|
|
2025
2143
|
span for tests. */}
|
|
2026
|
-
{
|
|
2144
|
+
{/* Round 362 / Loop: digit picks up font-semibold
|
|
2145
|
+
(fw 500 → 600) for within-chip weight tier. The
|
|
2146
|
+
chip's outer className stays at font-medium (R313
|
|
2147
|
+
data-weight baseline); the digit overrides to
|
|
2148
|
+
semibold so it reads heavier than its " working"
|
|
2149
|
+
unit (which keeps fw 500 + R338 opacity-70).
|
|
2150
|
+
Joins the R333-R341 chip-internal-hierarchy arc
|
|
2151
|
+
at the chip-count scope. Sibling edits on the
|
|
2152
|
+
online + active-links chip digits below. data-
|
|
2153
|
+
working-chip-digit attr exposes the digit span. */}
|
|
2154
|
+
<span className="font-semibold" data-working-chip-digit>{workingCount}</span><span className="opacity-70" data-working-chip-unit> working</span>
|
|
2027
2155
|
</span>
|
|
2028
2156
|
<span
|
|
2029
2157
|
// Round 201 / Loop: online chip — mirror of the working
|
|
@@ -2037,11 +2165,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2037
2165
|
/* Round 232 / Loop: tabular-nums on online chip
|
|
2038
2166
|
(sibling treatment to working chip — same row,
|
|
2039
2167
|
same digit-jitter physics on count crossings). */
|
|
2040
|
-
|
|
2168
|
+
// R398: hover translate-y lift on clickable variant — see working chip above.
|
|
2169
|
+
className={`tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
|
|
2041
2170
|
onlineNodes.length > 0
|
|
2042
|
-
? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30'
|
|
2171
|
+
? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30 hover:-translate-y-px'
|
|
2043
2172
|
: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'
|
|
2044
2173
|
}`}
|
|
2174
|
+
data-chip-hover-lift={onlineNodes.length > 0 ? 'true' : 'false'}
|
|
2045
2175
|
data-online-chip
|
|
2046
2176
|
data-online-chip-aliases={onlineAliases.join(',')}
|
|
2047
2177
|
data-pin-mirror={pinnedStatus === 'idle' ? 'true' : 'false'}
|
|
@@ -2086,7 +2216,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2086
2216
|
}}
|
|
2087
2217
|
>
|
|
2088
2218
|
{/* R337 sibling — online chip unit demotion. */}
|
|
2089
|
-
{
|
|
2219
|
+
{/* R362 sibling — online-chip digit gains font-semibold. */}
|
|
2220
|
+
<span className="font-semibold" data-online-chip-digit>{onlineNodes.length}</span><span className="opacity-70" data-online-chip-unit> online</span>
|
|
2090
2221
|
</span>
|
|
2091
2222
|
</>
|
|
2092
2223
|
);
|
|
@@ -2213,8 +2344,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2213
2344
|
title={`${w} working · ${i} idle · ${o} offline`}
|
|
2214
2345
|
data-fleet-pressure
|
|
2215
2346
|
>
|
|
2216
|
-
|
|
2217
|
-
|
|
2347
|
+
{/* Round 373 / Loop: pressure-bar kicker label gains
|
|
2348
|
+
font-medium (fw 400 → 500). Sibling small-text fw
|
|
2349
|
+
lift family with R363 recent-row alias + R364
|
|
2350
|
+
legend-row label + R366 group-label count + R368
|
|
2351
|
+
+N more flows footer — extends to a 5th surface
|
|
2352
|
+
(the chip-row's 'pressure' label). At fontSize
|
|
2353
|
+
10 px tracking-wide against the chip's gray bg,
|
|
2354
|
+
the default fw 400 sat below the deliberate-data
|
|
2355
|
+
band; fw 500 brings it into parity with the
|
|
2356
|
+
chip-row 'working / online / active links' unit
|
|
2357
|
+
spans (chip-level font-medium R313). data-fleet-
|
|
2358
|
+
pressure-kicker attr exposes the kicker for tests. */}
|
|
2359
|
+
<span className="text-[10px] tracking-wide font-medium" data-fleet-pressure-kicker>pressure</span>
|
|
2360
|
+
{/* Round 374 / Loop: pressure-bar height h-1.5 → h-2
|
|
2361
|
+
(6 → 8 px) — sibling visual-weight bump (8th anchor
|
|
2362
|
+
in the family):
|
|
2363
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
2364
|
+
R295 legend swatch base radius 5.5 → 6
|
|
2365
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
2366
|
+
R360 hub digit fontSize 11 → 12
|
|
2367
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
2368
|
+
R365 hub-highlight base radius 5 → 5.5
|
|
2369
|
+
R367 edge-badge rest stroke 1 → 1.25
|
|
2370
|
+
R374 pressure-bar height 1.5 → 2 (this round)
|
|
2371
|
+
+33 % bar height gives the working/idle/offline
|
|
2372
|
+
segments more visibility — at h-1.5 the 3-segment
|
|
2373
|
+
proportion bar was readable but slim; at h-2 the
|
|
2374
|
+
segments parse cleanly even when one tier is
|
|
2375
|
+
< 10 % share. Geometry-safe: items-center flex
|
|
2376
|
+
centers the bar inside the chip's py-1 (4 px top +
|
|
2377
|
+
4 px bottom) — bar at 8 px stays comfortably
|
|
2378
|
+
inside the 10-px text-row height. R165 segment
|
|
2379
|
+
width transitions + R210 brightness hover + R83
|
|
2380
|
+
pin-mirror box-shadow on segments all preserved
|
|
2381
|
+
(segments inherit width from parent so the height
|
|
2382
|
+
bump propagates without segment-side edits).
|
|
2383
|
+
data-fleet-pressure-bar-height attr exposes the
|
|
2384
|
+
height token for tests. */}
|
|
2385
|
+
<span className="inline-flex h-2 w-16 rounded-full overflow-hidden" style={{ background: 'rgb(75 85 99 / 0.25)' }} data-fleet-pressure-bar-height="h-2">
|
|
2218
2386
|
{seg(w, isLight ? '#059669' : '#22c55e', 'working', 'working')}
|
|
2219
2387
|
{seg(i, isLight ? '#0d9488' : '#2dd4bf', 'idle', 'idle')}
|
|
2220
2388
|
{seg(o, isLight ? '#94a3b8' : '#6b7280', 'offline', 'offline')}
|
|
@@ -2271,7 +2439,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2271
2439
|
data-active-filter="status"
|
|
2272
2440
|
data-filter-match-count={matchCount}
|
|
2273
2441
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2274
|
-
|
|
2442
|
+
// R355: `group` lets the inner opacity-70 spans (prefix
|
|
2443
|
+
// `filter:` + count `· N`) brighten to 100 % on pill hover.
|
|
2444
|
+
// Sibling treatment on group + vendor pills below.
|
|
2445
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2275
2446
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2276
2447
|
onClick={() => setPinnedStatus(null)}
|
|
2277
2448
|
style={{
|
|
@@ -2285,12 +2456,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2285
2456
|
cursor: 'pointer',
|
|
2286
2457
|
}}
|
|
2287
2458
|
>
|
|
2288
|
-
<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>
|
|
2459
|
+
<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>
|
|
2289
2460
|
<button
|
|
2290
2461
|
type="button"
|
|
2291
2462
|
aria-label={`Clear ${pinnedStatus} filter`}
|
|
2292
2463
|
onClick={(e) => { e.stopPropagation(); setPinnedStatus(null); }}
|
|
2293
|
-
|
|
2464
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2465
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2466
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2467
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2468
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2469
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2470
|
+
scale on hover so the click-target reads as "press me"
|
|
2471
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2472
|
+
button to its own compositor layer for crisper edges
|
|
2473
|
+
during the scale tween. transition-transform duration-
|
|
2474
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2475
|
+
inline-block is default for <button> so no display
|
|
2476
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2477
|
+
pills (status / group / vendor / edge) at once. */
|
|
2478
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2294
2479
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2295
2480
|
>×</button>
|
|
2296
2481
|
</span>
|
|
@@ -2309,7 +2494,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2309
2494
|
data-active-filter="group"
|
|
2310
2495
|
data-filter-match-count={matchCount}
|
|
2311
2496
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2312
|
-
|
|
2497
|
+
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2498
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2313
2499
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2314
2500
|
onClick={() => setPinnedGroup(null)}
|
|
2315
2501
|
style={{
|
|
@@ -2319,12 +2505,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2319
2505
|
cursor: 'pointer',
|
|
2320
2506
|
}}
|
|
2321
2507
|
>
|
|
2322
|
-
<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>
|
|
2508
|
+
<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>
|
|
2323
2509
|
<button
|
|
2324
2510
|
type="button"
|
|
2325
2511
|
aria-label={`Clear group filter ${pinnedGroup}`}
|
|
2326
2512
|
onClick={(e) => { e.stopPropagation(); setPinnedGroup(null); }}
|
|
2327
|
-
|
|
2513
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2514
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2515
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2516
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2517
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2518
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2519
|
+
scale on hover so the click-target reads as "press me"
|
|
2520
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2521
|
+
button to its own compositor layer for crisper edges
|
|
2522
|
+
during the scale tween. transition-transform duration-
|
|
2523
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2524
|
+
inline-block is default for <button> so no display
|
|
2525
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2526
|
+
pills (status / group / vendor / edge) at once. */
|
|
2527
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2328
2528
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2329
2529
|
>×</button>
|
|
2330
2530
|
</span>
|
|
@@ -2359,7 +2559,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2359
2559
|
data-active-filter="vendor"
|
|
2360
2560
|
data-filter-match-count={matchCount}
|
|
2361
2561
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2362
|
-
|
|
2562
|
+
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2563
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2363
2564
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
|
|
2364
2565
|
onClick={() => setPinnedVendor(null)}
|
|
2365
2566
|
style={{
|
|
@@ -2369,12 +2570,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2369
2570
|
cursor: 'pointer',
|
|
2370
2571
|
}}
|
|
2371
2572
|
>
|
|
2372
|
-
<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>
|
|
2573
|
+
<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>
|
|
2373
2574
|
<button
|
|
2374
2575
|
type="button"
|
|
2375
2576
|
aria-label={`Clear vendor filter ${pinnedVendor}`}
|
|
2376
2577
|
onClick={(e) => { e.stopPropagation(); setPinnedVendor(null); }}
|
|
2377
|
-
|
|
2578
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2579
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2580
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2581
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2582
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2583
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2584
|
+
scale on hover so the click-target reads as "press me"
|
|
2585
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2586
|
+
button to its own compositor layer for crisper edges
|
|
2587
|
+
during the scale tween. transition-transform duration-
|
|
2588
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2589
|
+
inline-block is default for <button> so no display
|
|
2590
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2591
|
+
pills (status / group / vendor / edge) at once. */
|
|
2592
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2378
2593
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2379
2594
|
>×</button>
|
|
2380
2595
|
</span>
|
|
@@ -2407,7 +2622,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2407
2622
|
data-filter-match-count={link.count}
|
|
2408
2623
|
data-filter-match-aliases={`${link.from},${link.to}`}
|
|
2409
2624
|
data-active-filter-edge-hot={isHot ? 'true' : 'false'}
|
|
2410
|
-
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"
|
|
2625
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2411
2626
|
title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
|
|
2412
2627
|
onClick={() => setPinnedEdgeKey(null)}
|
|
2413
2628
|
style={{
|
|
@@ -2451,7 +2666,21 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2451
2666
|
type="button"
|
|
2452
2667
|
aria-label={`Clear edge filter ${link.from} → ${link.to}`}
|
|
2453
2668
|
onClick={(e) => { e.stopPropagation(); setPinnedEdgeKey(null); }}
|
|
2454
|
-
|
|
2669
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2670
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2671
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2672
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2673
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2674
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2675
|
+
scale on hover so the click-target reads as "press me"
|
|
2676
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2677
|
+
button to its own compositor layer for crisper edges
|
|
2678
|
+
during the scale tween. transition-transform duration-
|
|
2679
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2680
|
+
inline-block is default for <button> so no display
|
|
2681
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2682
|
+
pills (status / group / vendor / edge) at once. */
|
|
2683
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2455
2684
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2456
2685
|
>×</button>
|
|
2457
2686
|
</span>
|
|
@@ -2716,9 +2945,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2716
2945
|
vendor letter chips ('A:N', 'O:N', '书:N',
|
|
2717
2946
|
'?:N'). They display vendor-distribution
|
|
2718
2947
|
data; same tier as the sibling data chips. */
|
|
2719
|
-
|
|
2948
|
+
/* Round 401 / Loop: vendor letter chip closes the
|
|
2949
|
+
hover-lift gesture family at its last unaddressed
|
|
2950
|
+
interactive HTML surface. R397/R398/R399 lifted
|
|
2951
|
+
filter pin pills + chip-row chips (working /
|
|
2952
|
+
online / active-links); R400 lifted standalone
|
|
2953
|
+
chrome buttons (reset / fullscreen). The vendor
|
|
2954
|
+
letter chips (A:N / O:N / 书:N / ?:N) are
|
|
2955
|
+
sibling interactive chips in the same chip-row
|
|
2956
|
+
— clickable to toggle the vendor filter pin —
|
|
2957
|
+
but were not yet on the hover-lift family.
|
|
2958
|
+
R401 closes the gap with hover:-translate-y-px
|
|
2959
|
+
+ transition-transform + transform-gpu added
|
|
2960
|
+
to the className. The inline transition list
|
|
2961
|
+
(box-shadow + background-color) keeps eaching
|
|
2962
|
+
independently — different property axes compose
|
|
2963
|
+
cleanly. Existing R354 glyph scale-1.1 (inner
|
|
2964
|
+
span) + R202 chip bg color-mix + R180 pin-mirror
|
|
2965
|
+
box-shadow + R354 glyph hover transform all
|
|
2966
|
+
preserved. data-vendor-letter-hover-lift attr
|
|
2967
|
+
surfaces the lift for tests. */
|
|
2968
|
+
className="tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu hover:-translate-y-px"
|
|
2720
2969
|
data-vendor-letter={v.initial}
|
|
2721
2970
|
data-vendor-letter-count={v.count}
|
|
2971
|
+
data-vendor-letter-hover-lift="true"
|
|
2722
2972
|
data-vendor-pinned={isPinned ? 'true' : 'false'}
|
|
2723
2973
|
data-vendor-hovered={hoveredVendor === v.initial ? 'true' : 'false'}
|
|
2724
2974
|
data-vendor-aliases={aliases.join(',')}
|
|
@@ -2762,7 +3012,64 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2762
3012
|
}
|
|
2763
3013
|
}}
|
|
2764
3014
|
>
|
|
2765
|
-
|
|
3015
|
+
{/* Round 354 / Loop: vendor letter glyph scales
|
|
3016
|
+
1.0 → 1.1 on hover. R88 already dims OTHER
|
|
3017
|
+
vendors on hover via canvas-wide opacity
|
|
3018
|
+
masking; R202 added a chip-level bg tint
|
|
3019
|
+
(color-mix 12 % alpha) so the chip itself
|
|
3020
|
+
responds. R354 closes the trio with a glyph-
|
|
3021
|
+
level lift: the focused vendor LETTER actively
|
|
3022
|
+
rises (transform scale) rather than the chip
|
|
3023
|
+
merely changing colour. Three layers of positive
|
|
3024
|
+
feedback on the hovered vendor + canvas-wide
|
|
3025
|
+
negative feedback on the others — a clean
|
|
3026
|
+
figure/ground separation.
|
|
3027
|
+
|
|
3028
|
+
display: inline-block is required for transform
|
|
3029
|
+
to apply (inline elements ignore transform).
|
|
3030
|
+
transformOrigin: 'center' so the glyph pivots
|
|
3031
|
+
around its centre instead of arcing from the
|
|
3032
|
+
baseline anchor. transition rides the existing
|
|
3033
|
+
Tailwind 4 transform/scale list (no new
|
|
3034
|
+
property — Tailwind already lists transform in
|
|
3035
|
+
the default transition-property set). 200ms
|
|
3036
|
+
matches the R202 chip bg-tint timing so the
|
|
3037
|
+
glyph lift and chip background ease in concert. */}
|
|
3038
|
+
{/* Round 369 / Loop: vendor letter glyph picks up
|
|
3039
|
+
fontWeight 600 (font-semibold). The glyph is the
|
|
3040
|
+
vendor identifier — the DATA the operator scans
|
|
3041
|
+
in this chip (A / O / 书 / C / G / ?). R333 set
|
|
3042
|
+
the count suffix `:N` to text-gray-400 + tabular-
|
|
3043
|
+
nums and (via parent inheritance) fw 500. Pre-
|
|
3044
|
+
R369 the LETTER also inherited fw 500 from the
|
|
3045
|
+
chip's font-medium — letter and count read at
|
|
3046
|
+
the same weight, contradicting the data-vs-label
|
|
3047
|
+
hierarchy the rest of the chip-row already speaks.
|
|
3048
|
+
R369 lifts the letter to fw 600 so the chip now
|
|
3049
|
+
reads as the same two-tier pattern R362 closed
|
|
3050
|
+
on the working / online / active-links chips:
|
|
3051
|
+
chip digit/letter fw 600 (data)
|
|
3052
|
+
chip unit/count fw 500 (label)
|
|
3053
|
+
Sibling treatment to R362 — extends the R333-R341
|
|
3054
|
+
chip-internal-hierarchy arc to the vendor-letter
|
|
3055
|
+
chip surface (9th surface family). R354 transform-
|
|
3056
|
+
scale-on-hover + R88 canvas-dim-others + R202
|
|
3057
|
+
chip bg color-mix all preserved on the same span.
|
|
3058
|
+
data-vendor-letter-glyph-font-weight attr exposes
|
|
3059
|
+
the value for tests. */}
|
|
3060
|
+
<span
|
|
3061
|
+
data-vendor-letter-glyph={v.initial}
|
|
3062
|
+
data-vendor-letter-glyph-hover={hoveredVendor === v.initial ? 'true' : 'false'}
|
|
3063
|
+
data-vendor-letter-glyph-font-weight="600"
|
|
3064
|
+
style={{
|
|
3065
|
+
color: v.color,
|
|
3066
|
+
display: 'inline-block',
|
|
3067
|
+
fontWeight: 600,
|
|
3068
|
+
transform: hoveredVendor === v.initial ? 'scale(1.1)' : 'scale(1)',
|
|
3069
|
+
transformOrigin: 'center',
|
|
3070
|
+
transition: 'transform 200ms ease-out',
|
|
3071
|
+
}}
|
|
3072
|
+
>{v.initial}</span>
|
|
2766
3073
|
{/* Round 333 / Loop: vendor count suffix `:{N}` joins
|
|
2767
3074
|
the R317 subordinate-text-lift family (gray-500 →
|
|
2768
3075
|
gray-400) plus picks up tabular-nums for digit
|
|
@@ -2864,11 +3171,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2864
3171
|
(third chip in the row — matches working + online
|
|
2865
3172
|
chip treatment so all three digits in the chip row
|
|
2866
3173
|
stay width-stable across counter crossings). */
|
|
2867
|
-
|
|
3174
|
+
/* Round 399 / Loop: active-links chip closes the 3-chip
|
|
3175
|
+
chip-row by extending R398's hover translateY(-1px)
|
|
3176
|
+
lift onto the third (rightmost) chip. The R398 family
|
|
3177
|
+
already covered working + online chips on the
|
|
3178
|
+
clickable variant; R399 adds the same gate (isInter-
|
|
3179
|
+
active = flowLinks.length > 0) so empty active-links
|
|
3180
|
+
stays planted at R206's opacity-50 receded paint.
|
|
3181
|
+
transition-transform + ease-out + transform-gpu join
|
|
3182
|
+
the inline transition list (different property axes
|
|
3183
|
+
compose cleanly: inline handles color/bg/border/
|
|
3184
|
+
opacity, className handles transform).
|
|
3185
|
+
Gesture-vocabulary table (post-R399 — now complete
|
|
3186
|
+
across the chip-row):
|
|
3187
|
+
working chip -1 px (R398)
|
|
3188
|
+
online chip -1 px (R398)
|
|
3189
|
+
active-links chip -1 px (R399, this round)
|
|
3190
|
+
filter pin pills -1 px (R397)
|
|
3191
|
+
recent-signal row -1 px (R143)
|
|
3192
|
+
legend row -1 px (R144)
|
|
3193
|
+
Every interactive chip in TopoGraph lifts on hover.
|
|
3194
|
+
data-chip-hover-lift attr exposes the lift surface
|
|
3195
|
+
state ('true' clickable, 'false' empty) for tests. */
|
|
3196
|
+
className={`tabular-nums font-medium hidden sm:inline px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu ${
|
|
2868
3197
|
isInteractive
|
|
2869
|
-
? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30'
|
|
3198
|
+
? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30 hover:-translate-y-px'
|
|
2870
3199
|
: 'bg-gray-500/10 text-gray-400 border-gray-500/20'
|
|
2871
3200
|
}`}
|
|
3201
|
+
data-chip-hover-lift={isInteractive ? 'true' : 'false'}
|
|
2872
3202
|
data-active-links-chip
|
|
2873
3203
|
data-active-links-flow-count={flowLinks.length}
|
|
2874
3204
|
data-active-links-clickable={isInteractive ? 'true' : 'false'}
|
|
@@ -2896,7 +3226,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2896
3226
|
the 5th chip surface in the R333/R335/R336/R337
|
|
2897
3227
|
chip-internal-hierarchy arc. data-active-links-
|
|
2898
3228
|
chip-unit exposes the unit span for tests. */}
|
|
2899
|
-
{
|
|
3229
|
+
{/* R362 sibling — active-links chip digit gains font-semibold. */}
|
|
3230
|
+
<span className="font-semibold" data-active-links-chip-digit>{flowLinks.length}</span><span className="opacity-70" data-active-links-chip-unit> active link{flowLinks.length === 1 ? '' : 's'}</span>
|
|
2900
3231
|
{rel ? (() => {
|
|
2901
3232
|
// Round 161 / Loop: extend R160's recency-pip
|
|
2902
3233
|
// vocabulary up one scope — from per-flow row to
|
|
@@ -2921,8 +3252,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2921
3252
|
const alpha = ageSec <= 30
|
|
2922
3253
|
? 1
|
|
2923
3254
|
: ageSec <= 300
|
|
2924
|
-
? 1 - ((ageSec - 30) / 270) * 0.
|
|
2925
|
-
: 0.25
|
|
3255
|
+
? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
|
|
3256
|
+
: 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
|
|
2926
3257
|
// Cyan dark / teal light to match palette legendAccent.
|
|
2927
3258
|
const dotColor = isLight
|
|
2928
3259
|
? `rgba(13, 148, 136, ${alpha.toFixed(2)})`
|
|
@@ -2939,7 +3270,28 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2939
3270
|
// color: dotColor — the lift only affects the trailing
|
|
2940
3271
|
// literal "last {rel}" text.
|
|
2941
3272
|
return (
|
|
2942
|
-
|
|
3273
|
+
// Round 357 / Loop: active-links chip freshness
|
|
3274
|
+
// suffix wrapper picks up `tabular-nums` for digit
|
|
3275
|
+
// width-lock. Pre-R357 the literal "last {rel}"
|
|
3276
|
+
// text (e.g. "last 5s ago", "last 10s ago",
|
|
3277
|
+
// "last 1m ago") had natural-figure digits — the
|
|
3278
|
+
// freshness ticker updates every second, so the
|
|
3279
|
+
// 9→10 boundary on the seconds counter and the
|
|
3280
|
+
// 59→60s → 1m flip both jittered ~1-2 px of glyph
|
|
3281
|
+
// width which propagated through the chip-row's
|
|
3282
|
+
// inline-flex layout, nudging the freshness DOT
|
|
3283
|
+
// and the chip's left edge. Tabular-nums on the
|
|
3284
|
+
// wrapper applies to all descendant digits only
|
|
3285
|
+
// (letters render at natural widths) so the
|
|
3286
|
+
// ticker stays planted across every count cross.
|
|
3287
|
+
// Joins the R224-R232 info-density tabular-nums
|
|
3288
|
+
// sweep at the chip-row freshness scope. Pure
|
|
3289
|
+
// paint-level change, no geometry shift on rest.
|
|
3290
|
+
// The R342 text-gray-400 lift + R161 dot freshness
|
|
3291
|
+
// alpha ramp + R317 subordinate-text-lift family
|
|
3292
|
+
// all preserved. data-active-links-freshness-
|
|
3293
|
+
// wrapper attr exposes the wrapper for tests.
|
|
3294
|
+
<span className="text-gray-400 tabular-nums" data-active-links-freshness-wrapper>
|
|
2943
3295
|
<span
|
|
2944
3296
|
data-active-links-freshness-dot
|
|
2945
3297
|
data-active-links-freshness-alpha={alpha.toFixed(2)}
|
|
@@ -3603,6 +3955,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3603
3955
|
active surfaces the activity state for test probes
|
|
3604
3956
|
(active spokes don't carry the bucket/dur attrs so
|
|
3605
3957
|
they need their own data anchor). */
|
|
3958
|
+
// Round 382 / Loop: hub-spoke path picks up
|
|
3959
|
+
// strokeLinecap='round'. Sibling polish to R378 flow-
|
|
3960
|
+
// rail dashes + R380 group box dashes — three dashed-
|
|
3961
|
+
// stroke surfaces now share 'round' linecap:
|
|
3962
|
+
// R378 flow-rail '2 12' -> soft 3-px pills
|
|
3963
|
+
// R380 group box '6 6' -> soft 7.5-px pills
|
|
3964
|
+
// R382 hub spoke '6 14' -> soft 7-px pills (this round)
|
|
3965
|
+
// For idle spokes (dashed at sw=1), each 6-px dash gains
|
|
3966
|
+
// 0.5-px round caps and reads as a soft pill instead of
|
|
3967
|
+
// a sharp 6 x 1 rectangle. Active spokes (solid, no
|
|
3968
|
+
// dasharray) have caps mostly hidden by the hub center +
|
|
3969
|
+
// node radius. Geometry-safe; paint-only. R51 sentinel
|
|
3970
|
+
// strokeWidth 1.5/3 untouched (idle=1, active=2). data-
|
|
3971
|
+
// topo-hub-spoke-linecap attr exposes the value for tests.
|
|
3972
|
+
// Round 391 / Loop: hub-spoke active opacity 0.7 → 0.8.
|
|
3973
|
+
// Pre-R391 active spokes (the spoke connecting the hub
|
|
3974
|
+
// to the currently-active alias — hovered or pinned)
|
|
3975
|
+
// lifted opacity from rest 0.45 to active 0.7 — a clear
|
|
3976
|
+
// step but slightly understated against the canvas
|
|
3977
|
+
// chrome. R391 lifts active to 0.8 so the "this spoke
|
|
3978
|
+
// connects to your active node" signal reads with
|
|
3979
|
+
// matching weight to the R370 hub hover-ring opacity
|
|
3980
|
+
// (0.7 → 0.8 cyber) — paired canvas signals now share
|
|
3981
|
+
// the same active-state alpha (0.8) so when a user
|
|
3982
|
+
// hovers a node, both the spoke and the hub-ring lift
|
|
3983
|
+
// to identical opacity. Rest 0.45 invariant preserved.
|
|
3984
|
+
// Theme-consistency / canvas-presence polish family
|
|
3985
|
+
// (6th anchor):
|
|
3986
|
+
// R370 hub hover-ring opacity 0.7 → 0.8 cyber
|
|
3987
|
+
// R371 edge-badge rest opacity 0.82 → 0.85 cyber
|
|
3988
|
+
// R372 minimap offline-dot opacity 0.5 → 0.6
|
|
3989
|
+
// R386 hub-highlight idle opacity 0.9 → 0.95
|
|
3990
|
+
// R387 hover-detail panel opacity 0.94 → 0.97 cyber
|
|
3991
|
+
// R391 hub-spoke active opacity 0.7 → 0.8 (this round)
|
|
3992
|
+
// Idle path (45% alpha + dashed flow animation) entirely
|
|
3993
|
+
// untouched — R391 is an active-state-only lift.
|
|
3994
|
+
// data-topo-hub-spoke-opacity attr exposes the resolved
|
|
3995
|
+
// value for tests. R382 strokeLinecap='round' + R51
|
|
3996
|
+
// sentinel-safe sw (1 idle / 2 active) preserved.
|
|
3606
3997
|
return (
|
|
3607
3998
|
<path
|
|
3608
3999
|
key={`hub-${session.alias}`}
|
|
@@ -3611,11 +4002,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3611
4002
|
stroke={isActiveSpoke ? pal.spokeStroke.active : pal.spokeStroke.idle}
|
|
3612
4003
|
strokeWidth={isActiveSpoke ? 2 : 1}
|
|
3613
4004
|
strokeDasharray={isActiveSpoke ? 'none' : '6 14'}
|
|
3614
|
-
|
|
4005
|
+
strokeLinecap="round"
|
|
4006
|
+
opacity={isActiveSpoke ? 0.8 : 0.45}
|
|
3615
4007
|
className={isActiveSpoke ? undefined : 'anet-topo-spoke-flow'}
|
|
3616
4008
|
data-topo-spoke-bucket={isActiveSpoke ? undefined : busy}
|
|
3617
4009
|
data-topo-spoke-dur={isActiveSpoke ? undefined : spokeDur}
|
|
3618
4010
|
data-topo-hub-spoke-active={isActiveSpoke ? 'true' : 'false'}
|
|
4011
|
+
data-topo-hub-spoke-opacity={isActiveSpoke ? 0.8 : 0.45}
|
|
4012
|
+
data-topo-hub-spoke-linecap="round"
|
|
3619
4013
|
style={{
|
|
3620
4014
|
transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out',
|
|
3621
4015
|
...(isActiveSpoke ? {} : {
|
|
@@ -3707,7 +4101,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3707
4101
|
stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}
|
|
3708
4102
|
strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}
|
|
3709
4103
|
strokeDasharray={(isPinned || isHovered) ? 'none' : '6 6'}
|
|
4104
|
+
/* Round 380 / Loop: cluster box stroke gets round
|
|
4105
|
+
linecap + round linejoin. Sibling SVG stroke-
|
|
4106
|
+
softening polish to R378 flow-rail linecap + R379
|
|
4107
|
+
minimap viewport linejoin — extends the family to
|
|
4108
|
+
the group cluster boundary box (grid layout only):
|
|
4109
|
+
R288 chrome icons strokeLinecap='round'
|
|
4110
|
+
R378 flow-rail dashes strokeLinecap='round'
|
|
4111
|
+
R380 group box dashes strokeLinecap='round' (this round)
|
|
4112
|
+
R379 viewport rect strokeLinejoin='round'
|
|
4113
|
+
R380 group box corners strokeLinejoin='round' (this round)
|
|
4114
|
+
Linecap rounds the R85 '6 6' marching-ants dash
|
|
4115
|
+
pills at rest — each 6 px dash gains a ~0.75 px
|
|
4116
|
+
round cap (sw=1.5 idle), reading as soft pills
|
|
4117
|
+
instead of sharp 6 × 1.5 px rectangles. Linejoin
|
|
4118
|
+
rounds the 4 sharp 90° corners (any state — solid
|
|
4119
|
+
or dashed); at sw=1.5 the join arc is ~0.75 px,
|
|
4120
|
+
matching R379 viewport vocabulary. Geometry-safe:
|
|
4121
|
+
stroke-* properties only affect paint, not bbox.
|
|
4122
|
+
The R51 sentinel 1.5/3 strokeWidth values stay
|
|
4123
|
+
intact (the overlap probe is gated to g[data-
|
|
4124
|
+
node], so this cluster-internal rect is invisible
|
|
4125
|
+
to it anyway). data-group-box-linecap + -linejoin
|
|
4126
|
+
attrs expose the values for tests. */
|
|
4127
|
+
strokeLinecap="round"
|
|
4128
|
+
strokeLinejoin="round"
|
|
3710
4129
|
data-group-box-pinned={isPinned ? 'true' : 'false'}
|
|
4130
|
+
data-group-box-linecap="round"
|
|
4131
|
+
data-group-box-linejoin="round"
|
|
3711
4132
|
// R85: ambient "marching ants" drift on the perimeter
|
|
3712
4133
|
// when this group has at least one working member, and
|
|
3713
4134
|
// neither pin nor hover is active (those treatments
|
|
@@ -3907,12 +4328,33 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3907
4328
|
lives at a fixed dx=6 offset from the name, so a
|
|
3908
4329
|
digit-width jitter at 9→10 used to shift the
|
|
3909
4330
|
whole count visibly. Tabular locks it. */}
|
|
4331
|
+
{/* Round 366 / Loop: group label member-count tspan
|
|
4332
|
+
fontWeight 400 → 500. Sibling polish to R363
|
|
4333
|
+
recent-row alias text fw 400 → 500 + R364 legend-
|
|
4334
|
+
row label fw 400 → 500 — closes the per-row 'count
|
|
4335
|
+
is fw 500 against label-tier fw 700' pattern at
|
|
4336
|
+
the group-label scope (grid layout cluster mark).
|
|
4337
|
+
Hierarchy snapshot post-R366 across all 3 row
|
|
4338
|
+
surfaces:
|
|
4339
|
+
recent count(hot/cold) fw 700/600 (R320)
|
|
4340
|
+
recent alias fw 500 (R363)
|
|
4341
|
+
legend count fw 600 (R309)
|
|
4342
|
+
legend label fw 500 (R364)
|
|
4343
|
+
group name fw 700 (legacy)
|
|
4344
|
+
group count fw 500 (R366, this round)
|
|
4345
|
+
Monospace family + R225 tabular-nums lock digit
|
|
4346
|
+
width, so the fw bump is paint-only — bbox
|
|
4347
|
+
unchanged + overlap-test invariants hold. R229
|
|
4348
|
+
fill-inherit from parent label (hover-deepen-own-
|
|
4349
|
+
hue family) preserved. data-group-label-count-
|
|
4350
|
+
font-weight attr exposes the value for tests. */}
|
|
3910
4351
|
<tspan
|
|
3911
4352
|
dx="6"
|
|
3912
4353
|
fontSize="11"
|
|
3913
|
-
fontWeight="
|
|
4354
|
+
fontWeight="500"
|
|
3914
4355
|
data-group-label-count={box.key}
|
|
3915
4356
|
data-group-label-count-value={box.count}
|
|
4357
|
+
data-group-label-count-font-weight="500"
|
|
3916
4358
|
style={{ fontVariantNumeric: 'tabular-nums' }}
|
|
3917
4359
|
>· {box.count}</tspan>
|
|
3918
4360
|
{/* Round 58 / Loop: status mix pip strip. Compact text-
|
|
@@ -4043,13 +4485,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4043
4485
|
// synchronised per-edge animation set.
|
|
4044
4486
|
const stagger = (index * 0.37) % duration;
|
|
4045
4487
|
// Round 10 / Loop: freshness fade. An edge that fired ≤30s ago
|
|
4046
|
-
// stays at full intensity; over 5 minutes it decays to
|
|
4047
|
-
// Surfaces "what's happening now" vs background
|
|
4048
|
-
// hiding old flow entirely (some context
|
|
4049
|
-
// captured at useMemo-recompute time
|
|
4050
|
-
// — accuracy is within the poll
|
|
4488
|
+
// stays at full intensity; over 5 minutes it decays to a
|
|
4489
|
+
// floor. Surfaces "what's happening now" vs background
|
|
4490
|
+
// chatter without hiding old flow entirely (some context
|
|
4491
|
+
// still useful). `now` captured at useMemo-recompute time
|
|
4492
|
+
// (every 5s message refresh) — accuracy is within the poll
|
|
4493
|
+
// interval, plenty.
|
|
4494
|
+
//
|
|
4495
|
+
// Round 406 / Loop: edge freshness fade floor 0.35 → 0.40.
|
|
4496
|
+
// Stale-state legibility lift family (6th anchor) — pre-
|
|
4497
|
+
// R406 edges older than 5 minutes faded to α=0.35 (a 65 %
|
|
4498
|
+
// dim against full intensity). The decay rate is the same
|
|
4499
|
+
// 1 - ageMs/300s curve; only the FLOOR shifts. Sibling
|
|
4500
|
+
// treatment to:
|
|
4501
|
+
// R317 subordinate-text gray-500 → gray-400
|
|
4502
|
+
// R358 freshness ramp floor 0.25 → 0.30
|
|
4503
|
+
// R372 minimap offline-dot opacity 0.5 → 0.6
|
|
4504
|
+
// R404 hub-halo cyber trough 0.08 → 0.10
|
|
4505
|
+
// R405 hub-halo light trough 0.32 → 0.34
|
|
4506
|
+
// R406 edge freshness floor 0.35 → 0.40 (this round)
|
|
4507
|
+
// Edges past 5min now sit at 40% intensity instead of 35%
|
|
4508
|
+
// — they still recede against fresh edges but read
|
|
4509
|
+
// legibly enough to convey "this conversation existed".
|
|
4510
|
+
// ageMs threshold for the 5-minute decay unchanged; the
|
|
4511
|
+
// decay curve shape (linear) unchanged. The visual delta
|
|
4512
|
+
// is most pronounced on edges between 5-60 minutes old —
|
|
4513
|
+
// where the floor was binding pre-R406.
|
|
4051
4514
|
const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;
|
|
4052
|
-
const fresh = Math.max(0.
|
|
4515
|
+
const fresh = Math.max(0.40, 1 - ageMs / (5 * 60 * 1000));
|
|
4053
4516
|
// Round 16 arrow-tier binning — keep `topo-arrow` as the
|
|
4054
4517
|
// medium tier id so the legend swatch picks it up unchanged.
|
|
4055
4518
|
const arrowId = link.count <= 2 ? 'topo-arrow-s'
|
|
@@ -4201,20 +4664,56 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4201
4664
|
online chips to splice in additional properties
|
|
4202
4665
|
beside Tailwind's). data-edge-flow-rail attr
|
|
4203
4666
|
surfaces the path for test introspection. */}
|
|
4667
|
+
{/* Round 381 / Loop: edge visible flow path picks up
|
|
4668
|
+
strokeLinecap='round'. Sibling polish to R378
|
|
4669
|
+
flow-rail dashed linecap — both flow-element paths
|
|
4670
|
+
(visible primary + dashed secondary rail) now share
|
|
4671
|
+
'round' linecap vocabulary. The visible path runs
|
|
4672
|
+
source-node → dest-node as one continuous line, so
|
|
4673
|
+
the dest-end is covered by the markerEnd arrow and
|
|
4674
|
+
the source-end usually sits inside the source-node
|
|
4675
|
+
circle. At certain alignments (post-zoom, post-
|
|
4676
|
+
layout-switch transitions), the source-end may peek
|
|
4677
|
+
out by a fraction of a px past the node edge —
|
|
4678
|
+
round caps render that overshoot as a smooth half-
|
|
4679
|
+
disc instead of a sharp rectangle. Pure paint
|
|
4680
|
+
refinement, geometry-safe (bbox of the stroke
|
|
4681
|
+
unchanged at the join with the arrow marker).
|
|
4682
|
+
data-edge-visible-linecap attr exposes the value
|
|
4683
|
+
for tests. */}
|
|
4204
4684
|
<path
|
|
4205
4685
|
d={path}
|
|
4206
4686
|
fill="none"
|
|
4207
4687
|
stroke={pal.flowEdge}
|
|
4208
4688
|
strokeWidth={renderWidth}
|
|
4689
|
+
strokeLinecap="round"
|
|
4209
4690
|
opacity={Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)}
|
|
4210
4691
|
filter={isLight ? undefined : 'url(#topo-glow)'}
|
|
4211
4692
|
markerEnd={`url(#${arrowId})`}
|
|
4212
4693
|
data-edge-visible={link.key}
|
|
4694
|
+
data-edge-visible-linecap="round"
|
|
4213
4695
|
style={{
|
|
4214
4696
|
pointerEvents: 'none',
|
|
4215
4697
|
transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out',
|
|
4216
4698
|
}}
|
|
4217
4699
|
/>
|
|
4700
|
+
{/* Round 378 / Loop: edge flow-path dashed-rail picks
|
|
4701
|
+
up strokeLinecap='round'. Pre-R378 the rail
|
|
4702
|
+
rendered '2 12' dashes as sharp 1×2 rectangles
|
|
4703
|
+
against the canvas backdrop; default 'butt' caps
|
|
4704
|
+
leave dash ends square. R378 rounds each cap so
|
|
4705
|
+
the dashes read as soft 3-px pills (1 px stroke +
|
|
4706
|
+
0.5 px round cap each end). The flow-rail is the
|
|
4707
|
+
secondary 'invisible-spine' line that gives the
|
|
4708
|
+
R57 spoke flow a directional rail to slide along
|
|
4709
|
+
— rounding the dashes softens its presence
|
|
4710
|
+
against the primary visible flow path (R245 has
|
|
4711
|
+
no strokeLinecap so it inherits 'butt' on a
|
|
4712
|
+
continuous line, irrelevant). Geometry-safe:
|
|
4713
|
+
round caps only widen the visible dash; the
|
|
4714
|
+
bbox of the path is unchanged so overlap-test
|
|
4715
|
+
invariants hold. data-edge-flow-rail-linecap
|
|
4716
|
+
attr exposes the value for tests. */}
|
|
4218
4717
|
<path
|
|
4219
4718
|
id={`flow-path-${index}`}
|
|
4220
4719
|
d={path}
|
|
@@ -4222,8 +4721,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4222
4721
|
stroke={pal.flowPath}
|
|
4223
4722
|
strokeWidth="1"
|
|
4224
4723
|
strokeDasharray="2 12"
|
|
4724
|
+
strokeLinecap="round"
|
|
4225
4725
|
opacity={Math.min(1, (isLight ? 0.4 : 0.75) * fresh * edgeOpacityMul)}
|
|
4226
4726
|
data-edge-flow-rail={link.key}
|
|
4727
|
+
data-edge-flow-rail-linecap="round"
|
|
4227
4728
|
style={{ transition: 'opacity 300ms ease-out, stroke 300ms ease-out' }}
|
|
4228
4729
|
/>
|
|
4229
4730
|
{!reducedMotion && (
|
|
@@ -4588,14 +5089,137 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4588
5089
|
R251 closes the per-edge surface theme-toggle
|
|
4589
5090
|
smoothness — every theme-driven property on
|
|
4590
5091
|
every edge element now eases under cyber↔light. */}
|
|
5092
|
+
{/* Round 367 / Loop: edge midpoint badge rest
|
|
5093
|
+
stroke-width 1 → 1.25. Sibling visual-weight
|
|
5094
|
+
bump family (7th canvas anchor now):
|
|
5095
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
5096
|
+
R295 legend swatch base radius 5.5 → 6
|
|
5097
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
5098
|
+
R360 hub digit fontSize 11 → 12
|
|
5099
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
5100
|
+
R365 hub-highlight base radius 5 → 5.5
|
|
5101
|
+
R367 edge-badge rest stroke 1 → 1.25 (this round)
|
|
5102
|
+
Cold edge badges gain ~25 % stroke presence
|
|
5103
|
+
(1.25/1.0 = 1.25). Stays clear of the R51
|
|
5104
|
+
overlap-test sentinel values (1.5 / 3 reserved
|
|
5105
|
+
for node strokes — the test selector is gated
|
|
5106
|
+
to g[data-node] ancestors so this edge-internal
|
|
5107
|
+
circle is invisible to that probe anyway, but
|
|
5108
|
+
1.25 is a safe non-sentinel value regardless).
|
|
5109
|
+
R188 transition list 'stroke-width 300ms ease-
|
|
5110
|
+
out' still smoothes the hot/pin flip — now
|
|
5111
|
+
1.25 → 2 instead of 1 → 2, same ease pace.
|
|
5112
|
+
data-edge-badge-stroke-width-rest exposes the
|
|
5113
|
+
new baseline for tests. */}
|
|
5114
|
+
{/* Round 371 / Loop: edge-badge cyber opacity 0.82
|
|
5115
|
+
→ 0.85. Sibling theme-consistency polish to R370
|
|
5116
|
+
hub hover-ring 0.7 → 0.8. R251 designed this
|
|
5117
|
+
badge with opacity 0.82 (cyber) / 0.95 (light)
|
|
5118
|
+
— 13 % delta. Cyber-theme dark bg needs more
|
|
5119
|
+
alpha to read as 'present'; R371 narrows the
|
|
5120
|
+
gap to 10 %, bringing the badge closer to light
|
|
5121
|
+
theme's 0.95 floor. Light stays at 0.95
|
|
5122
|
+
(already in the legibility band). data-edge-
|
|
5123
|
+
badge-opacity attr exposes the resolved value.
|
|
5124
|
+
Theme-consistency polish family:
|
|
5125
|
+
R246/R247 panel transition family
|
|
5126
|
+
R251 edge badge fill/opacity baseline
|
|
5127
|
+
R370 hub hover-ring cyber 0.7 → 0.8
|
|
5128
|
+
R371 edge badge cyber 0.82 → 0.85 (this round)
|
|
5129
|
+
R164 r=9/10.5 hover-lift + R188/R251 transition
|
|
5130
|
+
list + R367 strokeWidth=1.25 cold rest preserved. */}
|
|
5131
|
+
{/* Round 394 / Loop: edge-badge gains a hover
|
|
5132
|
+
strokeWidth tier (1.5) between cold rest
|
|
5133
|
+
(R367 1.25) and pin/hot (2). Pre-R394 the
|
|
5134
|
+
badge lifted only its radius on hover (R164
|
|
5135
|
+
9 → 10.5); the stroke stayed at cold rest
|
|
5136
|
+
1.25 unless pin/hot kicked in, so a plain
|
|
5137
|
+
hover felt half-lifted — geometry expanded
|
|
5138
|
+
while the contour stayed thin. R394 adds
|
|
5139
|
+
strokeWidth=1.5 on isHoveredEdge so hover
|
|
5140
|
+
now lifts both r AND stroke in concert —
|
|
5141
|
+
same pattern R385 used for the hub hover-
|
|
5142
|
+
ring (1.5 → 1.75) where the ring's three
|
|
5143
|
+
hover axes (r grow / opacity fade-in /
|
|
5144
|
+
stroke thicken) all rise together.
|
|
5145
|
+
Three-tier stroke hierarchy now:
|
|
5146
|
+
cold rest 1.25 (R367)
|
|
5147
|
+
hovered 1.5 (R394 — this round)
|
|
5148
|
+
pinned / hot 2.0 (R188)
|
|
5149
|
+
R51 sentinel concern: strokeWidth=1.5 is
|
|
5150
|
+
one of the two sentinels reserved for node
|
|
5151
|
+
detection, but the R51 selector is gated
|
|
5152
|
+
to `g[data-node]` ancestors so this edge-
|
|
5153
|
+
internal circle is invisible to the probe
|
|
5154
|
+
(same lesson R177 hub hover-ring + R367
|
|
5155
|
+
cold rest documented). 300ms strokeWidth
|
|
5156
|
+
transition already in the style list eases
|
|
5157
|
+
the new tier naturally. data-edge-badge-
|
|
5158
|
+
stroke-width-hover attr exposes the hover
|
|
5159
|
+
value for tests. */}
|
|
5160
|
+
{/* Round 395 / Loop: edge-badge gains a third
|
|
5161
|
+
hover axis — opacity 0.85 (cyber) / 0.95
|
|
5162
|
+
(light) → 1.0 on isHoveredEdge. Pre-R395
|
|
5163
|
+
hovering thickened the stroke (R394 1.25 →
|
|
5164
|
+
1.5) and grew the radius (R164 9 → 10.5)
|
|
5165
|
+
but the badge's translucency stayed put at
|
|
5166
|
+
R371's rest alpha (cyber 0.85 / light 0.95).
|
|
5167
|
+
R395 lifts hover to a clean 1.0 — fully
|
|
5168
|
+
opaque — so the hovered badge reads as
|
|
5169
|
+
"in focus" against the dim siblings.
|
|
5170
|
+
Three-axis hover-lift parity now complete:
|
|
5171
|
+
hub hover-ring (R177/R370/R385):
|
|
5172
|
+
r 14 → 17, opacity 0 → 0.8 cyber, sw 1.5 → 1.75
|
|
5173
|
+
edge badge (R164/R394/R395):
|
|
5174
|
+
r 9 → 10.5, sw 1.25 → 1.5, opacity → 1.0
|
|
5175
|
+
200ms opacity transition (already in the
|
|
5176
|
+
style list) eases the new axis naturally.
|
|
5177
|
+
R371 rest opacity (0.85 cyber / 0.95 light)
|
|
5178
|
+
preserved as the resting alpha — R395
|
|
5179
|
+
adds an isHoveredEdge override on top.
|
|
5180
|
+
data-edge-badge-opacity-hover attr exposes
|
|
5181
|
+
the hover value for tests. */}
|
|
5182
|
+
{/* Round 396 / Loop: extend the R395 opacity → 1.0
|
|
5183
|
+
lift to the pinned state. Pre-R396 the badge
|
|
5184
|
+
shared `r=10.5` on both hover AND pin (R164
|
|
5185
|
+
unified-lift) but R395's opacity lift fired
|
|
5186
|
+
ONLY on isHoveredEdge — pinned badges stayed
|
|
5187
|
+
at R371 rest alpha (cyber 0.85 / light 0.95).
|
|
5188
|
+
That left pin (sticky selection) reading
|
|
5189
|
+
softer than hover (transient preview), even
|
|
5190
|
+
though pin is the stronger commitment.
|
|
5191
|
+
R396 unifies hover + pin at opacity=1.0
|
|
5192
|
+
so the same data-edge-badge-lifted='true'
|
|
5193
|
+
surface uniformly carries full alpha. Pin
|
|
5194
|
+
stroke (R188 sw=2 + pal.legendHeadline color)
|
|
5195
|
+
continues to differentiate pin from hover —
|
|
5196
|
+
the opacity track now closes the lift parity.
|
|
5197
|
+
The new gate (isHoveredEdge || isPinned)
|
|
5198
|
+
mirrors the existing R164 r-lift gate, so
|
|
5199
|
+
the badge has a single "active state"
|
|
5200
|
+
signature across r + opacity.
|
|
5201
|
+
200ms opacity transition (already in style
|
|
5202
|
+
list) eases pin/unpin naturally. R371 rest
|
|
5203
|
+
opacity preserved as the resting alpha.
|
|
5204
|
+
data-edge-badge-opacity-hover renamed
|
|
5205
|
+
semantically to -active (covers hover+pin)
|
|
5206
|
+
via the new -opacity-active attr; the
|
|
5207
|
+
legacy -opacity-hover attr kept for R395
|
|
5208
|
+
test compatibility. */}
|
|
4591
5209
|
<circle
|
|
4592
5210
|
cx={badgeX} cy={badgeY}
|
|
4593
5211
|
r={isHoveredEdge || isPinned ? 10.5 : 9}
|
|
4594
5212
|
fill={pal.legendBox.fill}
|
|
4595
5213
|
stroke={isPinned ? pal.legendHeadline : isHot ? hotStroke : pal.flowEdge}
|
|
4596
|
-
strokeWidth={isPinned ? 2 : isHot ? 2 : 1}
|
|
4597
|
-
opacity={isLight ? 0.95 : 0.
|
|
5214
|
+
strokeWidth={isPinned ? 2 : isHot ? 2 : isHoveredEdge ? 1.5 : 1.25}
|
|
5215
|
+
opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}
|
|
4598
5216
|
data-edge-badge-lifted={(isHoveredEdge || isPinned) ? 'true' : 'false'}
|
|
5217
|
+
data-edge-badge-stroke-width-rest="1.25"
|
|
5218
|
+
data-edge-badge-stroke-width-hover="1.5"
|
|
5219
|
+
data-edge-badge-opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}
|
|
5220
|
+
data-edge-badge-opacity-rest={isLight ? 0.95 : 0.85}
|
|
5221
|
+
data-edge-badge-opacity-hover="1"
|
|
5222
|
+
data-edge-badge-opacity-active="1"
|
|
4599
5223
|
style={{ transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out' }}
|
|
4600
5224
|
/>
|
|
4601
5225
|
{/* Round 224 / Loop: edge badge text gains the 4th
|
|
@@ -4634,11 +5258,39 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4634
5258
|
x={badgeX} y={badgeY + 3}
|
|
4635
5259
|
textAnchor="middle"
|
|
4636
5260
|
fill={pal.legendHeadline}
|
|
4637
|
-
|
|
5261
|
+
/* Round 361 / Loop: edge midpoint badge text
|
|
5262
|
+
fontSize 10 → 11. Sibling visual-weight bump
|
|
5263
|
+
to R360 hub digit 11 → 12. The badge digit
|
|
5264
|
+
is the per-edge equivalent of the hub digit
|
|
5265
|
+
— a high-information scalar (link.count) at
|
|
5266
|
+
a stable canvas position. Pre-R361 fontSize=
|
|
5267
|
+
10 + R220 letter-spacing 0.4 + R224 tabular-
|
|
5268
|
+
nums made the digit READABLE but small
|
|
5269
|
+
against the r=9 / 18-px badge envelope;
|
|
5270
|
+
fontSize=11 nudges the glyph ~10 % bigger
|
|
5271
|
+
(bbox ~7×10 px from ~6×9 px) so the count
|
|
5272
|
+
reads more cleanly at glance — still well
|
|
5273
|
+
inside the r=9 idle circle and the r=10.5
|
|
5274
|
+
hover/pin lift (R164). y=badgeY+3 empirical
|
|
5275
|
+
vertical centring kept (1px drift at the
|
|
5276
|
+
bumped size is below the noise floor in
|
|
5277
|
+
the on-curve flow path).
|
|
5278
|
+
Visual-weight bump family:
|
|
5279
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
5280
|
+
R295 legend swatch base radius 5.5 → 6
|
|
5281
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
5282
|
+
R360 hub digit fontSize 11 → 12
|
|
5283
|
+
R361 edge-badge digit fontSize 10 → 11 (this round)
|
|
5284
|
+
data-edge-badge-text-font-size attr exposes
|
|
5285
|
+
the value for tests. R220 pin/hot letter-
|
|
5286
|
+
spacing tween + R224 tabular-nums + R188
|
|
5287
|
+
stroke-width pin/hot transitions all preserved. */
|
|
5288
|
+
fontSize="11"
|
|
4638
5289
|
fontFamily="monospace"
|
|
4639
5290
|
fontWeight="700"
|
|
4640
5291
|
data-edge-badge-text={link.key}
|
|
4641
5292
|
data-edge-badge-text-pin={(isPinned || isHot) ? 'true' : 'false'}
|
|
5293
|
+
data-edge-badge-text-font-size="11"
|
|
4642
5294
|
style={{
|
|
4643
5295
|
pointerEvents: 'none',
|
|
4644
5296
|
fontVariantNumeric: 'tabular-nums',
|
|
@@ -4754,19 +5406,68 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4754
5406
|
: workingCount <= 2 ? 1
|
|
4755
5407
|
: workingCount <= 5 ? 2
|
|
4756
5408
|
: 3;
|
|
5409
|
+
// Round 404 / Loop: hub-halo cyber trough opacity 0.08 →
|
|
5410
|
+
// 0.10. Pre-R404 the breath's low-point sat at α=0.08
|
|
5411
|
+
// cyber (per R84 family tuning) — the halo nearly faded
|
|
5412
|
+
// out at trough on the dark canvas. R404 lifts cyber
|
|
5413
|
+
// trough to 0.10. Per-bucket peak amplitudes [0.16/0.20/
|
|
5414
|
+
// 0.26/0.32] stay exactly tuned.
|
|
5415
|
+
//
|
|
5416
|
+
// Round 405 / Loop: hub-halo LIGHT trough 0.32 → 0.34 —
|
|
5417
|
+
// symmetric +0.02 lift to mirror R404's cyber treatment
|
|
5418
|
+
// across both themes. Pre-R405 only cyber got the lift
|
|
5419
|
+
// (R404 docstring noted "light already at the strong
|
|
5420
|
+
// end" as deliberate); but the cyber/light delta in
|
|
5421
|
+
// R404 was an inconsistency in the family pattern.
|
|
5422
|
+
// R405 closes the symmetry — both themes get +0.02
|
|
5423
|
+
// baseline lift, so the breath low-point reads with
|
|
5424
|
+
// matching confidence regardless of theme. Light peak
|
|
5425
|
+
// array [0.52/0.58/0.65/0.72] stays tuned.
|
|
5426
|
+
//
|
|
5427
|
+
// Stale-state legibility lift family (5 anchors now):
|
|
5428
|
+
// R317 subordinate-text gray-500 → gray-400
|
|
5429
|
+
// R358 freshness floor 0.25 → 0.30
|
|
5430
|
+
// R372 minimap offline-dot opacity 0.5 → 0.6
|
|
5431
|
+
// R404 hub-halo cyber trough 0.08 → 0.10
|
|
5432
|
+
// R405 hub-halo light trough 0.32 → 0.34 (this round)
|
|
5433
|
+
//
|
|
5434
|
+
// R84 per-bucket peak/dur + R245 ease-in-out spline
|
|
5435
|
+
// keySplines all preserved. Test fixture probes the
|
|
5436
|
+
// SMIL <animate> values via data-topo-hub-halo-trough
|
|
5437
|
+
// attr (now exposes both light + cyber resolved values).
|
|
4757
5438
|
const peakLight = [0.52, 0.58, 0.65, 0.72][busy];
|
|
4758
5439
|
const peakDark = [0.16, 0.20, 0.26, 0.32][busy];
|
|
4759
|
-
const troughLight = 0.
|
|
4760
|
-
const troughDark = 0.
|
|
5440
|
+
const troughLight = 0.34;
|
|
5441
|
+
const troughDark = 0.10;
|
|
4761
5442
|
const dur = [4.0, 3.2, 2.7, 2.4][busy];
|
|
4762
5443
|
const valuesLight = `${troughLight};${peakLight};${troughLight}`;
|
|
4763
5444
|
const valuesDark = `${troughDark};${peakDark};${troughDark}`;
|
|
5445
|
+
// Round 408 / Loop: hub-halo radius 18 → 20. The
|
|
5446
|
+
// grounding halo (the breathing outer circle around
|
|
5447
|
+
// the hub center) is the canvas's signature breath
|
|
5448
|
+
// element — R84 family. R408 bumps r=18 → 20 so the
|
|
5449
|
+
// breath extends slightly further while keeping 4px
|
|
5450
|
+
// clearance before the spoke origin (still room for
|
|
5451
|
+
// spoke start anchors). Visual presence on the
|
|
5452
|
+
// canvas focal point lifts ~23% area (π·20²/π·18²
|
|
5453
|
+
// = 1.23) without changing the per-bucket opacity
|
|
5454
|
+
// envelope or breath rhythm. Visual-weight bump
|
|
5455
|
+
// family 13th anchor — pairs with R404/R405 trough
|
|
5456
|
+
// lifts so the halo now breathes both with more
|
|
5457
|
+
// visible amplitude AND more visual footprint.
|
|
5458
|
+
// R84 per-bucket peak/dur invariants + R244 calc-
|
|
5459
|
+
// Mode='spline' + R245 ease-in-out keySplines all
|
|
5460
|
+
// preserved. data-topo-hub-halo-radius attr exposes
|
|
5461
|
+
// value for tests.
|
|
4764
5462
|
return (
|
|
4765
5463
|
<circle
|
|
4766
|
-
cx={cx} cy={cy} r="
|
|
5464
|
+
cx={cx} cy={cy} r="20"
|
|
4767
5465
|
fill={isLight ? '#d1fae5' : '#10b981'}
|
|
4768
5466
|
opacity={isLight ? 0.42 : 0.12}
|
|
4769
5467
|
data-hub-busyness={busy}
|
|
5468
|
+
data-topo-hub-halo-radius="20"
|
|
5469
|
+
data-topo-hub-halo-trough={isLight ? troughLight : troughDark}
|
|
5470
|
+
data-topo-hub-halo-peak={isLight ? peakLight : peakDark}
|
|
4770
5471
|
/* Round 253 / Loop: hub grounding halo fill transition
|
|
4771
5472
|
for theme toggle. Pre-R253 the base fill (cyber
|
|
4772
5473
|
#10b981 ↔ light #d1fae5) snapped while R244's SMIL
|
|
@@ -4865,11 +5566,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4865
5566
|
textAnchor="middle"
|
|
4866
5567
|
dy="0.36em"
|
|
4867
5568
|
fill={isLight ? '#d1fae5' : '#ecfdf5'}
|
|
4868
|
-
fontSize
|
|
5569
|
+
/* Round 360 / Loop: hub working-count digit fontSize 11
|
|
5570
|
+
→ 12. The hub is the canvas's focal point — its digit
|
|
5571
|
+
is the most-read scalar on the whole topology. R130
|
|
5572
|
+
sized it at 11 (well inside the r=10 / 20-px core);
|
|
5573
|
+
R360 nudges it to 12 (~13 px wide × 12 px tall, still
|
|
5574
|
+
well inside the 20-px diameter) for ~9 % more presence.
|
|
5575
|
+
Sibling visual-weight bump family:
|
|
5576
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
5577
|
+
R295 legend swatch base radius 5.5 → 6
|
|
5578
|
+
R359 recent-row pip radius 1.6 → 1.8
|
|
5579
|
+
R360 hub digit fontSize 11 → 12 (this round)
|
|
5580
|
+
The R209 scale-1.08-on-hub-hover, R225 tabular-nums,
|
|
5581
|
+
R253 fill transition, R213 always-mount opacity gate
|
|
5582
|
+
all preserved. data-topo-hub-working-count-font-size
|
|
5583
|
+
attr exposes the value for tests. */
|
|
5584
|
+
fontSize="12"
|
|
4869
5585
|
fontFamily="monospace"
|
|
4870
5586
|
fontWeight="700"
|
|
4871
5587
|
opacity={workingCount > 0 ? 1 : 0}
|
|
4872
5588
|
data-topo-hub-working-count={workingCount}
|
|
5589
|
+
data-topo-hub-working-count-font-size="12"
|
|
4873
5590
|
data-topo-hub-working-count-hovered={hoveredHub ? 'true' : 'false'}
|
|
4874
5591
|
data-topo-hub-working-count-visible={workingCount > 0 ? 'true' : 'false'}
|
|
4875
5592
|
// Round 209 / Loop: hub workingCount digit scales 1.0 →
|
|
@@ -4910,12 +5627,53 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4910
5627
|
{workingCount}
|
|
4911
5628
|
</text>
|
|
4912
5629
|
{/* decorative highlight (visible when workingCount === 0) */}
|
|
5630
|
+
{/* Round 365 / Loop: hub-center 'lit-lamp' decorative highlight
|
|
5631
|
+
circle r 5 → 5.5. Sibling visual-weight bump family —
|
|
5632
|
+
each round lifts one canvas anchor's geometric presence
|
|
5633
|
+
without disturbing its bbox envelope:
|
|
5634
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
5635
|
+
R295 legend swatch base radius 5.5 → 6
|
|
5636
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
5637
|
+
R360 hub digit fontSize 11 → 12
|
|
5638
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
5639
|
+
R365 hub-highlight base radius 5 → 5.5 (this round)
|
|
5640
|
+
The highlight only renders when workingCount === 0
|
|
5641
|
+
(decorative 'lamp lit but idle' state per R130 + R213
|
|
5642
|
+
always-mount opacity-gate). At idle, the 0.5-px radius
|
|
5643
|
+
bump (21 % area, π*5.5² / π*5² = 1.21) lifts the lamp's
|
|
5644
|
+
presence — still well inside the r=10 hub-core (R130).
|
|
5645
|
+
opacity=0 when working preserved so the hub-digit's R130
|
|
5646
|
+
takeover stays seamless. R213 always-mount opacity-gate
|
|
5647
|
+
+ 300ms opacity transition + pointerEvents:none all
|
|
5648
|
+
preserved. data-topo-hub-highlight-radius attr exposes
|
|
5649
|
+
the value for tests. */}
|
|
5650
|
+
{/* Round 386 / Loop: hub-highlight idle opacity 0.9 → 0.95.
|
|
5651
|
+
When workingCount===0 the highlight paints as the visible
|
|
5652
|
+
idle "lamp lit but no work" core (R130 takeover gate).
|
|
5653
|
+
Pre-R386 idle opacity was 0.9 — a ~6 % fade against full
|
|
5654
|
+
paint that read as slightly-dimmed-ghost on the focal
|
|
5655
|
+
point. R386 lifts to 0.95 (idle alpha gap halved 0.10
|
|
5656
|
+
→ 0.05) so the canvas anchor reads more confidently
|
|
5657
|
+
as a present-but-idle state rather than a faded ghost.
|
|
5658
|
+
Theme-consistency / canvas-presence polish family (4th
|
|
5659
|
+
anchor):
|
|
5660
|
+
R370 hub hover-ring opacity 0.7 → 0.8 cyber
|
|
5661
|
+
R371 edge-badge rest opacity 0.82 → 0.85 cyber
|
|
5662
|
+
R372 minimap offline-dot opacity 0.5 → 0.6
|
|
5663
|
+
R386 hub-highlight idle opacity 0.9 → 0.95 (this round)
|
|
5664
|
+
opacity=0 when working preserved so the hub-digit's
|
|
5665
|
+
R130 takeover stays seamless. 300ms opacity transition
|
|
5666
|
+
+ R213 always-mount opacity-gate + pointerEvents:none
|
|
5667
|
+
+ R365 r=5.5 all preserved. data-topo-hub-highlight-
|
|
5668
|
+
opacity attr exposes the resolved value for tests. */}
|
|
4913
5669
|
<circle
|
|
4914
|
-
cx={cx} cy={cy} r="5"
|
|
5670
|
+
cx={cx} cy={cy} r="5.5"
|
|
4915
5671
|
fill="#d1fae5"
|
|
4916
|
-
opacity={workingCount > 0 ? 0 : 0.
|
|
5672
|
+
opacity={workingCount > 0 ? 0 : 0.95}
|
|
4917
5673
|
data-topo-hub-highlight
|
|
4918
5674
|
data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}
|
|
5675
|
+
data-topo-hub-highlight-radius="5.5"
|
|
5676
|
+
data-topo-hub-highlight-opacity={workingCount > 0 ? 0 : 0.95}
|
|
4919
5677
|
style={{
|
|
4920
5678
|
pointerEvents: 'none',
|
|
4921
5679
|
transition: 'opacity 300ms ease-out',
|
|
@@ -4946,15 +5704,46 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4946
5704
|
R142 group boxes, R143/R144 rows, R164 edge badges,
|
|
4947
5705
|
R177 hub ring). prefers-reduced-motion respected via
|
|
4948
5706
|
R29 globals.css blanket. */}
|
|
5707
|
+
{/* Round 385 / Loop: hub hover-ring strokeWidth 1.5 → 1.75.
|
|
5708
|
+
Sibling visual-weight bump (11th anchor) to R367 edge-
|
|
5709
|
+
badge rest stroke 1 → 1.25. The ring is only visible
|
|
5710
|
+
during hub hover (opacity=0 rest, R177 + R370 control
|
|
5711
|
+
the hover-state alpha) so the change manifests purely
|
|
5712
|
+
as a thicker hover-state ring on the canvas focal
|
|
5713
|
+
point. R177 r 14 → 17 grow + R370 opacity 0 → 0.8
|
|
5714
|
+
already lift the hover cue; R385 adds stroke weight
|
|
5715
|
+
as the third lift axis. Stays clear of R51 overlap-
|
|
5716
|
+
test sentinel value 3 (1.75 is non-sentinel); the
|
|
5717
|
+
R51 selector is gated to g[data-node] ancestors so
|
|
5718
|
+
this hub-internal circle is invisible to the probe
|
|
5719
|
+
regardless. R253 stroke transition + pointerEvents:
|
|
5720
|
+
none preserved. data-topo-hub-hover-ring-stroke-width
|
|
5721
|
+
attr exposes the value for tests. */}
|
|
4949
5722
|
<circle
|
|
4950
5723
|
cx={cx} cy={cy}
|
|
4951
5724
|
r={hoveredHub ? 17 : 14}
|
|
4952
5725
|
fill="none"
|
|
4953
5726
|
stroke={isLight ? '#059669' : '#10b981'}
|
|
4954
|
-
strokeWidth="1.
|
|
4955
|
-
|
|
5727
|
+
strokeWidth="1.75"
|
|
5728
|
+
/* Round 370 / Loop: hub hover-ring cyber opacity 0.7 →
|
|
5729
|
+
0.8. R177 designed the hub hover-ring at opacity-0 →
|
|
5730
|
+
0.85 (light) / 0 → 0.7 (cyber). The 15 % gap between
|
|
5731
|
+
the two themes meant cyber-theme operators got a
|
|
5732
|
+
noticeably softer hover cue than light-theme users
|
|
5733
|
+
against backgrounds that should equalise (dark bg
|
|
5734
|
+
needs more luminance to read as 'on'). R370 bumps
|
|
5735
|
+
cyber 0.7 → 0.8, narrowing the theme gap to 5 % —
|
|
5736
|
+
sibling theme-consistency polish to R251 edge badge
|
|
5737
|
+
fill/opacity (cyber 0.82 / light 0.95) and R246/R247
|
|
5738
|
+
panel transition families. Light theme 0.85 stays
|
|
5739
|
+
as is (already in the legibility band). data-topo-
|
|
5740
|
+
hub-hover-ring-opacity attr exposes the value for
|
|
5741
|
+
tests. */
|
|
5742
|
+
opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}
|
|
4956
5743
|
data-topo-hub-hover-ring
|
|
4957
5744
|
data-topo-hub-hover-ring-radius={hoveredHub ? 17 : 14}
|
|
5745
|
+
data-topo-hub-hover-ring-stroke-width="1.75"
|
|
5746
|
+
data-topo-hub-hover-ring-opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}
|
|
4958
5747
|
/* Round 253 / Loop: hub hover ring also gets stroke
|
|
4959
5748
|
transition for theme toggle (cyber #10b981 ↔ light
|
|
4960
5749
|
#059669). The opacity + r transitions stay for hover
|
|
@@ -5383,14 +6172,49 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5383
6172
|
keyTimes="0;0.5;1"
|
|
5384
6173
|
keySplines="0.42 0 0.58 1;0.42 0 0.58 1"
|
|
5385
6174
|
/>
|
|
6175
|
+
{/* Round 409 / Loop: active-node pulse peak
|
|
6176
|
+
opacity lift — cyber 0.18 → 0.20 / light
|
|
6177
|
+
0.12 → 0.14. The R243 pulse is the per-
|
|
6178
|
+
node "this alias has recent flow" breath;
|
|
6179
|
+
its peak amplitude was tuned in R243 but
|
|
6180
|
+
sat at 0.18 cyber / 0.12 light — visible
|
|
6181
|
+
but understated against the surrounding
|
|
6182
|
+
status halos (R407 lifted offline halos
|
|
6183
|
+
0.25→0.30 cyber; online halos sit at 0.65
|
|
6184
|
+
cyber). R409 lifts peak +0.02 on both
|
|
6185
|
+
themes so the active-state breath reads
|
|
6186
|
+
more confidently at its high-point while
|
|
6187
|
+
the trough (0.04 cyber / 0.02 light)
|
|
6188
|
+
stays put — breath amplitude widens
|
|
6189
|
+
slightly (cyber 0.14 → 0.16; light 0.10
|
|
6190
|
+
→ 0.12) without changing rhythm or dur.
|
|
6191
|
+
Theme-consistency / canvas-presence family
|
|
6192
|
+
(9 anchors now):
|
|
6193
|
+
R370 hub hover-ring 0.7 → 0.8 cyber
|
|
6194
|
+
R371 edge-badge rest 0.82 → 0.85 cyber
|
|
6195
|
+
R372 minimap offline-dot 0.5 → 0.6
|
|
6196
|
+
R386 hub-highlight idle 0.9 → 0.95
|
|
6197
|
+
R387 hover-detail panel 0.94 → 0.97 cyber
|
|
6198
|
+
R391 hub-spoke active 0.7 → 0.8
|
|
6199
|
+
R392 minimap online-dot 0.9 → 0.95
|
|
6200
|
+
R403 click-ripple start 0.7 → 0.8
|
|
6201
|
+
R409 active-node pulse peak (this round)
|
|
6202
|
+
cyber 0.18 → 0.20
|
|
6203
|
+
light 0.12 → 0.14
|
|
6204
|
+
R243 always-mount opacity-gate + R243
|
|
6205
|
+
ease-in-out keySplines + r animation
|
|
6206
|
+
(radius+8 ↔ radius+22) preserved.
|
|
6207
|
+
data-node-pulse-peak attr exposes the
|
|
6208
|
+
resolved per-theme peak for tests. */}
|
|
5386
6209
|
<animate
|
|
5387
6210
|
attributeName="opacity"
|
|
5388
|
-
values={isLight ? '0.
|
|
6211
|
+
values={isLight ? '0.14;0.02;0.14' : '0.20;0.04;0.20'}
|
|
5389
6212
|
dur="2.4s"
|
|
5390
6213
|
repeatCount="indefinite"
|
|
5391
6214
|
calcMode="spline"
|
|
5392
6215
|
keyTimes="0;0.5;1"
|
|
5393
6216
|
keySplines="0.42 0 0.58 1;0.42 0 0.58 1"
|
|
6217
|
+
data-node-pulse-peak={isLight ? '0.14' : '0.20'}
|
|
5394
6218
|
/>
|
|
5395
6219
|
</circle>
|
|
5396
6220
|
</g>
|
|
@@ -5435,12 +6259,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5435
6259
|
parent stays for status flips. data-node-halo-
|
|
5436
6260
|
breath-offset surfaces the chosen offset for
|
|
5437
6261
|
test introspection. */}
|
|
6262
|
+
{/* Round 407 / Loop: offline node halo opacity lift —
|
|
6263
|
+
cyber 0.25 → 0.30 and light 0.4 → 0.45. Pre-R407
|
|
6264
|
+
offline node halos faded to α=0.25 cyber (75 %
|
|
6265
|
+
dim) / α=0.4 light. On the dark canvas the 0.25
|
|
6266
|
+
halo read as "nearly gone" — exactly the
|
|
6267
|
+
legibility floor R404/R405 just lifted on the
|
|
6268
|
+
hub-halo and R372 lifted on minimap offline dots.
|
|
6269
|
+
R407 closes the same family at the per-node halo
|
|
6270
|
+
surface: +0.05 lift on both themes so offline
|
|
6271
|
+
anchors stay legibly present without crossing into
|
|
6272
|
+
"could be online" territory (online cyber 0.65 /
|
|
6273
|
+
light 0.85 unchanged — the 0.30/0.65 cyber ratio
|
|
6274
|
+
still gives 2.17× contrast for online/offline).
|
|
6275
|
+
Stale-state legibility lift family (7 anchors now):
|
|
6276
|
+
R317 subordinate-text gray-500 → gray-400
|
|
6277
|
+
R358 freshness floor 0.25 → 0.30
|
|
6278
|
+
R372 minimap offline-dot 0.5 → 0.6
|
|
6279
|
+
R404 hub-halo cyber trough 0.08 → 0.10
|
|
6280
|
+
R405 hub-halo light trough 0.32 → 0.34
|
|
6281
|
+
R406 edge freshness floor 0.35 → 0.40
|
|
6282
|
+
R407 node halo offline opacity (this round)
|
|
6283
|
+
cyber 0.25 → 0.30
|
|
6284
|
+
light 0.4 → 0.45
|
|
6285
|
+
R278 retired-breath gate + R12 status.halo color
|
|
6286
|
+
+ R226 phase stagger code-path preserved (the
|
|
6287
|
+
breath stays disabled per Vincent's R278 ask;
|
|
6288
|
+
only the BASE opacity floor shifts here). transi-
|
|
6289
|
+
tion list ('fill,opacity' 300ms ease-out) unchanged.
|
|
6290
|
+
data-node-halo-offline-opacity attr exposes the
|
|
6291
|
+
resolved value for tests. */}
|
|
5438
6292
|
<circle
|
|
5439
6293
|
cx={pos.x}
|
|
5440
6294
|
cy={pos.y}
|
|
5441
6295
|
r={radius + 8}
|
|
5442
6296
|
fill={status.halo}
|
|
5443
|
-
opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.
|
|
6297
|
+
opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.45 : 0.30)}
|
|
6298
|
+
data-node-halo-offline-opacity={isOnline ? undefined : (isLight ? 0.45 : 0.30)}
|
|
5444
6299
|
className="transition-[fill,opacity] duration-300 ease-out"
|
|
5445
6300
|
data-node-halo-breath={!reducedMotion && session.status === 'working' ? 'on' : 'off'}
|
|
5446
6301
|
data-node-halo-breath-offset={
|
|
@@ -6071,26 +6926,122 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6071
6926
|
const detailY = pos.y - detailH / 2;
|
|
6072
6927
|
return (
|
|
6073
6928
|
<g transform={`translate(${detailX}, ${detailY})`} data-topo-hover-detail={session.alias} style={{ pointerEvents: 'none' }}>
|
|
6929
|
+
{/* Round 387 / Loop: hover-detail panel cyber backdrop
|
|
6930
|
+
opacity 0.94 → 0.97. The hover-detail card is
|
|
6931
|
+
ALWAYS rendered in active-hover context (it IS
|
|
6932
|
+
the hover product), so it should carry the
|
|
6933
|
+
same backdrop weight as the R348 recent-signal /
|
|
6934
|
+
legend panel HOVER state (which lifts 0.92 →
|
|
6935
|
+
0.97 cyber). Pre-R387 the card sat at 0.94
|
|
6936
|
+
cyber, leaving a 0.03 alpha gap against the
|
|
6937
|
+
R348 panel-hover state — small but visible
|
|
6938
|
+
when the hover-detail floats next to a hovered
|
|
6939
|
+
recent-signal panel. R387 unifies them at 0.97
|
|
6940
|
+
so all active-hover panels paint with the same
|
|
6941
|
+
confident backdrop opacity in cyber. Light
|
|
6942
|
+
stays at 0.98 (already at the strong end —
|
|
6943
|
+
R348 light also stays at 0.97/0.98 max).
|
|
6944
|
+
Theme-consistency / canvas-presence polish
|
|
6945
|
+
family (5th anchor):
|
|
6946
|
+
R370 hub hover-ring opacity 0.7 → 0.8 cyber
|
|
6947
|
+
R371 edge-badge rest opacity 0.82 → 0.85 cyber
|
|
6948
|
+
R372 minimap offline-dot opacity 0.5 → 0.6
|
|
6949
|
+
R386 hub-highlight idle opacity 0.9 → 0.95
|
|
6950
|
+
R387 hover-detail panel opacity 0.94 → 0.97 cyber (this round)
|
|
6951
|
+
data-topo-hover-detail-opacity attr exposes
|
|
6952
|
+
the resolved value for tests. R348 drop-shadow
|
|
6953
|
+
+ rx=8 + stroke=pal.legendAccent + fill=pal.
|
|
6954
|
+
labelBox.fill all preserved. */}
|
|
6955
|
+
{/* Round 390 / Loop: hover-detail card rx 8 → 10.
|
|
6956
|
+
Corner-radius cascade family — the hover-detail
|
|
6957
|
+
card is a panel-tier surface (192×88 floating
|
|
6958
|
+
info card with drop-shadow + stroke), so its
|
|
6959
|
+
corner radius should match the R331 panel tier
|
|
6960
|
+
(rx=10) used by the recent-signal and legend
|
|
6961
|
+
panels. Pre-R390 it shared rx=8 with the R332
|
|
6962
|
+
minimap and R375/R376 segmented-control tier
|
|
6963
|
+
(Layout-toggle, nodeSize, zoom wrappers),
|
|
6964
|
+
which is the "compact chrome control" tier —
|
|
6965
|
+
a tier mismatch for a content-bearing panel.
|
|
6966
|
+
Corner-radius cascade (6 anchors now):
|
|
6967
|
+
R330 canvas rx 12 (root)
|
|
6968
|
+
R331 panels rx 10 (recent-signal, legend)
|
|
6969
|
+
R332 minimap rx 8 (compact chrome)
|
|
6970
|
+
R375 Layout-toggle rx 8 (segmented control)
|
|
6971
|
+
R376 nodeSize/zoom rx 8 (segmented control)
|
|
6972
|
+
R390 hover-detail rx 10 (panel — this round)
|
|
6973
|
+
Pure paint change; no layout shift (rx grows
|
|
6974
|
+
the corner curve INWARD without changing the
|
|
6975
|
+
card's outer bbox). data-topo-hover-detail-
|
|
6976
|
+
rx attr exposes the resolved value for tests.
|
|
6977
|
+
R348 drop-shadow + stroke + R387 opacity all
|
|
6978
|
+
preserved. */}
|
|
6074
6979
|
<rect
|
|
6075
|
-
x="0" y="0" width={detailW} height={detailH} rx="
|
|
6980
|
+
x="0" y="0" width={detailW} height={detailH} rx="10"
|
|
6076
6981
|
fill={pal.labelBox.fill}
|
|
6077
6982
|
stroke={pal.legendAccent}
|
|
6078
|
-
opacity={isLight ? 0.98 : 0.
|
|
6983
|
+
opacity={isLight ? 0.98 : 0.97}
|
|
6984
|
+
data-topo-hover-detail-opacity={isLight ? 0.98 : 0.97}
|
|
6985
|
+
data-topo-hover-detail-rx="10"
|
|
6079
6986
|
style={{ filter: isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.16))' : 'drop-shadow(0 4px 12px rgba(0,0,0,0.6))' }}
|
|
6080
6987
|
/>
|
|
6081
6988
|
<text x="10" y="16" fontSize="9" fontFamily="monospace" fill={pal.legendAccent} fontWeight="700">
|
|
6082
6989
|
{v.id !== 'unknown' ? v.label : '—'}
|
|
6083
6990
|
</text>
|
|
6084
|
-
|
|
6991
|
+
{/* Round 389 / Loop: hover-detail model line (y=32)
|
|
6992
|
+
fontWeight 400 → 600. R388 lifted body lines
|
|
6993
|
+
(runtime/host/task at fontSize=9) to fw=500;
|
|
6994
|
+
R389 closes the typography hierarchy by giving
|
|
6995
|
+
the model name (the dominant subhead text in
|
|
6996
|
+
the card) its own weight tier. Three-tier
|
|
6997
|
+
ladder now reads cleanly:
|
|
6998
|
+
vendor fontSize=9 fw=700 (label badge)
|
|
6999
|
+
model fontSize=10 fw=600 (subhead — this round)
|
|
7000
|
+
body 3× fontSize=9 fw=500 (R388)
|
|
7001
|
+
One tier step per dimension (size + weight)
|
|
7002
|
+
between adjacent levels — classic editorial
|
|
7003
|
+
hierarchy idiom adapted to a 192×88 SVG card.
|
|
7004
|
+
Sibling to the chip-internal-hierarchy arc
|
|
7005
|
+
(R333-R341/R362/R369) which uses fw=600/500
|
|
7006
|
+
for digit/unit pairs; R389 applies the same
|
|
7007
|
+
fw=600 to a content-bearing identity line.
|
|
7008
|
+
data-topo-hover-detail-model-fw attr exposes
|
|
7009
|
+
the resolved value for tests. pal.legendHeadline
|
|
7010
|
+
fill preserved (R389 doesn't touch color). */}
|
|
7011
|
+
<text x="10" y="32" fontSize="10" fontFamily="monospace" fontWeight="600" fill={pal.legendHeadline} data-topo-hover-detail-model-fw="600">
|
|
6085
7012
|
{session.model || 'model · pending'}
|
|
6086
7013
|
</text>
|
|
6087
|
-
|
|
7014
|
+
{/* Round 388 / Loop: hover-detail body lines (the
|
|
7015
|
+
three fontSize=9 lines: runtime, host, task)
|
|
7016
|
+
gain fontWeight=500. Small-text fw lift family
|
|
7017
|
+
(6th anchor) — fontSize 9-10 px text reads
|
|
7018
|
+
consistently bolder at fw=500 than at the
|
|
7019
|
+
default 400 weight at small sizes, especially
|
|
7020
|
+
on the cyber-theme backdrop where stroke-
|
|
7021
|
+
rendering is the limiting factor.
|
|
7022
|
+
Sibling lifts in this family:
|
|
7023
|
+
R363 recent-row alias text 400 → 500
|
|
7024
|
+
R364 legend-row label 400 → 500
|
|
7025
|
+
R366 group-label count tspan 400 → 500
|
|
7026
|
+
R368 +N more flows footer 400 → 500
|
|
7027
|
+
R373 pressure-bar kicker (font-medium)
|
|
7028
|
+
R388 hover-detail body lines 400 → 500 (this round)
|
|
7029
|
+
Tier structure preserved:
|
|
7030
|
+
y=16 vendor (fw=700, headline)
|
|
7031
|
+
y=32 model (fontSize=10, subhead by size)
|
|
7032
|
+
y=48 runtime / y=64 host / y=80 task (body, now fw=500)
|
|
7033
|
+
The y=80 task line keeps opacity=0.7 so its
|
|
7034
|
+
caption-tier identity stays distinct from the
|
|
7035
|
+
y=48 / y=64 body lines despite shared fw.
|
|
7036
|
+
data-topo-hover-detail-body-fw attr exposes
|
|
7037
|
+
the resolved value for tests. */}
|
|
7038
|
+
<text x="10" y="48" fontSize="9" fontFamily="monospace" fontWeight="500" fill={pal.legendText} data-topo-hover-detail-body-fw="500">
|
|
6088
7039
|
{rt ? rt.label : 'runtime · pending'}
|
|
6089
7040
|
</text>
|
|
6090
|
-
<text x="10" y="64" fontSize="9" fontFamily="monospace" fill={pal.legendText}>
|
|
7041
|
+
<text x="10" y="64" fontSize="9" fontFamily="monospace" fontWeight="500" fill={pal.legendText} data-topo-hover-detail-body-fw="500">
|
|
6091
7042
|
host · {session.server || 'unknown'}
|
|
6092
7043
|
</text>
|
|
6093
|
-
<text x="10" y="80" fontSize="9" fontFamily="monospace" fill={pal.legendText} opacity="0.7">
|
|
7044
|
+
<text x="10" y="80" fontSize="9" fontFamily="monospace" fontWeight="500" fill={pal.legendText} opacity="0.7" data-topo-hover-detail-body-fw="500">
|
|
6094
7045
|
{session.task ? truncate(session.task, 28) : 'no recent task'}
|
|
6095
7046
|
</text>
|
|
6096
7047
|
</g>
|
|
@@ -6154,14 +7105,41 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6154
7105
|
keySplines="0.25 0.1 0.25 1"
|
|
6155
7106
|
fill="freeze"
|
|
6156
7107
|
/>
|
|
7108
|
+
{/* Round 403 / Loop: click-ripple SMIL initial opacity
|
|
7109
|
+
0.7 → 0.8. Pre-R403 the ripple's opacity animation
|
|
7110
|
+
faded from 0.7 to 0 over 500ms, providing a clean
|
|
7111
|
+
click-feedback pulse. Theme-consistency / canvas-
|
|
7112
|
+
presence polish family (R370 hub hover-ring +
|
|
7113
|
+
R391 hub-spoke active) already lifted paired
|
|
7114
|
+
hover-state alphas from 0.7 → 0.8. R403 brings
|
|
7115
|
+
click-feedback into that same alpha — three canvas
|
|
7116
|
+
state-feedback indicators (hover-ring, active spoke,
|
|
7117
|
+
click ripple) now share a uniform 0.8 start alpha
|
|
7118
|
+
so the visual "I responded" signal carries the
|
|
7119
|
+
same weight regardless of which state fired it.
|
|
7120
|
+
Pre-R403 invariants preserved: 500ms duration,
|
|
7121
|
+
R227 calcMode='spline' + ease-out keySplines
|
|
7122
|
+
(0.25 0.1 0.25 1), fill='freeze', concurrent r
|
|
7123
|
+
animation. Theme-consistency family (8 anchors):
|
|
7124
|
+
R370 hub hover-ring 0.7 → 0.8
|
|
7125
|
+
R371 edge-badge rest 0.82 → 0.85 cyber
|
|
7126
|
+
R372 minimap offline-dot 0.5 → 0.6
|
|
7127
|
+
R386 hub-highlight idle 0.9 → 0.95
|
|
7128
|
+
R387 hover-detail panel 0.94 → 0.97 cyber
|
|
7129
|
+
R391 hub-spoke active 0.7 → 0.8
|
|
7130
|
+
R392 minimap online-dot 0.9 → 0.95
|
|
7131
|
+
R403 click-ripple start 0.7 → 0.8 (this round)
|
|
7132
|
+
data-click-ripple-start-opacity attr exposes the
|
|
7133
|
+
resolved value for tests. */}
|
|
6157
7134
|
<animate
|
|
6158
7135
|
attributeName="opacity"
|
|
6159
|
-
values="0.
|
|
7136
|
+
values="0.8;0"
|
|
6160
7137
|
dur="0.5s"
|
|
6161
7138
|
calcMode="spline"
|
|
6162
7139
|
keyTimes="0;1"
|
|
6163
7140
|
keySplines="0.25 0.1 0.25 1"
|
|
6164
7141
|
fill="freeze"
|
|
7142
|
+
data-click-ripple-start-opacity="0.8"
|
|
6165
7143
|
/>
|
|
6166
7144
|
</circle>
|
|
6167
7145
|
)}
|
|
@@ -6236,7 +7214,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6236
7214
|
x="0" y="0" width="230" height="88" rx="10"
|
|
6237
7215
|
fill={pal.legendBox.fill}
|
|
6238
7216
|
stroke={pal.legendBox.stroke}
|
|
6239
|
-
|
|
7217
|
+
// Round 348 / Loop: recent-signal panel rect opacity hover-
|
|
7218
|
+
// state bump — joins the panel-hover cue stack (R135 drop-
|
|
7219
|
+
// shadow boost + R345 title letter-spacing tween 0.3 → 0.4
|
|
7220
|
+
// + R266 fill theme-flip). Cyber 0.92 → 0.97, light 0.97 →
|
|
7221
|
+
// 1.0 on hoveredPanel === 'recent'. The panel "solidifies"
|
|
7222
|
+
// on hover — pure paint-level change, geometry-safe (bbox
|
|
7223
|
+
// unchanged so topo-overlap-test invariants hold). The
|
|
7224
|
+
// R247 transition list already includes `opacity 200ms
|
|
7225
|
+
// ease-out` so the value tween is automatic. Sibling
|
|
7226
|
+
// change at legend panel rect below (~line 7222).
|
|
7227
|
+
opacity={hoveredPanel === 'recent' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}
|
|
6240
7228
|
style={{
|
|
6241
7229
|
/* R135: drop-shadow intensifies on panel hover. Base
|
|
6242
7230
|
shadow (2px / 6px blur) signals card elevation
|
|
@@ -6359,8 +7347,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6359
7347
|
const alpha = ageSec <= 30
|
|
6360
7348
|
? 1
|
|
6361
7349
|
: ageSec <= 300
|
|
6362
|
-
? 1 - ((ageSec - 30) / 270) * 0.
|
|
6363
|
-
: 0.25
|
|
7350
|
+
? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
|
|
7351
|
+
: 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
|
|
6364
7352
|
// Dark cyan-400 / light teal-600 with alpha — same
|
|
6365
7353
|
// palette as R161's chip bullet so the two scopes
|
|
6366
7354
|
// visually align even side-by-side.
|
|
@@ -6373,6 +7361,21 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6373
7361
|
textAnchor="end"
|
|
6374
7362
|
fontSize="10"
|
|
6375
7363
|
fontFamily="monospace"
|
|
7364
|
+
// Round 349 / Loop: editorial letter-spacing 0.2 on the
|
|
7365
|
+
// recent-signal panel header count. Sits one tier below
|
|
7366
|
+
// the R301 panel title letterSpacing="0.3" so the panel
|
|
7367
|
+
// header reads as a 2-step hierarchy (title 0.3 / count
|
|
7368
|
+
// 0.2). Sibling change on the legend panel count below
|
|
7369
|
+
// closes the panel-pair editorial symmetry. Joins the
|
|
7370
|
+
// R285 / R289 / R301 / R302 / R304 / R325 editorial-
|
|
7371
|
+
// letterspacing tier at the panel-summary scope. The
|
|
7372
|
+
// R162 freshness fill, R225 tabular-nums, R311 fw=600,
|
|
7373
|
+
// R336 unit-tspan opacity-0.7 split all preserved —
|
|
7374
|
+
// the tier propagates to all descendant tspans via
|
|
7375
|
+
// SVG inheritance. data-recent-panel-count-letter-
|
|
7376
|
+
// spacing exposes the value for tests.
|
|
7377
|
+
letterSpacing="0.2"
|
|
7378
|
+
data-recent-panel-count-letter-spacing="0.2"
|
|
6376
7379
|
>
|
|
6377
7380
|
{/* Round 225 / Loop: tabular-nums on the panel-header
|
|
6378
7381
|
flow-count tspan. The "{N} flows" string lives in
|
|
@@ -6841,17 +7844,58 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6841
7844
|
const alpha = ageSec <= 30
|
|
6842
7845
|
? 1
|
|
6843
7846
|
: ageSec <= 300
|
|
6844
|
-
? 1 - ((ageSec - 30) / 270) * 0.
|
|
6845
|
-
: 0.25
|
|
7847
|
+
? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
|
|
7848
|
+
: 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
|
|
6846
7849
|
return (
|
|
6847
7850
|
<circle
|
|
6848
7851
|
cx={10}
|
|
6849
7852
|
cy={38 + index * 16 - 3}
|
|
6850
|
-
|
|
7853
|
+
/* Round 359 / Loop: recency pip base radius
|
|
7854
|
+
1.6 → 1.8. Sibling lift to R358's freshness-
|
|
7855
|
+
floor bump (alpha 0.25 → 0.30) — pre-R358/
|
|
7856
|
+
R359 the stale pip painted at r=1.6 + α=0.25
|
|
7857
|
+
which read as near-invisible chrome. R358
|
|
7858
|
+
gave it more alpha; R359 gives it more area
|
|
7859
|
+
(1.8² / 1.6² ≈ 1.27, so ~27 % more glyph)
|
|
7860
|
+
so the pip stays distinguishable across the
|
|
7861
|
+
freshness ramp. Geometry: 1.8-radius dot
|
|
7862
|
+
centred at (10, row_y - 3) is bbox 3.6×3.6,
|
|
7863
|
+
still well inside the 7-px left margin
|
|
7864
|
+
(x=6 rect-start → x=13 text-start) the R160
|
|
7865
|
+
pip was placed in. Overlap-test reads the
|
|
7866
|
+
parent row rect's bbox, not this pip's, so
|
|
7867
|
+
grid+ring invariants hold. Matches the same
|
|
7868
|
+
1.6 → 1.8 visual-weight bump R295 applied
|
|
7869
|
+
to the legend swatch (5.5 → 6 base radius)
|
|
7870
|
+
and R287 to the minimap viewport stroke
|
|
7871
|
+
(1 → 1.5). data-recent-row-freshness-radius
|
|
7872
|
+
attr exposes the value for tests. */
|
|
7873
|
+
/* Round 383 / Loop: recency pip base radius
|
|
7874
|
+
1.8 → 2.0. Continues the R359 lift
|
|
7875
|
+
trajectory — pip area grows ~23 % (π·2²/
|
|
7876
|
+
π·1.8² ≈ 1.23) for a clearer at-a-glance
|
|
7877
|
+
freshness anchor in each row. Bbox 4.0×4.0
|
|
7878
|
+
still inside the 7-px R160 left margin
|
|
7879
|
+
(3-px remaining clearance vs 3.4 at r=1.8
|
|
7880
|
+
— geometry-safe margin holds). Sibling
|
|
7881
|
+
visual-weight bump family (9th anchor now):
|
|
7882
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
7883
|
+
R295 legend swatch base radius 5.5 → 6
|
|
7884
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
7885
|
+
R360 hub digit fontSize 11 → 12
|
|
7886
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
7887
|
+
R365 hub-highlight base radius 5 → 5.5
|
|
7888
|
+
R367 edge-badge rest stroke 1 → 1.25
|
|
7889
|
+
R374 pressure-bar height 1.5 → 2
|
|
7890
|
+
R383 recent-row pip radius 1.8 → 2.0 (this round)
|
|
7891
|
+
data-recent-row-freshness-radius attr
|
|
7892
|
+
bumps to '2.0' for tests. */
|
|
7893
|
+
r={2.0}
|
|
6851
7894
|
fill={pal.legendAccent}
|
|
6852
7895
|
opacity={alpha}
|
|
6853
7896
|
data-recent-row-freshness={link.key}
|
|
6854
7897
|
data-recent-row-freshness-alpha={alpha.toFixed(2)}
|
|
7898
|
+
data-recent-row-freshness-radius="2.0"
|
|
6855
7899
|
style={{ pointerEvents: 'none', transition: 'opacity 200ms ease-out' }}
|
|
6856
7900
|
/>
|
|
6857
7901
|
);
|
|
@@ -6881,8 +7925,28 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6881
7925
|
fill={isRowActive ? pal.legendHeadline : pal.legendText}
|
|
6882
7926
|
fontSize="9"
|
|
6883
7927
|
fontFamily="monospace"
|
|
7928
|
+
/* Round 363 / Loop: recent-row text fontWeight 400
|
|
7929
|
+
→ 500 (font-medium tier). At fontSize=9 the
|
|
7930
|
+
default-weight 400 glyphs read thin against the
|
|
7931
|
+
panel chrome (pal.legendBox.fill with 0.92/0.97
|
|
7932
|
+
opacity); the 100-weight bump lifts the alias→
|
|
7933
|
+
alias text into the legibility band without
|
|
7934
|
+
changing geometry. The R320 count tspan fw=600
|
|
7935
|
+
(cold) / fw=700 (hot) override still wins
|
|
7936
|
+
locally via inline fontWeight on the inner
|
|
7937
|
+
tspan, so the count-vs-alias hierarchy stays
|
|
7938
|
+
intact:
|
|
7939
|
+
alias fw 500 (R363, this round)
|
|
7940
|
+
count fw 600/700 (R320)
|
|
7941
|
+
Sibling typography lift to R362 chip-row digit
|
|
7942
|
+
500 → 600 — both nudge a within-element data
|
|
7943
|
+
tier without disturbing the surrounding family
|
|
7944
|
+
baseline. data-recent-row-text-font-weight attr
|
|
7945
|
+
exposes the value for tests. */
|
|
7946
|
+
fontWeight="500"
|
|
6884
7947
|
data-recent-row-text={link.key}
|
|
6885
7948
|
data-recent-row-text-pinned={isRowPinned ? 'true' : 'false'}
|
|
7949
|
+
data-recent-row-text-font-weight="500"
|
|
6886
7950
|
style={{
|
|
6887
7951
|
transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',
|
|
6888
7952
|
letterSpacing: isRowPinned ? '0.5px' : '0px',
|
|
@@ -7144,6 +8208,23 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7144
8208
|
surface. transition list extends letter-spacing
|
|
7145
8209
|
200ms ease-out alongside the existing opacity/
|
|
7146
8210
|
fill easings. */}
|
|
8211
|
+
{/* Round 368 / Loop: `+N more flows` footer text gains
|
|
8212
|
+
fontWeight=500 (font-medium tier). Sibling small-
|
|
8213
|
+
text fw lift family with R363 recent-row alias
|
|
8214
|
+
+ R364 legend-row label + R366 group-label count
|
|
8215
|
+
— all four lifts share the same theory: at small
|
|
8216
|
+
fontSize (9-11 px) against panel chrome, SVG-
|
|
8217
|
+
default fw 400 sits at the legibility floor;
|
|
8218
|
+
fw 500 brings the glyph into the deliberate-data
|
|
8219
|
+
band. fontStyle=italic + opacity 0.55 rest + R325
|
|
8220
|
+
letterSpacing 0.2 baseline + R344 hover-spread
|
|
8221
|
+
0.2 → 0.3 + R195 cyan fill on hover all preserved
|
|
8222
|
+
— the fw bump just thickens the italic stroke.
|
|
8223
|
+
Hover-state punch (R195 fill + R325 opacity 0.55
|
|
8224
|
+
→ 0.85 + R344 letter-spacing + R133 underline)
|
|
8225
|
+
stays as is, so the rest-vs-hover delta still
|
|
8226
|
+
reads clearly. data-recent-panel-more-font-weight
|
|
8227
|
+
attr exposes the value for tests. */}
|
|
7147
8228
|
<text
|
|
7148
8229
|
x="115" y="82"
|
|
7149
8230
|
textAnchor="middle"
|
|
@@ -7151,11 +8232,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7151
8232
|
fontSize="9"
|
|
7152
8233
|
fontFamily="monospace"
|
|
7153
8234
|
fontStyle="italic"
|
|
8235
|
+
fontWeight="500"
|
|
7154
8236
|
letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}
|
|
7155
8237
|
opacity={hoveredRecentMore ? 0.85 : 0.55}
|
|
7156
8238
|
textDecoration={hoveredRecentMore ? 'underline' : 'none'}
|
|
7157
8239
|
data-recent-panel-more={moreCount}
|
|
7158
8240
|
data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}
|
|
8241
|
+
data-recent-panel-more-font-weight="500"
|
|
7159
8242
|
style={{ transition: 'opacity 150ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out' }}
|
|
7160
8243
|
>
|
|
7161
8244
|
{`+ ${moreCount}`}
|
|
@@ -7208,7 +8291,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7208
8291
|
x="0" y="0" width="224" height="88" rx="10"
|
|
7209
8292
|
fill={pal.legendBox.fill}
|
|
7210
8293
|
stroke={pal.legendBox.stroke}
|
|
7211
|
-
|
|
8294
|
+
// R348 sibling — legend panel rect opacity hover-state
|
|
8295
|
+
// bump 0.92 → 0.97 (cyber) / 0.97 → 1 (light) on
|
|
8296
|
+
// hoveredPanel === 'legend'. Pairs with the recent-signal
|
|
8297
|
+
// panel rect above so the two corner panels' hover cues
|
|
8298
|
+
// stay symmetric. Geometry-safe (paint-only).
|
|
8299
|
+
opacity={hoveredPanel === 'legend' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}
|
|
7212
8300
|
style={{
|
|
7213
8301
|
filter: hoveredPanel === 'legend'
|
|
7214
8302
|
? (isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.14))'
|
|
@@ -7297,7 +8385,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7297
8385
|
<text
|
|
7298
8386
|
x="211" y="21" textAnchor="end"
|
|
7299
8387
|
fill={pal.legendAccent} fontSize="10" fontFamily="monospace" fontWeight="600"
|
|
8388
|
+
// R349 sibling — legend panel header count picks up
|
|
8389
|
+
// letterSpacing="0.2", one tier below the R301 panel
|
|
8390
|
+
// title 0.3. Pairs with the recent-signal panel count
|
|
8391
|
+
// letter-spacing above so the two corner panels' header
|
|
8392
|
+
// typography stays editorially symmetric.
|
|
8393
|
+
letterSpacing="0.2"
|
|
7300
8394
|
data-legend-panel-count
|
|
8395
|
+
data-legend-panel-count-letter-spacing="0.2"
|
|
7301
8396
|
style={{
|
|
7302
8397
|
transition: 'fill 200ms ease-out',
|
|
7303
8398
|
fontVariantNumeric: 'tabular-nums',
|
|
@@ -7518,14 +8613,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7518
8613
|
so this legend-internal circle is invisible to
|
|
7519
8614
|
that probe. pointerEvents:none so the ring can't
|
|
7520
8615
|
intercept the row click that produced it. */}
|
|
8616
|
+
{/* Round 402 / Loop: legend pin-ring strokeWidth 1.5
|
|
8617
|
+
→ 1.75. Sibling visual-weight bump (12th anchor)
|
|
8618
|
+
to R385 hub hover-ring strokeWidth 1.5 → 1.75 —
|
|
8619
|
+
both are pin/hover state indicators painted as
|
|
8620
|
+
stroke-only circles outside their target swatch
|
|
8621
|
+
with the R51 sentinel value 1.5. R402 lifts to
|
|
8622
|
+
1.75 (matching R385's choice) so the pin signal
|
|
8623
|
+
reads slightly heavier without crossing the
|
|
8624
|
+
R51 sentinel band (3 reserved for offline node).
|
|
8625
|
+
The R51 selector is gated to g[data-node]
|
|
8626
|
+
ancestors so this legend-internal circle (lives
|
|
8627
|
+
under a <g data-legend-status>) is invisible
|
|
8628
|
+
to the probe — same lesson R177/R385 documented.
|
|
8629
|
+
Visual-weight bump family (12 anchors now):
|
|
8630
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
8631
|
+
R295 legend swatch radius 5.5 → 6
|
|
8632
|
+
R359 recent-row pip radius 1.6 → 1.8
|
|
8633
|
+
R360 hub digit fontSize 11 → 12
|
|
8634
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
8635
|
+
R365 hub-highlight radius 5 → 5.5
|
|
8636
|
+
R367 edge-badge rest stroke 1 → 1.25
|
|
8637
|
+
R374 pressure-bar height 1.5 → 2
|
|
8638
|
+
R383 recent-row pip radius 1.8 → 2.0
|
|
8639
|
+
R384 minimap online dot 1.7 → 1.9
|
|
8640
|
+
R385 hub hover-ring stroke 1.5 → 1.75
|
|
8641
|
+
R402 legend pin-ring stroke 1.5 → 1.75 (this round)
|
|
8642
|
+
R181 always-mount opacity gate + 150ms transition
|
|
8643
|
+
+ pointerEvents:none all preserved. data-legend-
|
|
8644
|
+
pin-ring-stroke-width attr exposes the value for
|
|
8645
|
+
tests. */}
|
|
7521
8646
|
<circle
|
|
7522
8647
|
cx="16" cy={row.y0} r="8"
|
|
7523
8648
|
fill="none"
|
|
7524
8649
|
stroke={row.fill}
|
|
7525
|
-
strokeWidth="1.
|
|
8650
|
+
strokeWidth="1.75"
|
|
7526
8651
|
opacity={isPinned ? 1 : 0}
|
|
7527
8652
|
data-legend-pin-ring={row.key}
|
|
7528
8653
|
data-legend-pin-ring-pinned={isPinned ? 'true' : 'false'}
|
|
8654
|
+
data-legend-pin-ring-stroke-width="1.75"
|
|
7529
8655
|
style={{
|
|
7530
8656
|
pointerEvents: 'none',
|
|
7531
8657
|
transition: 'opacity 150ms ease-out',
|
|
@@ -7552,8 +8678,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7552
8678
|
fill={hoveredStatus === row.key || isPinned ? pal.legendHeadline : pal.legendText}
|
|
7553
8679
|
fontSize="11"
|
|
7554
8680
|
fontFamily="monospace"
|
|
8681
|
+
/* Round 364 / Loop: legend-row label fontWeight 400
|
|
8682
|
+
→ 500. Sibling typography lift to R363 recent-row
|
|
8683
|
+
text fw 400 → 500. Both surfaces render small
|
|
8684
|
+
monospace text against panel chrome at fontSize
|
|
8685
|
+
9-11 where SVG-default fw 400 sits at the
|
|
8686
|
+
legibility floor. font-medium tier (500) gives
|
|
8687
|
+
the label a more deliberate-data register.
|
|
8688
|
+
The R309 per-row count text (separate element
|
|
8689
|
+
below at x=215 textAnchor=end) keeps its own
|
|
8690
|
+
fontWeight 600 inline override, so the count >
|
|
8691
|
+
label hierarchy stays intact at the legend
|
|
8692
|
+
scope same as R363 holds it at the recent-row
|
|
8693
|
+
scope:
|
|
8694
|
+
legend label fw 500 (R364, this round)
|
|
8695
|
+
legend count fw 600 (R309)
|
|
8696
|
+
recent alias fw 500 (R363)
|
|
8697
|
+
recent count fw 600/700 (R320)
|
|
8698
|
+
data-legend-row-label-font-weight attr exposes
|
|
8699
|
+
the value for tests. R219 letter-spacing pin
|
|
8700
|
+
tween + R55 fill transition + R181 always-mount
|
|
8701
|
+
pin ring all preserved. */
|
|
8702
|
+
fontWeight="500"
|
|
7555
8703
|
data-legend-row-label={row.key}
|
|
7556
8704
|
data-legend-row-label-pinned={isPinned ? 'true' : 'false'}
|
|
8705
|
+
data-legend-row-label-font-weight="500"
|
|
7557
8706
|
style={{
|
|
7558
8707
|
transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',
|
|
7559
8708
|
letterSpacing: isPinned ? '0.5px' : '0px',
|
|
@@ -7872,14 +9021,63 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7872
9021
|
const isOn = s.status !== 'offline' || !!sseN;
|
|
7873
9022
|
const st = nodeStatus(s, isOn, isLight);
|
|
7874
9023
|
return (
|
|
9024
|
+
/* Round 372 / Loop: minimap offline-dot opacity
|
|
9025
|
+
0.5 → 0.6. Sibling stale-state legibility lift
|
|
9026
|
+
to R358 freshness ramp floor 0.25 → 0.30 + R317
|
|
9027
|
+
subordinate-text-lift family. Pre-R372 R198
|
|
9028
|
+
drew offline dots at α=0.5 (44 % below online
|
|
9029
|
+
0.9). The minimap is a small overlay against
|
|
9030
|
+
the canvas backdrop — at α=0.5 offline dots
|
|
9031
|
+
sat at the legibility floor when the minimap
|
|
9032
|
+
mounted (only on non-default view). R372 lifts
|
|
9033
|
+
offline 0.5 → 0.6 for +20 % relative presence;
|
|
9034
|
+
online stays at 0.9 so the offline/online
|
|
9035
|
+
contrast ratio is now 0.6/0.9 ≈ 0.67 (vs prior
|
|
9036
|
+
0.5/0.9 ≈ 0.56) — still a clear two-tier
|
|
9037
|
+
distinction. R198 opacity + fill + r transition
|
|
9038
|
+
list preserved so status flips still ease
|
|
9039
|
+
smoothly. data-topo-minimap-dot-opacity attr
|
|
9040
|
+
exposes the resolved value for tests. */
|
|
7875
9041
|
<circle
|
|
7876
9042
|
key={s.alias}
|
|
7877
9043
|
cx={p.x * sx} cy={p.y * sy}
|
|
7878
|
-
|
|
9044
|
+
/* Round 384 / Loop: minimap online dot radius 1.7
|
|
9045
|
+
→ 1.9. Sibling visual-weight bump (10th anchor)
|
|
9046
|
+
to R383 recent-row pip 1.8 → 2.0. R198 designed
|
|
9047
|
+
the dots at 1.7 (online) / 1.2 (offline) — at
|
|
9048
|
+
the minimap's 120 × 82 scale these read clearly
|
|
9049
|
+
but the online ↔ offline contrast was modest
|
|
9050
|
+
(1.7/1.2 = 1.42×). R384 bumps online to 1.9 so
|
|
9051
|
+
the tier delta widens to 1.58× (1.9/1.2). Pair
|
|
9052
|
+
completes minimap-dot legibility polish:
|
|
9053
|
+
R358 (era R372) offline opacity 0.5 → 0.6
|
|
9054
|
+
R384 online radius 1.7 → 1.9 (this round)
|
|
9055
|
+
R198 transition list (opacity + fill + r 200ms)
|
|
9056
|
+
preserved so status flips still ease smoothly.
|
|
9057
|
+
data-topo-minimap-dot-radius attr exposes the
|
|
9058
|
+
resolved value for tests. */
|
|
9059
|
+
/* Round 392 / Loop: minimap online dot opacity
|
|
9060
|
+
0.9 → 0.95. Theme-consistency / canvas-presence
|
|
9061
|
+
polish family (7th anchor) — mirrors R386's
|
|
9062
|
+
hub-highlight idle 0.9 → 0.95 lift on the
|
|
9063
|
+
minimap surface: the online-dot's idle alpha
|
|
9064
|
+
gap (0.10 against full presence) halves to
|
|
9065
|
+
0.05, so the live-fleet anchors on the minimap
|
|
9066
|
+
read more confidently. Offline dot stays at
|
|
9067
|
+
R372 0.6 — the binary online/offline contrast
|
|
9068
|
+
ratio shifts from 0.6/0.9 ≈ 0.67 to 0.6/0.95
|
|
9069
|
+
≈ 0.63, preserved as a clear two-tier
|
|
9070
|
+
distinction. R198 opacity + fill + r transition
|
|
9071
|
+
list + R384 r=1.9 + R372 offline 0.6 all
|
|
9072
|
+
preserved. data-topo-minimap-dot-opacity attr
|
|
9073
|
+
bumps to '0.95' for tests. */
|
|
9074
|
+
r={isOn ? 1.9 : 1.2}
|
|
7879
9075
|
fill={st.primary}
|
|
7880
|
-
opacity={isOn ? 0.
|
|
9076
|
+
opacity={isOn ? 0.95 : 0.6}
|
|
7881
9077
|
data-topo-minimap-dot={s.alias}
|
|
7882
9078
|
data-topo-minimap-dot-online={isOn ? 'true' : 'false'}
|
|
9079
|
+
data-topo-minimap-dot-opacity={isOn ? 0.95 : 0.6}
|
|
9080
|
+
data-topo-minimap-dot-radius={isOn ? 1.9 : 1.2}
|
|
7883
9081
|
style={{
|
|
7884
9082
|
transition: 'opacity 200ms ease-out, fill 200ms ease-out, r 200ms ease-out',
|
|
7885
9083
|
} as React.CSSProperties}
|
|
@@ -7920,17 +9118,66 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7920
9118
|
information element to lift it above ambient
|
|
7921
9119
|
chrome. opacity 0.9 stays — strokeWidth alone
|
|
7922
9120
|
does the lifting. */}
|
|
9121
|
+
{/* Round 379 / Loop: minimap viewport rect picks up
|
|
9122
|
+
strokeLinejoin='round'. Pre-R379 the rect's 4
|
|
9123
|
+
corners painted with default 'miter' joins —
|
|
9124
|
+
sharp 90° corners with a small miter overshoot
|
|
9125
|
+
(≈ strokeWidth × 1.4 = 2.1 px at sw=1.5). R379
|
|
9126
|
+
rounds the joins so corners arc smoothly through
|
|
9127
|
+
a quarter-circle of radius ≈ strokeWidth/2. At
|
|
9128
|
+
sw=1.5 that's a 0.75-px radius — subtle but
|
|
9129
|
+
matches the same stroke-softening vocabulary R288
|
|
9130
|
+
chrome icons (zoom/reset/fullscreen) and R378
|
|
9131
|
+
flow-rail already speak. Geometry-safe: stroke-
|
|
9132
|
+
linejoin only affects the corner overshoot, the
|
|
9133
|
+
rect's bbox is unchanged. R287 strokeWidth=1.5 +
|
|
9134
|
+
R346 hover-state strokeWidth/opacity bump + R199
|
|
9135
|
+
smoothView x/y/w/h transition all preserved.
|
|
9136
|
+
data-topo-minimap-viewport-linejoin attr exposes
|
|
9137
|
+
the value for tests. */}
|
|
9138
|
+
{/* Round 393 / Loop: minimap viewport rect rx 0 → 2.
|
|
9139
|
+
Pre-R393 the cyan-stroked viewport rect (the frame
|
|
9140
|
+
showing what's currently visible on the canvas)
|
|
9141
|
+
drew with sharp corners inside the R332 rounded
|
|
9142
|
+
minimap container (rx=8). A small frame with sharp
|
|
9143
|
+
corners sitting inside a rounded container reads
|
|
9144
|
+
as visually loud — the 90° corners catch the eye
|
|
9145
|
+
against the soft container edge. R393 adds rx=2
|
|
9146
|
+
so the viewport corners get a subtle radius that
|
|
9147
|
+
matches the family's softening idiom on a sub-
|
|
9148
|
+
element scale. The R379 strokeLinejoin='round'
|
|
9149
|
+
already softens stroke joins; R393 adds a complete
|
|
9150
|
+
geometric soften via rx.
|
|
9151
|
+
Corner-radius cascade (7 anchors now):
|
|
9152
|
+
R330 canvas rx 12
|
|
9153
|
+
R331 panels rx 10
|
|
9154
|
+
R332 minimap container rx 8
|
|
9155
|
+
R375 Layout-toggle rx 8
|
|
9156
|
+
R376 nodeSize/zoom rx 8
|
|
9157
|
+
R390 hover-detail rx 10
|
|
9158
|
+
R393 minimap viewport rx 2 (this round, sub-element)
|
|
9159
|
+
The 2-px radius is intentionally small — the
|
|
9160
|
+
viewport rect is typically only 30-50px wide,
|
|
9161
|
+
where rx=2 reads as "rounded enough to not snap"
|
|
9162
|
+
without feeling pillowy. data-topo-minimap-
|
|
9163
|
+
viewport-rx attr exposes the resolved value
|
|
9164
|
+
for tests. R346 hover-state tweens (strokeWidth
|
|
9165
|
+
+ opacity) preserved verbatim. */}
|
|
7923
9166
|
<rect
|
|
7924
9167
|
x={Math.max(0, rectX)} y={Math.max(0, rectY)}
|
|
7925
9168
|
width={Math.max(0, Math.min(MW - Math.max(0, rectX), rectW))}
|
|
7926
9169
|
height={Math.max(0, Math.min(MH - Math.max(0, rectY), rectH))}
|
|
9170
|
+
rx="2"
|
|
7927
9171
|
fill="none" stroke={pal.legendAccent}
|
|
7928
9172
|
// R346: strokeWidth + opacity tween on container hover.
|
|
7929
9173
|
strokeWidth={hoveredMinimap ? '1.75' : '1.5'}
|
|
9174
|
+
strokeLinejoin="round"
|
|
7930
9175
|
opacity={hoveredMinimap ? '1' : '0.9'}
|
|
7931
9176
|
data-topo-minimap-viewport
|
|
9177
|
+
data-topo-minimap-viewport-rx="2"
|
|
7932
9178
|
data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}
|
|
7933
9179
|
data-topo-minimap-viewport-hover={hoveredMinimap ? 'true' : 'false'}
|
|
9180
|
+
data-topo-minimap-viewport-linejoin="round"
|
|
7934
9181
|
style={{
|
|
7935
9182
|
transition: smoothView
|
|
7936
9183
|
? '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'
|
|
@@ -7989,8 +9236,22 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7989
9236
|
own transition-colors. Same R254 holdover pattern that
|
|
7990
9237
|
R263 just closed at the canvas wrapper scope, now at the
|
|
7991
9238
|
chrome strip's nodeSize sub-wrapper scope. */}
|
|
9239
|
+
{/* Round 376 / Loop: nodeSize wrapper rounded-md → rounded-lg.
|
|
9240
|
+
Sibling polish to R375 Layout-toggle wrapper. Three
|
|
9241
|
+
chrome-strip segmented controls now all share rounded-lg
|
|
9242
|
+
at the wrapper tier:
|
|
9243
|
+
R375 Layout-toggle wrapper rounded-lg 8 px
|
|
9244
|
+
R376 nodeSize wrapper rounded-lg 8 px (this round)
|
|
9245
|
+
R376 zoom wrapper rounded-lg 8 px (this round)
|
|
9246
|
+
Individual atomic chrome buttons (reset, fullscreen) keep
|
|
9247
|
+
rounded-md (6 px) as their own atomic-button tier — the
|
|
9248
|
+
chrome strip's typography now expresses a clear two-tier
|
|
9249
|
+
hierarchy: 'segmented control container' (rounded-lg)
|
|
9250
|
+
vs 'standalone button' (rounded-md). Pure paint change,
|
|
9251
|
+
no layout shift. */}
|
|
7992
9252
|
<div
|
|
7993
|
-
className="flex items-center rounded-
|
|
9253
|
+
className="flex items-center rounded-lg border overflow-hidden"
|
|
9254
|
+
data-topo-chrome-nodesize-radius="rounded-lg"
|
|
7994
9255
|
style={{
|
|
7995
9256
|
background: pal.legendBox.fill,
|
|
7996
9257
|
borderColor: pal.containerBorder,
|
|
@@ -8070,8 +9331,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8070
9331
|
data-topo-chrome-view-group-leader marks the boundary surface
|
|
8071
9332
|
for the test probe; data-topo-chrome-fleet-group-trailer marks
|
|
8072
9333
|
the nodeSize wrapper's right edge for the gap measurement. */}
|
|
9334
|
+
{/* R376 sibling — zoom wrapper rounded-md → rounded-lg.
|
|
9335
|
+
Closes the chrome-strip segmented-control corner radius
|
|
9336
|
+
cascade (Layout R375 + nodeSize R376 + zoom R376). */}
|
|
8073
9337
|
<div
|
|
8074
|
-
className="ml-1.5 flex items-center rounded-
|
|
9338
|
+
className="ml-1.5 flex items-center rounded-lg border overflow-hidden"
|
|
9339
|
+
data-topo-chrome-zoom-wrapper-radius="rounded-lg"
|
|
8075
9340
|
style={{
|
|
8076
9341
|
background: pal.legendBox.fill,
|
|
8077
9342
|
borderColor: pal.containerBorder,
|
|
@@ -8087,7 +9352,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8087
9352
|
// R196: press-state deepens bg one tier above hover (white/5
|
|
8088
9353
|
// → white/10) so mouse-down has a tactile dim before the
|
|
8089
9354
|
// R186 icon pop fires on release.
|
|
8090
|
-
|
|
9355
|
+
// R352: `group` lets the inner svg respond via group-hover.
|
|
9356
|
+
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"
|
|
8091
9357
|
style={{ color: pal.legendText }}
|
|
8092
9358
|
aria-label="Zoom out"
|
|
8093
9359
|
title="Zoom out (−)"
|
|
@@ -8095,11 +9361,25 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8095
9361
|
{/* R186: icon pop on click. CSS animation runs once;
|
|
8096
9362
|
React removes the class after 240ms so a quick
|
|
8097
9363
|
re-click can replay. */}
|
|
9364
|
+
{/* Round 352 / Loop: zoom-out icon picks up group-hover:
|
|
9365
|
+
scale-110 — sibling to R350 reset hover-rotate. Pre-
|
|
9366
|
+
R352 hovering the zoom button only changed the bg
|
|
9367
|
+
(white/5); the icon inside stayed perfectly still.
|
|
9368
|
+
R352 lifts the icon 10% on hover for a tactile "this
|
|
9369
|
+
button does something" cue. The R186 anet-chrome-pop
|
|
9370
|
+
keyframe (220ms scale 1→1.06→1) still owns transform
|
|
9371
|
+
during click via CSS-animation precedence over
|
|
9372
|
+
transition-transform; after the pop ends + className
|
|
9373
|
+
is removed, the group-hover scale-110 picks up
|
|
9374
|
+
smoothly. `transform-gpu` hint promotes the svg to
|
|
9375
|
+
its own compositor layer for crisper edges during
|
|
9376
|
+
the scale tween. Sibling change on zoom-in icon
|
|
9377
|
+
below. */}
|
|
8098
9378
|
<svg
|
|
8099
9379
|
width="12" height="12" viewBox="0 0 24 24"
|
|
8100
9380
|
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
8101
9381
|
aria-hidden
|
|
8102
|
-
className={chromePopping === 'zoom-out' ? 'anet-chrome-pop' :
|
|
9382
|
+
className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-out' ? ' anet-chrome-pop' : ''}`}
|
|
8103
9383
|
data-topo-chrome-zoom-out-icon
|
|
8104
9384
|
><path d="M5 12h14" /></svg>
|
|
8105
9385
|
</button>
|
|
@@ -8145,11 +9425,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8145
9425
|
chromePopping === 'zoom-in' || chromePopping === 'zoom-out'
|
|
8146
9426
|
? 'true' : 'false'
|
|
8147
9427
|
}
|
|
9428
|
+
data-topo-chrome-zoom-level-hover={hoveredZoomLevel ? 'true' : 'false'}
|
|
9429
|
+
onMouseEnter={() => setHoveredZoomLevel(true)}
|
|
9430
|
+
onMouseLeave={() => setHoveredZoomLevel(false)}
|
|
8148
9431
|
style={{
|
|
8149
9432
|
color: pal.legendText,
|
|
8150
9433
|
borderColor: pal.containerBorder,
|
|
8151
9434
|
minWidth: 46,
|
|
8152
9435
|
display: 'inline-block',
|
|
9436
|
+
// R347: letter-spacing hover tween — extends R344/R345
|
|
9437
|
+
// hover-letter-spacing family into the chrome strip.
|
|
9438
|
+
letterSpacing: hoveredZoomLevel ? '0.5px' : '0',
|
|
8153
9439
|
/* Round 264 / Loop: zoom level readout gains theme-toggle
|
|
8154
9440
|
transition. The span has theme-driven color (pal.
|
|
8155
9441
|
legendText) + border-x (pal.containerBorder via the
|
|
@@ -8158,7 +9444,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8158
9444
|
on theme flip while siblings eased. Sibling treatment
|
|
8159
9445
|
to the nodeSize + zoom wrapper transitions added this
|
|
8160
9446
|
round. */
|
|
8161
|
-
transition: 'color 200ms ease-out, border-color 200ms ease-out',
|
|
9447
|
+
transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out',
|
|
8162
9448
|
}}
|
|
8163
9449
|
title="Current zoom level"
|
|
8164
9450
|
>
|
|
@@ -8169,18 +9455,22 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8169
9455
|
data-topo-chrome-zoom-in
|
|
8170
9456
|
data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}
|
|
8171
9457
|
// R196: press-state (mirror of zoom-out above).
|
|
8172
|
-
|
|
9458
|
+
// R352: `group` lets the inner svg respond via group-hover.
|
|
9459
|
+
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"
|
|
8173
9460
|
style={{ color: pal.legendText }}
|
|
8174
9461
|
aria-label="Zoom in"
|
|
8175
9462
|
title="Zoom in (+)"
|
|
8176
9463
|
>
|
|
8177
9464
|
{/* R186: icon pop on click. Same one-shot CSS animation
|
|
8178
9465
|
as zoom-out; React removes the class after 240ms. */}
|
|
9466
|
+
{/* R352 sibling — zoom-in icon picks up the same
|
|
9467
|
+
group-hover:scale-110 family. Mirror change at
|
|
9468
|
+
the zoom-out icon above. */}
|
|
8179
9469
|
<svg
|
|
8180
9470
|
width="12" height="12" viewBox="0 0 24 24"
|
|
8181
9471
|
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
8182
9472
|
aria-hidden
|
|
8183
|
-
className={chromePopping === 'zoom-in' ? 'anet-chrome-pop' :
|
|
9473
|
+
className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-in' ? ' anet-chrome-pop' : ''}`}
|
|
8184
9474
|
data-topo-chrome-zoom-in-icon
|
|
8185
9475
|
><path d="M12 5v14M5 12h14" /></svg>
|
|
8186
9476
|
</button>
|
|
@@ -8189,9 +9479,35 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8189
9479
|
onClick={() => { armResetSpin(); resetView(); }}
|
|
8190
9480
|
data-topo-chrome-reset
|
|
8191
9481
|
data-topo-chrome-reset-spinning={resetSpinning ? 'true' : 'false'}
|
|
9482
|
+
data-topo-chrome-reset-hover={hoveredReset ? 'true' : 'false'}
|
|
9483
|
+
// R350: hover state drives the icon transform below.
|
|
9484
|
+
onMouseEnter={() => setHoveredReset(true)}
|
|
9485
|
+
onMouseLeave={() => setHoveredReset(false)}
|
|
9486
|
+
onFocus={() => setHoveredReset(true)}
|
|
9487
|
+
onBlur={() => setHoveredReset(false)}
|
|
8192
9488
|
// R196: press-state deepens before R184 reset-spin fires on
|
|
8193
9489
|
// release — mouse-down dim then 450ms spin = full handshake.
|
|
8194
|
-
|
|
9490
|
+
/* Round 400 / Loop · milestone: chrome reset + fullscreen
|
|
9491
|
+
buttons gain hover:-translate-y-px lift — closes the
|
|
9492
|
+
hover-lift gesture vocabulary across every standalone
|
|
9493
|
+
interactive HTML element in TopoGraph. Segmented
|
|
9494
|
+
controls (zoom -/+, nodeSize S/M/L, Layout Ring/Grid)
|
|
9495
|
+
intentionally stay planted: lifting one segment of a
|
|
9496
|
+
unified strip would tear the visual unity of the
|
|
9497
|
+
segmented control. Only the standalone chrome buttons
|
|
9498
|
+
(reset, fullscreen) get the lift.
|
|
9499
|
+
Gesture vocabulary post-R400 (now complete across HTML):
|
|
9500
|
+
chip-row chips (3×) -1 px R398, R399
|
|
9501
|
+
filter pin pills (4×) -1 px R397
|
|
9502
|
+
recent-signal row -1 px R143
|
|
9503
|
+
legend row -1 px R144
|
|
9504
|
+
reset button -1 px R400 (this round)
|
|
9505
|
+
fullscreen button -1 px R400 (this round)
|
|
9506
|
+
Every standalone interactive HTML surface in TopoGraph
|
|
9507
|
+
now lifts on hover. data-topo-chrome-reset-hover-lift
|
|
9508
|
+
attr surfaces the lift for tests. */
|
|
9509
|
+
className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
|
|
9510
|
+
data-topo-chrome-reset-hover-lift="true"
|
|
8195
9511
|
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
|
|
8196
9512
|
aria-label="Reset view"
|
|
8197
9513
|
title="Reset zoom + pan (0, or double-click the canvas)"
|
|
@@ -8218,6 +9534,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8218
9534
|
aria-hidden
|
|
8219
9535
|
className={resetSpinning ? 'anet-reset-spin' : undefined}
|
|
8220
9536
|
data-topo-chrome-reset-icon
|
|
9537
|
+
// R350: hover-rotate preview of the R184 click-spin.
|
|
9538
|
+
// Gated on !resetSpinning so the anet-reset-spin keyframe
|
|
9539
|
+
// owns transform during its 450ms run. transformOrigin
|
|
9540
|
+
// 'center' so rotation pivots around the icon's centre
|
|
9541
|
+
// (default would be top-left and the icon would arc).
|
|
9542
|
+
style={{
|
|
9543
|
+
transform: hoveredReset && !resetSpinning ? 'rotate(-8deg)' : 'rotate(0deg)',
|
|
9544
|
+
transformOrigin: 'center',
|
|
9545
|
+
transition: 'transform 200ms ease-out',
|
|
9546
|
+
}}
|
|
9547
|
+
data-topo-chrome-reset-icon-hover={hoveredReset && !resetSpinning ? 'true' : 'false'}
|
|
8221
9548
|
>
|
|
8222
9549
|
<path d="M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8" />
|
|
8223
9550
|
<path d="M3 3v5h5" />
|
|
@@ -8251,11 +9578,18 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8251
9578
|
its inactive state benefits from the same "hover previews
|
|
8252
9579
|
active state" idiom R163 designed. Sibling treatment to
|
|
8253
9580
|
the nodeSize buttons at line ~6711. */
|
|
8254
|
-
|
|
9581
|
+
// R353: `group` lets the inner svg respond via group-hover —
|
|
9582
|
+
// sibling to R352 zoom buttons. Closes the chrome-strip per-
|
|
9583
|
+
// icon hover-affordance arc (zoom-out / zoom-in / reset /
|
|
9584
|
+
// fullscreen now all carry an icon-level hover gesture in
|
|
9585
|
+
// addition to the bg hover).
|
|
9586
|
+
// R400: hover translateY(-1px) lift — see reset button above for family doc.
|
|
9587
|
+
className={`group p-1.5 rounded-md border hover:-translate-y-px transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
|
|
8255
9588
|
isFullscreen
|
|
8256
9589
|
? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'
|
|
8257
9590
|
: 'hover:bg-cyan-500/5 active:bg-cyan-500/15'
|
|
8258
9591
|
}${chromePopping === 'fullscreen' ? ' anet-chrome-pop' : ''}`}
|
|
9592
|
+
data-topo-chrome-fullscreen-hover-lift="true"
|
|
8259
9593
|
style={{
|
|
8260
9594
|
borderColor: pal.containerBorder,
|
|
8261
9595
|
...(isFullscreen
|
|
@@ -8270,12 +9604,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8270
9604
|
at the reset icon above. data-topo-chrome-fullscreen-
|
|
8271
9605
|
icon attribute exposes BOTH variants (entered / exited)
|
|
8272
9606
|
for the round's stroke-width regression probe. */}
|
|
9607
|
+
{/* Round 353 / Loop: fullscreen icon (both enter + exit
|
|
9608
|
+
variants) picks up the R352 family group-hover:scale-110.
|
|
9609
|
+
Pre-R353 hovering the button only changed the bg; the
|
|
9610
|
+
icon stayed still. R353 lifts the icon 10 % on hover —
|
|
9611
|
+
same gesture vocabulary as the zoom buttons. transform-
|
|
9612
|
+
gpu hint promotes the svg to its own compositor layer
|
|
9613
|
+
for crisper edges during the scale tween. Closes the
|
|
9614
|
+
chrome-strip per-icon hover-affordance arc. */}
|
|
8273
9615
|
{isFullscreen ? (
|
|
8274
|
-
<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">
|
|
9616
|
+
<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">
|
|
8275
9617
|
<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" />
|
|
8276
9618
|
</svg>
|
|
8277
9619
|
) : (
|
|
8278
|
-
<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">
|
|
9620
|
+
<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">
|
|
8279
9621
|
<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" />
|
|
8280
9622
|
</svg>
|
|
8281
9623
|
)}
|