@sleep2agi/agent-network-dashboard 0.5.3-preview.5 → 0.5.3-preview.50
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 +32 -32
- 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 +12 -12
- package/.next/server/app/_not-found.segments/_full.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/admin.segments/_full.segment.rsc +14 -14
- package/.next/server/app/admin.segments/_head.segment.rsc +4 -4
- package/.next/server/app/admin.segments/_index.segment.rsc +7 -7
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/admin.segments/admin.segment.rsc +3 -3
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +14 -14
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +7 -7
- 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 +14 -14
- package/.next/server/app/login.segments/_full.segment.rsc +14 -14
- package/.next/server/app/login.segments/_head.segment.rsc +4 -4
- package/.next/server/app/login.segments/_index.segment.rsc +7 -7
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/login.segments/login.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/logs.segments/logs.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/messages.segments/_full.segment.rsc +14 -14
- package/.next/server/app/messages.segments/_head.segment.rsc +4 -4
- package/.next/server/app/messages.segments/_index.segment.rsc +7 -7
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/messages.segments/messages.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/node.segments/_full.segment.rsc +14 -14
- package/.next/server/app/node.segments/_head.segment.rsc +4 -4
- package/.next/server/app/node.segments/_index.segment.rsc +7 -7
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/node.segments/node.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/nodes.segments/_full.segment.rsc +14 -14
- package/.next/server/app/nodes.segments/_head.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/_index.segment.rsc +7 -7
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/nodes.segments/nodes.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/server-logs.segments/_full.segment.rsc +14 -14
- package/.next/server/app/server-logs.segments/_head.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/_index.segment.rsc +7 -7
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +3 -3
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +3 -3
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +3 -3
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +14 -14
- package/.next/server/app/settings.segments/_full.segment.rsc +14 -14
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +7 -7
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- 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 +14 -14
- package/.next/server/app/tasks.segments/_full.segment.rsc +14 -14
- package/.next/server/app/tasks.segments/_head.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/_index.segment.rsc +7 -7
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/tasks.segments/tasks.segment.rsc +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +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/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_components_0mvyi-4._.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/0i52vrjouk_iz.js +4 -0
- package/.next/static/chunks/{031p2u1xb1~uk.js → 0ouzi411y-4rz.js} +1 -1
- package/.next/static/chunks/0sht0y-y7x71m.css +2 -0
- package/.next/static/chunks/{03a4--7ncekmk.js → 0v4-5tng.uh.7.js} +2 -2
- package/.next/static/chunks/13-4ew5p22q1s.js +1 -0
- package/.next/static/chunks/13~7.4euxuws0.js +1 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/ServersDrawer.tsx +16 -3
- package/app/components/TopoGraph.tsx +1296 -67
- package/app/globals.css +55 -7
- package/package.json +4 -4
- package/scripts/p157-servers-copy-test.mjs +95 -0
- package/scripts/topo-alias-glow-test.mjs +121 -0
- package/scripts/topo-avatar-brightness-test.mjs +116 -0
- package/scripts/topo-chip-row-press-test.mjs +93 -0
- package/scripts/topo-chrome-press-fullstrip-test.mjs +105 -0
- package/scripts/topo-cluster-count-attr-test.mjs +80 -0
- package/scripts/topo-crescent-breath-test.mjs +104 -0
- package/scripts/topo-crescent-recede-test.mjs +111 -0
- package/scripts/topo-edge-badge-hover-glow-test.mjs +90 -0
- package/scripts/topo-filter-pills-press-test.mjs +96 -0
- package/scripts/topo-fleet-density-tier-test.mjs +84 -0
- package/scripts/topo-freshness-chip-fade-test.mjs +105 -0
- package/scripts/topo-fullscreen-attr-test.mjs +73 -0
- package/scripts/topo-grid-content-bottom-attr-test.mjs +72 -0
- package/scripts/topo-hub-digit-ls-test.mjs +119 -0
- package/scripts/topo-hub-halo-glow-test.mjs +96 -0
- package/scripts/topo-hub-highlight-amplify-test.mjs +139 -0
- package/scripts/topo-hub-highlight-fill-transition-test.mjs +84 -0
- package/scripts/topo-hub-highlight-glow-test.mjs +99 -0
- package/scripts/topo-hub-highlight-r-test.mjs +112 -0
- package/scripts/topo-hub-highlight-recede-test.mjs +144 -0
- package/scripts/topo-hub-highlight-theme-fill-test.mjs +83 -0
- package/scripts/topo-hub-hover-ring-glow-test.mjs +97 -0
- package/scripts/topo-hub-idle-breath-test.mjs +109 -0
- package/scripts/topo-hub-recede-test.mjs +124 -0
- package/scripts/topo-hub-spoke-glow-test.mjs +112 -0
- package/scripts/topo-layout-hover-fw-test.mjs +98 -0
- package/scripts/topo-legend-count-letter-spacing-test.mjs +108 -0
- package/scripts/topo-legend-label-fw-test.mjs +107 -0
- package/scripts/topo-nodesize-hover-fw-test.mjs +99 -0
- package/scripts/topo-orphan-box-dash-test.mjs +89 -0
- package/scripts/topo-orphan-fill-opacity-test.mjs +91 -0
- package/scripts/topo-orphan-label-italic-test.mjs +90 -0
- package/scripts/topo-pinned-aspect-test.mjs +89 -0
- package/scripts/topo-pressure-seg-motion-test.mjs +101 -0
- package/scripts/topo-recent-hot-pulse-test.mjs +102 -0
- package/scripts/topo-recent-more-fw-test.mjs +126 -0
- package/scripts/topo-recent-row-fw-test.mjs +115 -0
- package/scripts/topo-reduced-motion-attr-test.mjs +69 -0
- package/scripts/topo-reset-icon-hover-scale-test.mjs +102 -0
- package/scripts/topo-starfield-hue-test.mjs +109 -0
- package/scripts/topo-vendor-activelinks-press-test.mjs +100 -0
- package/scripts/topo-watermark-breath-test.mjs +100 -0
- package/scripts/topo-watermark-recede-test.mjs +114 -0
- package/scripts/topo-zoom-level-color-test.mjs +105 -0
- package/.next/static/chunks/024l23a_mex8..js +0 -4
- package/.next/static/chunks/09q4enn24qy1u.js +0 -1
- package/.next/static/chunks/0iwmx1skjhecc.js +0 -1
- package/.next/static/chunks/0yj_zl0p6_4ws.css +0 -2
- /package/.next/static/{3Apg3z__jP_I-Dl6oqXnF → dvI1EprNBiXLc0bYYb50p}/_buildManifest.js +0 -0
- /package/.next/static/{3Apg3z__jP_I-Dl6oqXnF → dvI1EprNBiXLc0bYYb50p}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{3Apg3z__jP_I-Dl6oqXnF → dvI1EprNBiXLc0bYYb50p}/_ssgManifest.js +0 -0
|
@@ -240,11 +240,30 @@ function FreshnessChip({ sessions }: { sessions: unknown }) {
|
|
|
240
240
|
stale-onset to direct attention. */
|
|
241
241
|
if (!stale) return null;
|
|
242
242
|
return (
|
|
243
|
+
/* Round 505 / Loop — FreshnessChip mount animation. Pre-R505 the
|
|
244
|
+
chip popped into the chip-row instantly when SWR data crossed
|
|
245
|
+
the 10s stale threshold; users saw an abrupt amber pill appear
|
|
246
|
+
mid-row. R505 adds the existing `anet-fade-in` class so the
|
|
247
|
+
chip eases through opacity 0→1 over 150ms (R51 globals.css
|
|
248
|
+
keyframe) on first appearance. The chip itself only renders
|
|
249
|
+
when stale (R275 conditional), so the fade plays exactly when
|
|
250
|
+
the stale signal first arrives — perfectly aligned with the
|
|
251
|
+
semantic. Mount-once via React reconciliation (key not used
|
|
252
|
+
since FreshnessChip is a singleton in the parent).
|
|
253
|
+
a11y respected via R29 blanket — `@media (prefers-reduced-
|
|
254
|
+
motion: reduce)` neutralizes anet-fade-in to `animation:none`
|
|
255
|
+
(globals.css line 1083-1089 includes anet-fade-in in the
|
|
256
|
+
blanket list). Reduced-motion users see the chip pop instantly,
|
|
257
|
+
same as pre-R505 behavior — no regression.
|
|
258
|
+
Pure paint-axis addition (opacity animation, no geometry),
|
|
259
|
+
bbox unchanged. data-freshness-chip-mount-fade attr exposes
|
|
260
|
+
the gate for tests. */
|
|
243
261
|
<span
|
|
244
|
-
className={`${baseClass} ${colorClass}`}
|
|
262
|
+
className={`${baseClass} ${colorClass} anet-fade-in`}
|
|
245
263
|
title={stale ? `Last sync ${sec}s ago — SWR refresh may be lagging` : `Live data · refreshes every 5s · last sync ${sec}s ago`}
|
|
246
264
|
data-freshness-chip
|
|
247
265
|
data-freshness-chip-stale={stale ? 'true' : 'false'}
|
|
266
|
+
data-freshness-chip-mount-fade="true"
|
|
248
267
|
>
|
|
249
268
|
{/* Round 272 / Loop: swap prefix word to match color state so
|
|
250
269
|
text and color point the same way. Pre-R272 the chip read
|
|
@@ -867,12 +886,19 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
867
886
|
// R63 label render + R86 hover-pin keying + #99 tooltip
|
|
868
887
|
// member listing, so all the existing group-box machinery
|
|
869
888
|
// applies uniformly to the orphan bucket too.
|
|
889
|
+
// Round 499 / Loop — surface `isOrphan` flag on the box
|
|
890
|
+
// shape so downstream renderers (label text, future polish)
|
|
891
|
+
// can apply orphan-specific typography (italic) without
|
|
892
|
+
// re-deriving the flag from key === '其他' (key matching
|
|
893
|
+
// would also catch a legitimate "其他" prefix-group, this
|
|
894
|
+
// flag is canonical from the band assignment pass).
|
|
870
895
|
return {
|
|
871
896
|
key: band.isOrphan
|
|
872
897
|
? '其他'
|
|
873
898
|
: band.members.length
|
|
874
899
|
? groupKeys[band.members[0].alias]
|
|
875
900
|
: '',
|
|
901
|
+
isOrphan: !!band.isOrphan,
|
|
876
902
|
count: band.members.length,
|
|
877
903
|
statuses: { working: w, idle: i, offline: o },
|
|
878
904
|
x: minX - GROUP_PAD,
|
|
@@ -977,7 +1003,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
977
1003
|
groupKeys,
|
|
978
1004
|
// #111: group boxes are a grid-layout feature only — radially scattered
|
|
979
1005
|
// ring nodes can't be cleanly boxed. Ring keeps the #83 prefix hue.
|
|
980
|
-
groupBoxes: [] as { key: string; count: number; statuses: { working: number; idle: number; offline: number }; x: number; y: number; w: number; h: number }[],
|
|
1006
|
+
groupBoxes: [] as { key: string; isOrphan?: boolean; count: number; statuses: { working: number; idle: number; offline: number }; x: number; y: number; w: number; h: number }[],
|
|
981
1007
|
// ring fits within VIEWBOX_H by construction (offlineRadius=325 + centre at y=330)
|
|
982
1008
|
gridContentBottom: 0,
|
|
983
1009
|
};
|
|
@@ -1959,8 +1985,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1959
1985
|
// overlays the release-pop. Matching `transform-gpu`
|
|
1960
1986
|
// promotes the layer so the scale doesn't trigger
|
|
1961
1987
|
// layout/paint thrash. Sibling change on Grid below.
|
|
1962
|
-
|
|
1963
|
-
|
|
1988
|
+
/* Round 522 / Loop — extends R521's typography-preview
|
|
1989
|
+
idiom (chrome nodeSize hover:font-medium 400 → 500) to
|
|
1990
|
+
the Ring/Grid layout toggle's inactive variant. Pre-
|
|
1991
|
+
R522 the inactive Ring/Grid had `hover:text-cyan-300
|
|
1992
|
+
hover:bg-cyan-500/5` (R270 color + bg previews of the
|
|
1993
|
+
active state) but no typography preview — the active
|
|
1994
|
+
variant uses `font-medium` (fw 500), inactive sat at
|
|
1995
|
+
default fw 400 even on hover. R522 adds `hover:font-
|
|
1996
|
+
medium` to the inactive Ring/Grid so the rest-vs-hover
|
|
1997
|
+
transition previews the typography state the click
|
|
1998
|
+
would commit to, matching the click commits's locked
|
|
1999
|
+
weight.
|
|
2000
|
+
font-weight 150ms appended to the transition list
|
|
2001
|
+
matching the existing 150ms color/bg cadence at this
|
|
2002
|
+
button — when hover lifts color (gray-400 → cyan-300)
|
|
2003
|
+
+ bg (transparent → cyan-500/5) + fw (400 → 500), all
|
|
2004
|
+
3 ease at the same 150ms beat.
|
|
2005
|
+
Hover-fw family extension (6 anchors): R416/R420/R425/
|
|
2006
|
+
R520/R521/R522. R522 closes the chrome toggle group
|
|
2007
|
+
typography preview at the last remaining toggle —
|
|
2008
|
+
layout (Ring/Grid). After R521 (nodeSize) + R522
|
|
2009
|
+
(layout), every multi-state chrome toggle has hover-
|
|
2010
|
+
fw preview on its inactive variant.
|
|
2011
|
+
data-topo-chrome-layout-hover-preview-fw="500" attr
|
|
2012
|
+
on inactive button exposes the polish for tests. */
|
|
2013
|
+
className={`px-2.5 py-1 focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide active:scale-95 transform-gpu ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}
|
|
2014
|
+
data-topo-chrome-layout-hover-preview-fw={layout === 'ring' ? null : '500'}
|
|
2015
|
+
style={{ transition: 'background-color 150ms ease, color 150ms ease, letter-spacing 200ms ease-out, transform 150ms ease-out, font-weight 150ms ease' }}
|
|
1964
2016
|
>
|
|
1965
2017
|
Ring
|
|
1966
2018
|
</button>
|
|
@@ -1982,7 +2034,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1982
2034
|
// R492 sibling — Grid button picks up active:scale-95
|
|
1983
2035
|
// press feedback + transform in transition list. Same
|
|
1984
2036
|
// vocabulary as Ring above.
|
|
1985
|
-
|
|
2037
|
+
/* Round 522 sibling — Grid button mirrors Ring above:
|
|
2038
|
+
inactive variant gains `hover:font-medium` typography
|
|
2039
|
+
preview + font-weight 150ms in inline transition list.
|
|
2040
|
+
Same idiom, same family (R522 chrome layout). */
|
|
2041
|
+
className={`px-2.5 py-1 border-l focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset hover:tracking-wide active:scale-95 transform-gpu ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}
|
|
2042
|
+
data-topo-chrome-layout-hover-preview-fw={layout === 'grid' ? null : '500'}
|
|
1986
2043
|
/* Round 268 / Loop: Grid button's left border (the
|
|
1987
2044
|
internal divider between Ring and Grid) picks up
|
|
1988
2045
|
pal.containerBorder, matching the wrapper change at
|
|
@@ -1995,7 +2052,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1995
2052
|
keeps R268's theme-toggle smoothness intact.
|
|
1996
2053
|
R492 adds `transform 150ms ease-out` so active:scale-95
|
|
1997
2054
|
eases smoothly. */
|
|
1998
|
-
style={{ borderColor: pal.containerBorder, transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out, transform 150ms ease-out' }}
|
|
2055
|
+
style={{ borderColor: pal.containerBorder, transition: 'background-color 150ms ease, color 150ms ease, border-color 200ms ease-out, letter-spacing 200ms ease-out, transform 150ms ease-out, font-weight 150ms ease' }}
|
|
1999
2056
|
>
|
|
2000
2057
|
Grid
|
|
2001
2058
|
</button>
|
|
@@ -2112,9 +2169,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2112
2169
|
// to R355 filter pin pill inner-span hover-brighten.
|
|
2113
2170
|
// Hover-brighten family extends from filter pills to
|
|
2114
2171
|
// chip-row chips at the inner-span scope.
|
|
2172
|
+
// Round 494 / Loop — chip-row working chip joins the
|
|
2173
|
+
// active:scale-95 press-feedback family (R492 Ring/Grid +
|
|
2174
|
+
// R493 chrome-strip rest). Gated on the clickable branch
|
|
2175
|
+
// (workingCount > 0) — when the chip is a placeholder
|
|
2176
|
+
// at count=0, scale-95 stays off to match the existing
|
|
2177
|
+
// R398 hover-lift conditional. Composes with hover:-
|
|
2178
|
+
// translate-y-px for the same lift-and-compress
|
|
2179
|
+
// tactile signature R493 brought to reset/fullscreen.
|
|
2115
2180
|
className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
|
|
2116
2181
|
workingCount > 0
|
|
2117
|
-
? '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'
|
|
2182
|
+
? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30 hover:-translate-y-px active:scale-95'
|
|
2118
2183
|
: 'bg-green-500/10 text-green-300 border-green-500/20'
|
|
2119
2184
|
}`}
|
|
2120
2185
|
data-chip-hover-lift={workingCount > 0 ? 'true' : 'false'}
|
|
@@ -2214,9 +2279,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2214
2279
|
same digit-jitter physics on count crossings). */
|
|
2215
2280
|
// R398: hover translate-y lift on clickable variant — see working chip above.
|
|
2216
2281
|
// R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
|
|
2282
|
+
// R494 sibling — online chip joins the active:scale-95 press
|
|
2283
|
+
// family (gated on onlineNodes.length > 0 clickable branch,
|
|
2284
|
+
// same conditional pattern as the working chip above).
|
|
2217
2285
|
className={`group tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors transition-transform duration-200 ease-out transform-gpu ${
|
|
2218
2286
|
onlineNodes.length > 0
|
|
2219
|
-
? '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'
|
|
2287
|
+
? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95'
|
|
2220
2288
|
: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'
|
|
2221
2289
|
}`}
|
|
2222
2290
|
data-chip-hover-lift={onlineNodes.length > 0 ? 'true' : 'false'}
|
|
@@ -2491,7 +2559,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2491
2559
|
// R355: `group` lets the inner opacity-70 spans (prefix
|
|
2492
2560
|
// `filter:` + count `· N`) brighten to 100 % on pill hover.
|
|
2493
2561
|
// Sibling treatment on group + vendor pills below.
|
|
2494
|
-
|
|
2562
|
+
// R495 — filter pills (3 sibling `group` variants) join the
|
|
2563
|
+
// active:scale-95 press-feedback family. R490's !important
|
|
2564
|
+
// transition list on .anet-topo-chip-focus already covers
|
|
2565
|
+
// transform, so just appending active:scale-95 to the
|
|
2566
|
+
// className wires the press tactile in one token. Compound
|
|
2567
|
+
// with R400-era hover:-translate-y-px gives lift-and-compress.
|
|
2568
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2495
2569
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2496
2570
|
onClick={() => setPinnedStatus(null)}
|
|
2497
2571
|
style={{
|
|
@@ -2555,7 +2629,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2555
2629
|
data-filter-match-count={matchCount}
|
|
2556
2630
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2557
2631
|
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2558
|
-
|
|
2632
|
+
// R495 — filter pills (3 sibling `group` variants) join the
|
|
2633
|
+
// active:scale-95 press-feedback family. R490's !important
|
|
2634
|
+
// transition list on .anet-topo-chip-focus already covers
|
|
2635
|
+
// transform, so just appending active:scale-95 to the
|
|
2636
|
+
// className wires the press tactile in one token. Compound
|
|
2637
|
+
// with R400-era hover:-translate-y-px gives lift-and-compress.
|
|
2638
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2559
2639
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2560
2640
|
onClick={() => setPinnedGroup(null)}
|
|
2561
2641
|
style={{
|
|
@@ -2621,7 +2701,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2621
2701
|
data-filter-match-count={matchCount}
|
|
2622
2702
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2623
2703
|
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2624
|
-
|
|
2704
|
+
// R495 — filter pills (3 sibling `group` variants) join the
|
|
2705
|
+
// active:scale-95 press-feedback family. R490's !important
|
|
2706
|
+
// transition list on .anet-topo-chip-focus already covers
|
|
2707
|
+
// transform, so just appending active:scale-95 to the
|
|
2708
|
+
// className wires the press tactile in one token. Compound
|
|
2709
|
+
// with R400-era hover:-translate-y-px gives lift-and-compress.
|
|
2710
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2625
2711
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
|
|
2626
2712
|
onClick={() => setPinnedVendor(null)}
|
|
2627
2713
|
style={{
|
|
@@ -2684,7 +2770,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2684
2770
|
data-filter-match-count={link.count}
|
|
2685
2771
|
data-filter-match-aliases={`${link.from},${link.to}`}
|
|
2686
2772
|
data-active-filter-edge-hot={isHot ? 'true' : 'false'}
|
|
2687
|
-
|
|
2773
|
+
// R495 sibling — 4th filter pill (no `group` prefix variant)
|
|
2774
|
+
// joins active:scale-95 press family alongside the 3 group
|
|
2775
|
+
// variants above. Same recipe.
|
|
2776
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px active:scale-95 transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2688
2777
|
title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
|
|
2689
2778
|
onClick={() => setPinnedEdgeKey(null)}
|
|
2690
2779
|
style={{
|
|
@@ -3034,7 +3123,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3034
3123
|
// — sibling to R355 filter-pill prefix/suffix + R414
|
|
3035
3124
|
// chip-row unit brighten. Closes the inner-span
|
|
3036
3125
|
// hover-brighten family at the vendor chip surface.
|
|
3037
|
-
|
|
3126
|
+
// R496 — vendor letter chip joins active:scale-95 press
|
|
3127
|
+
// family. Last vendor-row clickable joining the family
|
|
3128
|
+
// R495 cashed via R490's transition-cascade dividend.
|
|
3129
|
+
// Same compound w/ R401 hover-lift idiom — lift-and-
|
|
3130
|
+
// compress on press, springs back on release.
|
|
3131
|
+
className="group tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu hover:-translate-y-px active:scale-95"
|
|
3038
3132
|
data-vendor-letter={v.initial}
|
|
3039
3133
|
data-vendor-letter-count={v.count}
|
|
3040
3134
|
data-vendor-letter-hover-lift="true"
|
|
@@ -3272,9 +3366,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3272
3366
|
data-chip-hover-lift attr exposes the lift surface
|
|
3273
3367
|
state ('true' clickable, 'false' empty) for tests. */
|
|
3274
3368
|
// R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
|
|
3369
|
+
// R496 — active-links chip joins active:scale-95 press
|
|
3370
|
+
// family. Sibling to working+online chips (R494). Gated
|
|
3371
|
+
// on `isInteractive` (flowLinks.length > 0) — same R399
|
|
3372
|
+
// conditional pattern used for hover-lift.
|
|
3275
3373
|
className={`group tabular-nums font-medium hidden sm:inline px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-transform duration-200 ease-out transform-gpu ${
|
|
3276
3374
|
isInteractive
|
|
3277
|
-
? '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'
|
|
3375
|
+
? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30 hover:-translate-y-px active:scale-95'
|
|
3278
3376
|
: 'bg-gray-500/10 text-gray-400 border-gray-500/20'
|
|
3279
3377
|
}`}
|
|
3280
3378
|
data-chip-hover-lift={isInteractive ? 'true' : 'false'}
|
|
@@ -3521,6 +3619,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3521
3619
|
on the canvas root for non-visual consumers.
|
|
3522
3620
|
Composed from existing onlineNodes / workingCount /
|
|
3523
3621
|
offlineNodes / flowLinks — no new state. */
|
|
3622
|
+
/* Round 502 / Loop — categorical density-tier paired with the
|
|
3623
|
+
R469 numeric counts. data-topo-fleet-density-tier classifies
|
|
3624
|
+
the fleet size into 5 buckets so external consumers (CSS
|
|
3625
|
+
selectors, Playwright probes, future density-conditional
|
|
3626
|
+
polish gates like R109 dense-label collapse at 16+ nodes)
|
|
3627
|
+
can branch on a stable tier name without re-deriving the
|
|
3628
|
+
threshold logic from the raw numeric. Buckets:
|
|
3629
|
+
'empty' — onlineNodes.length === 0
|
|
3630
|
+
'sparse' — 1-3 nodes
|
|
3631
|
+
'normal' — 4-15 nodes
|
|
3632
|
+
'dense' — 16-30 nodes (matches R109 collapse gate)
|
|
3633
|
+
'very-dense' — 31+ nodes
|
|
3634
|
+
Picks the gate boundaries that already drive CONDITIONAL
|
|
3635
|
+
RENDER decisions elsewhere (R109 denseLayout = >16, R110
|
|
3636
|
+
plain-text fallback) so the tier name is semantically
|
|
3637
|
+
aligned with the visual mode the canvas already switches
|
|
3638
|
+
to. Composed from existing onlineNodes — no new state.
|
|
3639
|
+
12th attr in the canvas state surface set (R462/R466/R467/
|
|
3640
|
+
R469×4/R471×2/R487/R488/R502). 12 attrs covers: build
|
|
3641
|
+
identity, transient/sticky inspection modes, fleet split
|
|
3642
|
+
numerics, fleet density tier, canvas layout/theme, canvas
|
|
3643
|
+
zoom, hover identity. A test harness can snapshot the
|
|
3644
|
+
full canvas state with 12 getAttribute calls. */
|
|
3645
|
+
data-topo-fleet-density-tier={
|
|
3646
|
+
onlineNodes.length === 0 ? 'empty' :
|
|
3647
|
+
onlineNodes.length <= 3 ? 'sparse' :
|
|
3648
|
+
onlineNodes.length <= 15 ? 'normal' :
|
|
3649
|
+
onlineNodes.length <= 30 ? 'dense' :
|
|
3650
|
+
'very-dense'
|
|
3651
|
+
}
|
|
3524
3652
|
data-topo-online-count={onlineNodes.length}
|
|
3525
3653
|
data-topo-working-count={workingCount}
|
|
3526
3654
|
data-topo-offline-count={offlineNodes.length}
|
|
@@ -3586,6 +3714,119 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3586
3714
|
categorical) — separate dedicated attrs if/when needed.
|
|
3587
3715
|
Root svg attribute set now 11 attrs total. */
|
|
3588
3716
|
data-topo-hovered-alias={hoveredAlias ?? ''}
|
|
3717
|
+
/* Round 504 / Loop — categorical pin-aspect attr paired with
|
|
3718
|
+
R467 any-pinned boolean and R488 hovered-alias identity.
|
|
3719
|
+
Pre-R504 the canvas state surface set told tests WHETHER
|
|
3720
|
+
any pin was active (R467 boolean) but tests had to enumerate
|
|
3721
|
+
4 individual state vars to determine WHICH pin axis fired:
|
|
3722
|
+
pinnedStatus legend-row status filter
|
|
3723
|
+
pinnedGroup prefix-cluster lock
|
|
3724
|
+
pinnedVendor vendor-chip filter
|
|
3725
|
+
pinnedEdgeKey edge-focus
|
|
3726
|
+
R504 surfaces the active aspect as a single categorical
|
|
3727
|
+
attribute: data-topo-pinned-aspect ∈
|
|
3728
|
+
'none' no pin active
|
|
3729
|
+
'status' pinnedStatus only
|
|
3730
|
+
'group' pinnedGroup only
|
|
3731
|
+
'vendor' pinnedVendor only
|
|
3732
|
+
'edge' pinnedEdgeKey only
|
|
3733
|
+
'multi' 2 or more pins active simultaneously
|
|
3734
|
+
('multi' covers cross-cutting filters — e.g. user pins
|
|
3735
|
+
status='working' AND vendor='claude' simultaneously to
|
|
3736
|
+
narrow the canvas. Each pin axis is independently
|
|
3737
|
+
dismissable via Esc / individual chip click, so multi
|
|
3738
|
+
states are reachable and worth surfacing as a distinct
|
|
3739
|
+
tier.)
|
|
3740
|
+
13th attr in the canvas state surface set after R502.
|
|
3741
|
+
Composed from 4 existing state vars — no new state. */
|
|
3742
|
+
/* Round 512 / Loop — 14th canvas state attr. groupBoxes.length
|
|
3743
|
+
surfaces the count of cluster boxes currently rendered in
|
|
3744
|
+
grid layout (always 0 in ring). Paired with R502 categorical
|
|
3745
|
+
density tier + R469 fleet numerics for a complete cluster-
|
|
3746
|
+
cardinality surface:
|
|
3747
|
+
R469 data-topo-online-count node-count
|
|
3748
|
+
R502 data-topo-fleet-density-tier categorical
|
|
3749
|
+
R512 data-topo-cluster-count cluster-count ← this round
|
|
3750
|
+
Use cases:
|
|
3751
|
+
- Playwright: assert orphan-band existence by
|
|
3752
|
+
`cluster-count === N + 1` vs prefix-only `=== N`
|
|
3753
|
+
- external CSS: `[data-topo-cluster-count='1']` to apply
|
|
3754
|
+
single-cluster grid-specific layout adjustments
|
|
3755
|
+
- future polish gates: cluster-count > N could trigger
|
|
3756
|
+
dense-grid mode
|
|
3757
|
+
Composed from existing `groupBoxes.length` — no new state.
|
|
3758
|
+
Always renders (0 in ring layout, N in grid), so tests can
|
|
3759
|
+
rely on attribute presence + value. */
|
|
3760
|
+
data-topo-cluster-count={groupBoxes.length}
|
|
3761
|
+
/* Round 513 / Loop — 15th canvas state attr. Surfaces the
|
|
3762
|
+
user's prefers-reduced-motion preference directly on the
|
|
3763
|
+
root SVG so external CSS / Playwright tests can branch on
|
|
3764
|
+
a11y state without re-reading the media query.
|
|
3765
|
+
reducedMotion is already in component scope (R29 a11y
|
|
3766
|
+
blanket reads it via a useEffect listener); R513 just
|
|
3767
|
+
exposes it as a stable attribute handle.
|
|
3768
|
+
Use cases:
|
|
3769
|
+
- Playwright: assert reduced-motion gates from one attr
|
|
3770
|
+
read instead of mocking media-query state per test
|
|
3771
|
+
- External CSS hooks: `[data-topo-prefers-reduced-motion=
|
|
3772
|
+
"true"]` to apply paint-only overrides (e.g. mute
|
|
3773
|
+
hover glows entirely on a11y instead of just
|
|
3774
|
+
disabling transitions)
|
|
3775
|
+
- Future polish rounds: any motion-gated render can
|
|
3776
|
+
read this attr server-side without the media-query
|
|
3777
|
+
hydration mismatch risk
|
|
3778
|
+
'true' / 'false' string values (consistent with R466/R467
|
|
3779
|
+
boolean attrs). */
|
|
3780
|
+
data-topo-prefers-reduced-motion={reducedMotion ? 'true' : 'false'}
|
|
3781
|
+
/* Round 515 / Loop — 16th canvas state attr. Surfaces the
|
|
3782
|
+
fullscreen-mode state directly on root SVG so external
|
|
3783
|
+
consumers don't have to traverse the chrome strip's
|
|
3784
|
+
`data-topo-chrome-fullscreen-active` button attr (which
|
|
3785
|
+
measures the BUTTON state, not the canvas state — they
|
|
3786
|
+
agree, but reading from the root is semantically cleaner
|
|
3787
|
+
for canvas-state probes).
|
|
3788
|
+
Composed from existing isFullscreen React state (R103
|
|
3789
|
+
fullscreen toggle).
|
|
3790
|
+
Use cases:
|
|
3791
|
+
- Playwright: assert canvas mode in one attr read
|
|
3792
|
+
(paired with R471 data-topo-layout for ring/grid +
|
|
3793
|
+
R487 data-topo-zoom for zoom level + R513 reduced-
|
|
3794
|
+
motion for a11y mode = 4-axis canvas-mode probe)
|
|
3795
|
+
- External CSS: `[data-topo-fullscreen="true"]` to
|
|
3796
|
+
apply fullscreen-only paint adjustments outside the
|
|
3797
|
+
React tree (e.g. body-level scrollbar hide)
|
|
3798
|
+
'true' / 'false' string values (consistent with R466/
|
|
3799
|
+
R467/R513 boolean attrs). */
|
|
3800
|
+
data-topo-fullscreen={isFullscreen ? 'true' : 'false'}
|
|
3801
|
+
/* Round 516 / Loop — 17th canvas state attr. Surfaces the
|
|
3802
|
+
grid layout's content-bottom y-coordinate so tests can
|
|
3803
|
+
verify grid content doesn't extend past the viewBox or
|
|
3804
|
+
collide with chrome elements positioned below the canvas.
|
|
3805
|
+
Composed from existing gridContentBottom derived state
|
|
3806
|
+
(computed at line ~915 from gy0 + totalRows * cellH + 8).
|
|
3807
|
+
In ring layout, gridContentBottom is 0 (no grid). In grid
|
|
3808
|
+
layout it's the actual pixel y-coordinate where the
|
|
3809
|
+
cluster bands end.
|
|
3810
|
+
Use cases:
|
|
3811
|
+
- Playwright: assert grid layout doesn't exceed viewBox
|
|
3812
|
+
height (680) without re-computing the layout math
|
|
3813
|
+
- External CSS: `[data-topo-grid-content-bottom='0']` to
|
|
3814
|
+
distinguish ring-mode (no grid content) from grid-mode
|
|
3815
|
+
in CSS without parsing layout attr
|
|
3816
|
+
- Future polish gates: if cluster count grows large
|
|
3817
|
+
enough to push grid bottom past viewBox, can trigger
|
|
3818
|
+
a 'compact' mode automatically */
|
|
3819
|
+
data-topo-grid-content-bottom={gridContentBottom}
|
|
3820
|
+
data-topo-pinned-aspect={(() => {
|
|
3821
|
+
const aspects: string[] = [];
|
|
3822
|
+
if (pinnedStatus) aspects.push('status');
|
|
3823
|
+
if (pinnedGroup) aspects.push('group');
|
|
3824
|
+
if (pinnedVendor) aspects.push('vendor');
|
|
3825
|
+
if (pinnedEdgeKey) aspects.push('edge');
|
|
3826
|
+
if (aspects.length === 0) return 'none';
|
|
3827
|
+
if (aspects.length === 1) return aspects[0];
|
|
3828
|
+
return 'multi';
|
|
3829
|
+
})()}
|
|
3589
3830
|
/* Round 466 / Loop — aggregate hover signal on the root SVG.
|
|
3590
3831
|
Exposes a single boolean `data-topo-any-hover` that
|
|
3591
3832
|
reflects whether ANY hover state in the topology is
|
|
@@ -3828,7 +4069,38 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3828
4069
|
const x = ((seed * 13) % 1000);
|
|
3829
4070
|
const y = ((seed * 7) % 680);
|
|
3830
4071
|
const r = (i % 3 === 0) ? 1.2 : 0.7;
|
|
3831
|
-
|
|
4072
|
+
/* Round 523 / Loop — 配色 family extension to a 3rd anchor.
|
|
4073
|
+
Pre-R523 all 14 starfield dots painted at the same
|
|
4074
|
+
hardcoded `#a5b4fc` (indigo-300). The starfield's role
|
|
4075
|
+
is atmospheric depth (R45, R291 comment), but a flat
|
|
4076
|
+
single-hue field reads more like a regular dot grid
|
|
4077
|
+
than a star field — real starlight has color
|
|
4078
|
+
temperature variation (blue-white hot stars / yellow
|
|
4079
|
+
sun-like / cool red).
|
|
4080
|
+
R523 cycles a 3-color deterministic rotation based on
|
|
4081
|
+
`i % 3`:
|
|
4082
|
+
i % 3 === 0 → #a5b4fc indigo-300 (original, cool)
|
|
4083
|
+
i % 3 === 1 → #67e8f9 cyan-300 (cyber accent, hot)
|
|
4084
|
+
i % 3 === 2 → #cbd5e1 slate-300 (neutral, warm white)
|
|
4085
|
+
All three hues sit inside the cyber theme's palette
|
|
4086
|
+
family (indigo / cyan / slate) so the starfield reads
|
|
4087
|
+
varied-but-coherent rather than rainbow. At opacity
|
|
4088
|
+
0.5 (parent <g>) * 0.35-0.50 (per-dot) the temperature
|
|
4089
|
+
shifts are gentle but perceptible — closes the gap
|
|
4090
|
+
between 'dot grid' and 'star field'.
|
|
4091
|
+
配色 family extension (3 anchors): R509/R510 hub-
|
|
4092
|
+
highlight cross-theme fill + R523 starfield color
|
|
4093
|
+
temperature variation. Light theme unaffected
|
|
4094
|
+
(starfield gated `!isLight` so light theme stays
|
|
4095
|
+
clean per R45's original 'white surface stays clean'
|
|
4096
|
+
intent).
|
|
4097
|
+
Deterministic on `i` — no JS hydration mismatch,
|
|
4098
|
+
same SSR/client output. data-topo-starfield-dot-hue
|
|
4099
|
+
attr exposes the resolved hue category for tests. */
|
|
4100
|
+
const hues = ['#a5b4fc', '#67e8f9', '#cbd5e1'] as const;
|
|
4101
|
+
const hueNames = ['indigo', 'cyan', 'slate'] as const;
|
|
4102
|
+
const hueIdx = i % 3;
|
|
4103
|
+
return <circle key={i} cx={x} cy={y} r={r} fill={hues[hueIdx]} opacity={0.35 + (i % 4) * 0.05} data-topo-starfield-dot={i} data-topo-starfield-dot-hue={hueNames[hueIdx]} />;
|
|
3832
4104
|
})}
|
|
3833
4105
|
</g>
|
|
3834
4106
|
)}
|
|
@@ -4366,8 +4638,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4366
4638
|
data-topo-hub-spoke-stroke-width={spokeStrokeWidth}
|
|
4367
4639
|
data-topo-hub-spoke-stroke-width-active="2.25"
|
|
4368
4640
|
data-topo-hub-spoke-linecap="round"
|
|
4641
|
+
/* Round 533 / Loop — extends drop-shadow visual-polish
|
|
4642
|
+
family to a 9th anchor: hub spokes gain filter:drop-
|
|
4643
|
+
shadow glow on hub-hover. Subtle 1.5px cyan/teal blur
|
|
4644
|
+
applied across ALL spokes simultaneously when the
|
|
4645
|
+
user hovers the hub — the network mesh visually
|
|
4646
|
+
"lights up" in response to focal attention. Sibling
|
|
4647
|
+
to R476 hub-digit + R532 hub-highlight glow at the
|
|
4648
|
+
same gate (hoveredHub && !reducedMotion); together
|
|
4649
|
+
the three anchors (digit + highlight disc + spokes)
|
|
4650
|
+
form a unified focal-cluster glow that signals
|
|
4651
|
+
"you're focused on the hub" across geometry,
|
|
4652
|
+
paint, and mesh-extent axes.
|
|
4653
|
+
Theme-aware glow palette matches the spoke stroke
|
|
4654
|
+
family:
|
|
4655
|
+
light: rgba(13, 148, 136, 0.4) teal-600
|
|
4656
|
+
cyber: rgba(34, 211, 238, 0.4) cyan-400
|
|
4657
|
+
0.4 alpha keeps the glow subtle across N spokes
|
|
4658
|
+
(30+ at peak fleet sizes) — loud bloom across many
|
|
4659
|
+
edges would compete with the focal cluster itself.
|
|
4660
|
+
1.5px blur is conservative; tuned so each spoke
|
|
4661
|
+
gains a faint outer halo rather than a wide bloom.
|
|
4662
|
+
filter is paint-only; bbox unchanged; existing
|
|
4663
|
+
R241 transition list extends to 'filter 250ms
|
|
4664
|
+
ease-out' matching the spoke transition cadence
|
|
4665
|
+
(250ms, distinct from the 200ms hub-cluster
|
|
4666
|
+
cadence — spokes ease slightly slower since they
|
|
4667
|
+
respond to per-alias state, not just hub state).
|
|
4668
|
+
data-topo-hub-spoke-glow attr exposes the gate
|
|
4669
|
+
state for tests. */
|
|
4670
|
+
data-topo-hub-spoke-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
4369
4671
|
style={{
|
|
4370
|
-
transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out',
|
|
4672
|
+
transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out, filter 250ms ease-out',
|
|
4673
|
+
filter: !reducedMotion && hoveredHub
|
|
4674
|
+
? (isLight
|
|
4675
|
+
? 'drop-shadow(0 0 1.5px rgba(13, 148, 136, 0.4))'
|
|
4676
|
+
: 'drop-shadow(0 0 1.5px rgba(34, 211, 238, 0.4))')
|
|
4677
|
+
: undefined,
|
|
4371
4678
|
...(isActiveSpoke ? {} : {
|
|
4372
4679
|
animationDelay: `${-(idx * 0.25)}s`,
|
|
4373
4680
|
// CSS var consumed by `.anet-topo-spoke-flow`
|
|
@@ -4515,12 +4822,75 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4515
4822
|
// pinned → fill 0.08 / 0.13, stroke 3 px (locked)
|
|
4516
4823
|
// hovered → fill 0.05 / 0.09, stroke 2 px (inspecting)
|
|
4517
4824
|
// idle → fill 0.025 / 0.045, stroke 1.5 px dashed
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4825
|
+
/* Round 506 / Loop — category-differentiation family
|
|
4826
|
+
3rd anchor. Orphan band rest-state fillOpacity drops
|
|
4827
|
+
slightly below prefix-group rest (0.025/0.045 →
|
|
4828
|
+
0.015/0.028). Adds a 3rd independent paint
|
|
4829
|
+
differentiator to the orphan visual signature:
|
|
4830
|
+
R499 fontStyle: italic (label text)
|
|
4831
|
+
R503 '3 6' dash pattern (rect stroke)
|
|
4832
|
+
R506 lower fillOpacity (rect fill) ← this round
|
|
4833
|
+
Three independent channels (typography + stroke
|
|
4834
|
+
pattern + fill density) collectively encode the
|
|
4835
|
+
catchall semantic at rest. Pin and hover branches
|
|
4836
|
+
UNCHANGED (still 0.05/0.09 hover, 0.08/0.13 pin) —
|
|
4837
|
+
orphan box gets full visual emphasis on inspection
|
|
4838
|
+
identical to prefix groups; the differentiation
|
|
4839
|
+
lives ONLY in the unsolicited rest state. The
|
|
4840
|
+
~40% drop (0.045 → 0.028 cyber, 0.025 → 0.015
|
|
4841
|
+
light) is subtle enough that the orphan box stays
|
|
4842
|
+
visible at rest, just quieter — matches the
|
|
4843
|
+
"misc bucket, less attention-deserving" semantic
|
|
4844
|
+
without losing the visual anchor.
|
|
4845
|
+
Pure paint axis; bbox unchanged; R51 SVG sentinel
|
|
4846
|
+
safety untouched (overlap-test gates to g[data-
|
|
4847
|
+
node], cluster rect invisible to it).
|
|
4848
|
+
data-group-box-fill-opacity attr surfaces the
|
|
4849
|
+
resolved value for tests. */
|
|
4850
|
+
fillOpacity={
|
|
4851
|
+
isPinned ? (isLight ? 0.08 : 0.13)
|
|
4852
|
+
: isHovered ? (isLight ? 0.05 : 0.09)
|
|
4853
|
+
: box.isOrphan ? (isLight ? 0.015 : 0.028)
|
|
4854
|
+
: (isLight ? 0.025 : 0.045)
|
|
4855
|
+
}
|
|
4856
|
+
data-group-box-fill-opacity={
|
|
4857
|
+
isPinned ? (isLight ? 0.08 : 0.13)
|
|
4858
|
+
: isHovered ? (isLight ? 0.05 : 0.09)
|
|
4859
|
+
: box.isOrphan ? (isLight ? 0.015 : 0.028)
|
|
4860
|
+
: (isLight ? 0.025 : 0.045)
|
|
4861
|
+
}
|
|
4521
4862
|
stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}
|
|
4522
4863
|
strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}
|
|
4523
|
-
|
|
4864
|
+
/* Round 503 / Loop — category-differentiation family
|
|
4865
|
+
2nd anchor (R499 = orphan label italic, 1st anchor).
|
|
4866
|
+
Orphan band rest-state strokeDasharray switches from
|
|
4867
|
+
'6 6' (prefix-group default) to '3 6' (tighter
|
|
4868
|
+
dashes). Pre-R503 the rect dash pattern was uniform
|
|
4869
|
+
across all bands; combined with R499's italic label,
|
|
4870
|
+
the orphan box now has TWO independent paint/
|
|
4871
|
+
typography differentiators at rest:
|
|
4872
|
+
R499 fontStyle: italic (label text)
|
|
4873
|
+
R503 '3 6' dash pattern (rect stroke) ← this round
|
|
4874
|
+
The R85 marching-ants animation continues to work
|
|
4875
|
+
with the new dash size (uses --march-dur custom
|
|
4876
|
+
property, dash-length-agnostic) — orphan's ants
|
|
4877
|
+
just have a different visual rhythm than prefix-
|
|
4878
|
+
group ants, reinforcing the catchall semantic.
|
|
4879
|
+
Pinned/hovered orphan still gets 'none' (solid
|
|
4880
|
+
stroke) so the hover/pin affordance is preserved
|
|
4881
|
+
— the differentiation lives ONLY in the rest
|
|
4882
|
+
state, never blocking inspection.
|
|
4883
|
+
Pure paint axis; no geometry change; bbox unchanged
|
|
4884
|
+
(strokeDasharray is paint-only). R51 SVG sentinel
|
|
4885
|
+
safety untouched (overlap-test gates to g[data-
|
|
4886
|
+
node], this cluster rect is invisible to it).
|
|
4887
|
+
data-group-box-orphan attr surfaces the gate for
|
|
4888
|
+
tests + future polish references. */
|
|
4889
|
+
strokeDasharray={
|
|
4890
|
+
(isPinned || isHovered) ? 'none' :
|
|
4891
|
+
box.isOrphan ? '3 6' : '6 6'
|
|
4892
|
+
}
|
|
4893
|
+
data-group-box-orphan={box.isOrphan ? 'true' : 'false'}
|
|
4524
4894
|
/* Round 380 / Loop: cluster box stroke gets round
|
|
4525
4895
|
linecap + round linejoin. Sibling SVG stroke-
|
|
4526
4896
|
softening polish to R378 flow-rail linecap + R379
|
|
@@ -4865,16 +5235,39 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4865
5235
|
ease-out' alongside the existing fill/ls/fw/opacity
|
|
4866
5236
|
200ms tweens. */
|
|
4867
5237
|
data-group-label-glow={isPinned ? 'true' : 'false'}
|
|
5238
|
+
/* Round 499 / Loop — orphan band "其他" label gets
|
|
5239
|
+
fontStyle: italic to visually distinguish the
|
|
5240
|
+
catchall from real prefix-group bands. Pre-R499
|
|
5241
|
+
the orphan box label rendered identically to
|
|
5242
|
+
prefix-group labels (Hero D fontSize=9, fw=700,
|
|
5243
|
+
opacity 0.55 rest), so users had to read the
|
|
5244
|
+
literal text "其他" to identify the catchall. R499
|
|
5245
|
+
adds a pure-typography differentiation: italic
|
|
5246
|
+
signals "this is the misc bucket, not a real
|
|
5247
|
+
named group" while preserving full opacity
|
|
5248
|
+
affordance on hover/pin — the orphan box stays
|
|
5249
|
+
equally inspectable, just typographically marked
|
|
5250
|
+
as a different category. No geometry change
|
|
5251
|
+
(italic shifts glyph slant within the same bbox),
|
|
5252
|
+
no opacity loss, no behavior change. Sibling to
|
|
5253
|
+
R432 letter-spacing 3-tier + R457 pin fw-lift +
|
|
5254
|
+
R479 pin drop-shadow at the group-label scope.
|
|
5255
|
+
Falls under 配色 / 节点视觉 themes per the prompt;
|
|
5256
|
+
advances the "信息密度" axis by encoding
|
|
5257
|
+
category-distinction into a single typography
|
|
5258
|
+
channel without adding visual chrome. */
|
|
4868
5259
|
style={{
|
|
4869
5260
|
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
|
|
4870
5261
|
letterSpacing: isPinned ? '0.5px' :
|
|
4871
5262
|
isHovered ? '0.25px' : '0px',
|
|
5263
|
+
fontStyle: box.isOrphan ? 'italic' : undefined,
|
|
4872
5264
|
filter: isPinned
|
|
4873
5265
|
? `drop-shadow(0 0 3px ${pal.legendAccent}80)`
|
|
4874
5266
|
: undefined,
|
|
4875
5267
|
}}
|
|
4876
5268
|
data-group-label={box.key}
|
|
4877
5269
|
data-group-label-pinned={isPinned ? 'true' : 'false'}
|
|
5270
|
+
data-group-label-orphan={box.isOrphan ? 'true' : 'false'}
|
|
4878
5271
|
>
|
|
4879
5272
|
{box.key}
|
|
4880
5273
|
{/* Round 19 / Loop: member-count chip. Inline tspan stays
|
|
@@ -5961,11 +6354,48 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5961
6354
|
data-edge-badge-opacity-rest={isLight ? 0.95 : 0.85}
|
|
5962
6355
|
data-edge-badge-opacity-hover="1"
|
|
5963
6356
|
data-edge-badge-opacity-active="1"
|
|
5964
|
-
data-edge-badge-glow={isHot ? '
|
|
6357
|
+
data-edge-badge-glow={(isHoveredEdge || isPinned) ? 'hover' : isHot ? 'hot' : 'false'}
|
|
6358
|
+
/* Round 534 / Loop — extends edge-badge drop-shadow
|
|
6359
|
+
coverage from hot-only (R480 amber) to also fire
|
|
6360
|
+
on hover/pin with a cyan accent glow. Pre-R534
|
|
6361
|
+
the badge's hover/pin lifted r (R164 9 → 10.5)
|
|
6362
|
+
+ sw (R394 1.25 → 1.5) + opacity (R395/R396 →
|
|
6363
|
+
1.0), but the paint axis stayed at the badge's
|
|
6364
|
+
rest fill — no glow to telegraph "in focus" at
|
|
6365
|
+
the paint layer. R534 closes that 4-axis hover-
|
|
6366
|
+
lift parity by adding drop-shadow glow on
|
|
6367
|
+
(hovered || pinned).
|
|
6368
|
+
Precedence: (hover || pin) wins over isHot when
|
|
6369
|
+
BOTH true — interactive signal (user is
|
|
6370
|
+
inspecting) overrides informational signal
|
|
6371
|
+
(hot lane). When only isHot fires (no hover/
|
|
6372
|
+
pin) the amber R480 glow remains; the hover/
|
|
6373
|
+
pin case paints cyan/teal `pal.legendAccent`
|
|
6374
|
+
at 0x99 alpha (~60%) — bright enough to read
|
|
6375
|
+
as "lit" but won't overwhelm at small badge
|
|
6376
|
+
size (r=10.5).
|
|
6377
|
+
Edge-badge 4-axis hover-lift parity now:
|
|
6378
|
+
R164 r 9 → 10.5
|
|
6379
|
+
R394 stroke-wd 1.25 → 1.5
|
|
6380
|
+
R395 opacity rest → 1.0
|
|
6381
|
+
R534 filter none → drop-shadow glow ← this round
|
|
6382
|
+
Drop-shadow visual-polish family extension —
|
|
6383
|
+
edge-badge surface upgraded from single-gate
|
|
6384
|
+
(R480 isHot) to two-gate (isHot OR hover-pin).
|
|
6385
|
+
transition list already includes filter 200ms
|
|
6386
|
+
ease-out (R480). data-edge-badge-glow attr
|
|
6387
|
+
upgraded from `isHot ? true : false` to a
|
|
6388
|
+
3-value string: 'hot' | 'hover' | 'false' so
|
|
6389
|
+
tests can distinguish gate cause.
|
|
6390
|
+
R51 sentinel safety: badge is edge-internal
|
|
6391
|
+
(not g[data-node] ancestor); filter is paint-
|
|
6392
|
+
only; bbox unchanged. */
|
|
5965
6393
|
style={{
|
|
5966
|
-
filter:
|
|
5967
|
-
? `drop-shadow(0 0 3px ${
|
|
5968
|
-
:
|
|
6394
|
+
filter: (isHoveredEdge || isPinned)
|
|
6395
|
+
? `drop-shadow(0 0 3px ${pal.legendAccent}99)`
|
|
6396
|
+
: isHot
|
|
6397
|
+
? `drop-shadow(0 0 3px ${hotStroke}80)`
|
|
6398
|
+
: undefined,
|
|
5969
6399
|
transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out, filter 200ms ease-out',
|
|
5970
6400
|
}}
|
|
5971
6401
|
/>
|
|
@@ -6265,6 +6695,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6265
6695
|
data-hub-busyness={busy}
|
|
6266
6696
|
data-topo-hub-halo-radius={haloR}
|
|
6267
6697
|
data-topo-hub-halo-hovered={isHaloHovered ? 'true' : 'false'}
|
|
6698
|
+
data-topo-hub-halo-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
6268
6699
|
data-topo-hub-halo-trough={isLight ? troughLight : troughDark}
|
|
6269
6700
|
data-topo-hub-halo-peak={isLight ? peakLight : peakDark}
|
|
6270
6701
|
/* Round 253 / Loop: hub grounding halo fill transition
|
|
@@ -6276,10 +6707,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6276
6707
|
conflict.
|
|
6277
6708
|
R451: r as CSS property (R197/R198 idiom) so the
|
|
6278
6709
|
hover-radius tween eases smoothly under the same
|
|
6279
|
-
200ms cadence as fill.
|
|
6710
|
+
200ms cadence as fill.
|
|
6711
|
+
R536: extends hub-cluster glow QUARTET (R476 digit
|
|
6712
|
+
+ R532 disc + R535 ring + R533 spokes) to a 5th
|
|
6713
|
+
tier — the halo gains drop-shadow at the outermost
|
|
6714
|
+
concentric ring on hub-hover. 2px blur + 0.3 alpha
|
|
6715
|
+
keeps the halo's glow subtle since (a) the halo is
|
|
6716
|
+
the LARGEST hub element (r=22 hover) and a heavier
|
|
6717
|
+
glow would bleed visibly past the ring tier into
|
|
6718
|
+
the spoke origin, and (b) the halo already SMIL-
|
|
6719
|
+
animates opacity (R84/R244 breath), so the visible
|
|
6720
|
+
glow pulses with the breath — an atmospheric
|
|
6721
|
+
"breathing glow" idiom rather than a static rim.
|
|
6722
|
+
Hub-cluster glow QUINTET (R476/R532/R533/R535/R536):
|
|
6723
|
+
digit (typo center) 3px emerald 0.6
|
|
6724
|
+
disc (r=5.5/6) 3px emerald 0.6
|
|
6725
|
+
ring (r=14/17) 3px emerald 0.5
|
|
6726
|
+
halo (r=20/22) 2px emerald 0.3 ← this round
|
|
6727
|
+
spokes (mesh) 1.5px cyan/teal 0.4
|
|
6728
|
+
Emerald palette continues through the focal-disc
|
|
6729
|
+
family (digit/disc/ring/halo); spokes break out
|
|
6730
|
+
into cyan/teal at the mesh tier. The 4-step alpha
|
|
6731
|
+
ladder 0.6→0.6→0.5→0.3 reads as the focal cluster
|
|
6732
|
+
fading outward — the OUTERMOST emerald glow is the
|
|
6733
|
+
softest, the focal digit is the brightest.
|
|
6734
|
+
filter is paint-only; SMIL animate on opacity
|
|
6735
|
+
continues independently (attribute vs CSS-property
|
|
6736
|
+
non-conflicting). transition list extends to
|
|
6737
|
+
'filter 200ms ease-out' alongside fill + r.
|
|
6738
|
+
Drop-shadow visual-polish family extension (12
|
|
6739
|
+
anchors). preview.50 milestone round. data-topo-
|
|
6740
|
+
hub-halo-glow attr exposes the gate state. */
|
|
6280
6741
|
style={{
|
|
6281
6742
|
r: `${haloR}px`,
|
|
6282
|
-
|
|
6743
|
+
filter: !reducedMotion && hoveredHub
|
|
6744
|
+
? (isLight
|
|
6745
|
+
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.3))'
|
|
6746
|
+
: 'drop-shadow(0 0 2px rgba(52, 211, 153, 0.3))')
|
|
6747
|
+
: undefined,
|
|
6748
|
+
transition: 'fill 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',
|
|
6283
6749
|
} as React.CSSProperties}
|
|
6284
6750
|
>
|
|
6285
6751
|
{/* Round 244 / Loop: hub grounding halo breath gets
|
|
@@ -6489,22 +6955,91 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6489
6955
|
so the glow eases under the same cadence as the
|
|
6490
6956
|
scale + fw + fill axes. */
|
|
6491
6957
|
data-topo-hub-working-count-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
6958
|
+
/* Round 507 / Loop — focal recede. When ANY non-hub
|
|
6959
|
+
canvas surface is hovered (a node / an edge / a
|
|
6960
|
+
group label / a legend row / a vendor chip), the
|
|
6961
|
+
hub-center workingCount digit fades to 0.85 opacity,
|
|
6962
|
+
signaling "you're inspecting elsewhere, hub recedes
|
|
6963
|
+
to background." When the user un-hovers (or hovers
|
|
6964
|
+
the hub itself), opacity returns to 1.0. Pure paint
|
|
6965
|
+
polish at the canvas's most prominent focal point.
|
|
6966
|
+
Hits 信息密度 + 动效 themes — the hub digit gives
|
|
6967
|
+
way visually to the surface under inspection,
|
|
6968
|
+
reinforcing the "this is the focal point right now"
|
|
6969
|
+
gesture without requiring users to track which
|
|
6970
|
+
surface holds attention.
|
|
6971
|
+
Gate excludes hoveredHub specifically: hovering the
|
|
6972
|
+
hub itself should LIFT the digit (R425 fw bump +
|
|
6973
|
+
R476 glow + R209 scale 1.08) — the existing hover-
|
|
6974
|
+
on-hub signature is intact; only inspection
|
|
6975
|
+
ELSEWHERE recedes the hub.
|
|
6976
|
+
Composed from existing hoveredAlias / hoveredEdge-
|
|
6977
|
+
Key / hoveredGroupLabel / hoveredStatus / hovered-
|
|
6978
|
+
Vendor — no new state. 300ms ease-out opacity
|
|
6979
|
+
transition already in the style list (existing R213
|
|
6980
|
+
transition spec), so the fade rides on existing
|
|
6981
|
+
infrastructure.
|
|
6982
|
+
data-topo-hub-recede attr surfaces the gate state
|
|
6983
|
+
for tests. */
|
|
6984
|
+
data-topo-hub-recede={
|
|
6985
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
6986
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'
|
|
6987
|
+
}
|
|
6988
|
+
/* Round 527 / Loop — focal-amplify family extension to a
|
|
6989
|
+
2nd anchor. R511 introduced focal-amplify at the hub-
|
|
6990
|
+
highlight disc (base opacity 0.95 → 1.0 on hover); R527
|
|
6991
|
+
extends to the hub-center workingCount digit with a
|
|
6992
|
+
letter-spacing tween 0 → 0.3px on hub-hover.
|
|
6993
|
+
Composes with existing 3-axis hub-hover signature on
|
|
6994
|
+
this element:
|
|
6995
|
+
R209 transform scale(1.08) geometry
|
|
6996
|
+
R425 fontWeight 700 → 800 typography weight
|
|
6997
|
+
R476 filter drop-shadow glow paint
|
|
6998
|
+
R527 letter-spacing 0 → 0.3px typography kerning ← this round
|
|
6999
|
+
tabular-nums (R225) preserved — each digit cell keeps
|
|
7000
|
+
fixed width; the inter-digit advance grows by 0.3px
|
|
7001
|
+
per gap. Single-digit counts (1-9) show no kerning
|
|
7002
|
+
effect; multi-digit counts (10+) show the spread as
|
|
7003
|
+
info-density signaling. Sibling to R427/R431/R432/
|
|
7004
|
+
R433/R434 (hover-letter-spacing family at panel-text
|
|
7005
|
+
scope) — R527 brings the same idiom to the canvas's
|
|
7006
|
+
most-read scalar.
|
|
7007
|
+
Reduced-motion gate matches R209 scale, R425 fw, R476
|
|
7008
|
+
filter — !reducedMotion gates the lift; reducedMotion
|
|
7009
|
+
users see static digit baseline regardless of hover.
|
|
7010
|
+
Focal-amplify family extension (2 anchors): R511 hub-
|
|
7011
|
+
highlight opacity / R527 hub-digit letter-spacing.
|
|
7012
|
+
transition list extends to include `letter-spacing
|
|
7013
|
+
200ms ease-out`, matching the cadence of the other
|
|
7014
|
+
hub-hover axes. data-topo-hub-working-count-letter-
|
|
7015
|
+
spacing attr exposes the resolved value for tests. */
|
|
7016
|
+
data-topo-hub-working-count-letter-spacing={!reducedMotion && hoveredHub ? '0.3px' : '0px'}
|
|
6492
7017
|
style={{
|
|
6493
7018
|
pointerEvents: 'none',
|
|
6494
7019
|
transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',
|
|
6495
7020
|
transformBox: 'fill-box',
|
|
6496
7021
|
transformOrigin: 'center',
|
|
7022
|
+
opacity: (hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
7023
|
+
hoveredStatus || hoveredVendor) && !hoveredHub
|
|
7024
|
+
? 0.85
|
|
7025
|
+
: 1,
|
|
6497
7026
|
filter: !reducedMotion && hoveredHub
|
|
6498
7027
|
? (isLight
|
|
6499
7028
|
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6))'
|
|
6500
7029
|
: 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6))')
|
|
6501
7030
|
: undefined,
|
|
7031
|
+
letterSpacing: !reducedMotion && hoveredHub ? '0.3px' : '0px',
|
|
6502
7032
|
/* R425: font-weight 200ms appended so the hover fw
|
|
6503
7033
|
bump 700 → 800 eases under the same cadence as
|
|
6504
7034
|
R209 scale + R253 fill + R213 opacity.
|
|
6505
7035
|
R476: filter 200ms appended so the new drop-
|
|
6506
|
-
shadow glow eases at the same cadence.
|
|
6507
|
-
|
|
7036
|
+
shadow glow eases at the same cadence.
|
|
7037
|
+
R507: opacity 300ms (existing in list) covers
|
|
7038
|
+
the new focal-recede fade.
|
|
7039
|
+
R527: letter-spacing 200ms appended so the new
|
|
7040
|
+
hover-kerning bump eases at the same cadence
|
|
7041
|
+
as the other axes. */
|
|
7042
|
+
transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, filter 200ms ease-out, letter-spacing 200ms ease-out',
|
|
6508
7043
|
fontVariantNumeric: 'tabular-nums',
|
|
6509
7044
|
}}
|
|
6510
7045
|
>
|
|
@@ -6550,19 +7085,205 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6550
7085
|
+ R213 always-mount opacity-gate + pointerEvents:none
|
|
6551
7086
|
+ R365 r=5.5 all preserved. data-topo-hub-highlight-
|
|
6552
7087
|
opacity attr exposes the resolved value for tests. */}
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
7088
|
+
{/* Round 508 / Loop — focal-recede pattern 2nd anchor.
|
|
7089
|
+
Extends R507's hub-digit recede to the hub-highlight
|
|
7090
|
+
circle so the hub focal CLUSTER (digit at z-top + this
|
|
7091
|
+
idle-state highlight beneath) recedes as a unit when
|
|
7092
|
+
canvas attention is elsewhere. Computed once: a single
|
|
7093
|
+
non-hub-hover gate drives BOTH the digit (R507) AND
|
|
7094
|
+
this highlight (R508) so they always co-move.
|
|
7095
|
+
Recede multiplies the visible opacity by 0.85 — when
|
|
7096
|
+
workingCount===0 the rest opacity 0.95 becomes 0.81
|
|
7097
|
+
during external-hover; when workingCount>0 the
|
|
7098
|
+
opacity stays 0 (invisible) regardless of recede.
|
|
7099
|
+
Additionally, when recede is active the SMIL breath
|
|
7100
|
+
animation halts (animate node un-mounts) so the
|
|
7101
|
+
receded state reads as quietly static, not pulsing
|
|
7102
|
+
at 0.85↔1.0 against the recede multiplier (which
|
|
7103
|
+
would visually conflict — competing 15% drops). On
|
|
7104
|
+
un-hover the animate re-mounts and breath resumes.
|
|
7105
|
+
data-topo-hub-recede on both digit AND highlight
|
|
7106
|
+
provides a stable test handle for the unified-recede
|
|
7107
|
+
gate.
|
|
7108
|
+
Composed from existing hover state vars — no new
|
|
7109
|
+
state. Pure paint axis. */}
|
|
7110
|
+
{(() => {
|
|
7111
|
+
const hubRecede = !!((hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
7112
|
+
hoveredStatus || hoveredVendor) && !hoveredHub);
|
|
7113
|
+
/* Round 511 / Loop — hub-highlight gains a 3rd opacity tier
|
|
7114
|
+
(hovered-amplify). Pre-R511 the highlight had 2 visible
|
|
7115
|
+
states: rest (0.95) and recede (0.81 = 0.95 × 0.85).
|
|
7116
|
+
When the hub itself was hovered, the digit got R425 fw
|
|
7117
|
+
lift + R476 drop-shadow + R209 scale-1.08, but the
|
|
7118
|
+
highlight disc sibling stayed at 0.95 — the focal
|
|
7119
|
+
cluster lifted in 3 channels (typography/paint/scale)
|
|
7120
|
+
but the highlight didn't participate.
|
|
7121
|
+
R511 closes that asymmetry: when hoveredHub is true,
|
|
7122
|
+
highlight base opacity lifts to 1.0 (5% boost from
|
|
7123
|
+
rest 0.95). Cluster now lifts as a unit on hub-hover,
|
|
7124
|
+
just like it recedes as a unit on non-hub-hover
|
|
7125
|
+
(R508).
|
|
7126
|
+
3-state opacity ladder:
|
|
7127
|
+
hub-hovered: baseOpacity = 1.0 (R511 NEW)
|
|
7128
|
+
rest (no hover): baseOpacity = 0.95 (existing)
|
|
7129
|
+
non-hub canvas hov: baseOpacity × 0.85 = 0.81 (R508)
|
|
7130
|
+
Composes cleanly: hubRecede gate requires !hoveredHub,
|
|
7131
|
+
so the hovered-amplify and recede states are mutually
|
|
7132
|
+
exclusive (they can't both fire). breathActive
|
|
7133
|
+
continues to halt on either non-rest state (recede OR
|
|
7134
|
+
hub-hover would visually compete with the 0.85↔1
|
|
7135
|
+
breath — clean for the unit-lift semantic too). */
|
|
7136
|
+
const baseOpacity = workingCount > 0 ? 0
|
|
7137
|
+
: hoveredHub ? 1.0
|
|
7138
|
+
: 0.95;
|
|
7139
|
+
const resolvedOpacity = hubRecede ? baseOpacity * 0.85 : baseOpacity;
|
|
7140
|
+
const breathActive = !reducedMotion && workingCount === 0 && !hubRecede && !hoveredHub;
|
|
7141
|
+
/* Round 529 / Loop — focal-amplify family 3rd anchor.
|
|
7142
|
+
Hub-highlight gains geometric amplify r 5.5 → 6 on
|
|
7143
|
+
hub-hover, mirroring R451's hub-halo r 20 → 22 hover
|
|
7144
|
+
pattern. Pre-R529 the highlight had paint-axis
|
|
7145
|
+
amplify only (R511 opacity 0.95 → 1.0 on hub-hover);
|
|
7146
|
+
R529 adds geometric amplify so the focal disc
|
|
7147
|
+
BREATHES outward on hub attention, like the halo
|
|
7148
|
+
does. Composes with the existing 2-axis hub-hover
|
|
7149
|
+
lift on this element:
|
|
7150
|
+
R511 opacity 0.95 → 1.0 paint (focal-amplify 1st)
|
|
7151
|
+
R529 r 5.5 → 6 geometry (this round)
|
|
7152
|
+
Implementation matches R451: CSS `r` property
|
|
7153
|
+
(R197/R198 idiom) for smooth interpolation. SVG
|
|
7154
|
+
attribute `r="5.5"` provides SSR fallback and serves
|
|
7155
|
+
as default; inline style.r overrides for animated
|
|
7156
|
+
value. transition list extends to include `r 200ms
|
|
7157
|
+
ease-out`, matching the fill cadence (also 200ms);
|
|
7158
|
+
opacity transition stays at 300ms (existing).
|
|
7159
|
+
r 6 sits well inside the existing visual envelope
|
|
7160
|
+
(next-larger sibling r=10 hub core, r=14 hub hover
|
|
7161
|
+
ring). The 0.5px lift is +9% radius / +19% area —
|
|
7162
|
+
enough to read as 'lift' without breaching the core
|
|
7163
|
+
boundary or invalidating overlap-test invariants.
|
|
7164
|
+
SMIL animate on opacity continues independently
|
|
7165
|
+
(animateAttr='opacity' vs CSS-property r — non-
|
|
7166
|
+
conflicting, same pattern R451 noted for halo).
|
|
7167
|
+
Focal-amplify family extension (3 anchors):
|
|
7168
|
+
R511 hub-highlight opacity 0.95 → 1.0
|
|
7169
|
+
R527 hub-digit letter-spacing 0 → 0.3px
|
|
7170
|
+
R529 hub-highlight radius 5.5 → 6 ← this round
|
|
7171
|
+
data-topo-hub-highlight-radius attr now reports the
|
|
7172
|
+
dynamic value (was static '5.5'). */
|
|
7173
|
+
const highlightR = !reducedMotion && hoveredHub ? 6 : 5.5;
|
|
7174
|
+
return (
|
|
7175
|
+
<circle
|
|
7176
|
+
cx={cx} cy={cy} r="5.5"
|
|
7177
|
+
/* Round 509 / Loop — 配色 cross-theme cleanup. Pre-R509
|
|
7178
|
+
the hub-highlight fill was hardcoded `#d1fae5`
|
|
7179
|
+
(emerald-100, a pale tone). On the light theme this
|
|
7180
|
+
near-white green ran against a pale background at
|
|
7181
|
+
0.95 opacity — the disc was effectively invisible.
|
|
7182
|
+
Matches the existing R253 halo theme-inversion
|
|
7183
|
+
pattern (line ~6481): light theme picks the dark
|
|
7184
|
+
vibrant emerald (#10b981 emerald-600), dark theme
|
|
7185
|
+
keeps the pale emerald (#d1fae5 emerald-100). Both
|
|
7186
|
+
read at the same 0.95 opacity against their
|
|
7187
|
+
respective backdrops — light gets a saturated
|
|
7188
|
+
focal dot; dark keeps the soft glow signature.
|
|
7189
|
+
Pure paint axis (fill change only); bbox unchanged;
|
|
7190
|
+
R51 SVG sentinel safety untouched.
|
|
7191
|
+
transition list already includes `fill 200ms`?
|
|
7192
|
+
Actually the existing transition spec is `opacity
|
|
7193
|
+
300ms ease-out` — fill change on theme toggle
|
|
7194
|
+
will be instant. That's acceptable: theme toggle
|
|
7195
|
+
is a discrete event, and the halo (line 6500)
|
|
7196
|
+
already snaps fill on theme toggle the same way
|
|
7197
|
+
(`fill 200ms ease-out` was added later to halo
|
|
7198
|
+
via R253). Future round could add `fill 200ms`
|
|
7199
|
+
to highlight too if theme-switch flicker is
|
|
7200
|
+
noticed. */
|
|
7201
|
+
fill={isLight ? '#10b981' : '#d1fae5'}
|
|
7202
|
+
opacity={resolvedOpacity}
|
|
7203
|
+
data-topo-hub-highlight
|
|
7204
|
+
data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}
|
|
7205
|
+
data-topo-hub-highlight-radius={highlightR}
|
|
7206
|
+
data-topo-hub-highlight-opacity={resolvedOpacity}
|
|
7207
|
+
data-topo-hub-highlight-breath={breathActive ? 'true' : 'false'}
|
|
7208
|
+
data-topo-hub-highlight-recede={hubRecede ? 'true' : 'false'}
|
|
7209
|
+
data-topo-hub-highlight-hovered={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
7210
|
+
data-topo-hub-highlight-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
7211
|
+
/* Round 510 / Loop — R509 follow-on: theme-toggle fill
|
|
7212
|
+
ease. Pre-R510 the hub-highlight transition spec only
|
|
7213
|
+
listed `opacity 300ms ease-out`. When R509 introduced
|
|
7214
|
+
theme-conditional fill (#d1fae5 ↔ #10b981), the fill
|
|
7215
|
+
change SNAPPED on theme toggle because the transition
|
|
7216
|
+
list didn't include `fill`. R510 extends to `fill
|
|
7217
|
+
200ms ease-out` so theme cycles smoothly through the
|
|
7218
|
+
emerald palette. 200ms timing matches the R253 halo
|
|
7219
|
+
fill transition (line ~6500) — both hub-cluster
|
|
7220
|
+
theme transitions now share a cadence so the focal
|
|
7221
|
+
cluster (digit + highlight + halo) eases as a unit.
|
|
7222
|
+
R508's recede opacity transition unchanged (300ms);
|
|
7223
|
+
fill is independent.
|
|
7224
|
+
R529: r as CSS property (R197/R198 idiom) + `r
|
|
7225
|
+
200ms ease-out` appended to transition list so
|
|
7226
|
+
the new hub-hover radius lift (5.5 → 6) eases
|
|
7227
|
+
under the same fill cadence. SVG attr r="5.5"
|
|
7228
|
+
above provides SSR fallback; inline style.r
|
|
7229
|
+
wins the cascade for the dynamic value.
|
|
7230
|
+
R532: filter drop-shadow glow on hub-hover —
|
|
7231
|
+
sibling to R476 hub-digit drop-shadow at the
|
|
7232
|
+
same gate (hoveredHub && !reducedMotion). Two
|
|
7233
|
+
adjacent hub focal elements (digit + highlight
|
|
7234
|
+
disc) now BOTH glow on hub-hover, reading as
|
|
7235
|
+
one unified focal cluster. Emerald palette
|
|
7236
|
+
matches R476:
|
|
7237
|
+
light: drop-shadow(0 0 2px rgba(16,185,129,0.6)) emerald-500
|
|
7238
|
+
cyber: drop-shadow(0 0 3px rgba(52,211,153,0.6)) emerald-400
|
|
7239
|
+
filter is paint-only (bbox unchanged); SMIL
|
|
7240
|
+
animate on opacity continues independently
|
|
7241
|
+
(animateAttr='opacity' vs CSS-property filter
|
|
7242
|
+
— non-conflicting). transition list extends to
|
|
7243
|
+
'filter 200ms ease-out' alongside fill/r.
|
|
7244
|
+
Drop-shadow visual-polish family extension
|
|
7245
|
+
(8 anchors): R476 hub-digit / R477 legend pin-
|
|
7246
|
+
ring / R478 recent freshness / hot edge / group
|
|
7247
|
+
label / zoom-state / node alias + R532 hub-
|
|
7248
|
+
highlight (this round). data-topo-hub-highlight-
|
|
7249
|
+
glow attr exposes the gate state. */
|
|
7250
|
+
style={{
|
|
7251
|
+
pointerEvents: 'none',
|
|
7252
|
+
r: `${highlightR}px`,
|
|
7253
|
+
filter: !reducedMotion && hoveredHub
|
|
7254
|
+
? (isLight
|
|
7255
|
+
? 'drop-shadow(0 0 2px rgba(16, 185, 129, 0.6))'
|
|
7256
|
+
: 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.6))')
|
|
7257
|
+
: undefined,
|
|
7258
|
+
transition: 'opacity 300ms ease-out, fill 200ms ease-out, r 200ms ease-out, filter 200ms ease-out',
|
|
7259
|
+
} as React.CSSProperties}
|
|
7260
|
+
>
|
|
7261
|
+
{/* Round 497 / Loop — idle-state breath (呼吸感 theme pivot
|
|
7262
|
+
from the R492-R496 press-family arc). Pre-R497 the hub
|
|
7263
|
+
idle highlight read as a static dim disc — present but
|
|
7264
|
+
motionless, visually mute. R497 adds a 4s opacity breath
|
|
7265
|
+
(0.85 ↔ 1.0 ↔ 0.85) so the hub reads "alive but quiet"
|
|
7266
|
+
instead of "frozen", giving the empty-fleet state a
|
|
7267
|
+
subtle living signature.
|
|
7268
|
+
Gates:
|
|
7269
|
+
- !reducedMotion (R29 a11y blanket) — reducedMotion
|
|
7270
|
+
users see static 0.95 disc, no animate
|
|
7271
|
+
- workingCount === 0 — when fleet is busy, the
|
|
7272
|
+
highlight is invisible (opacity=0) so the animate
|
|
7273
|
+
would waste paint cycles. Gating saves work.
|
|
7274
|
+
SMIL <animate> overrides the static opacity={0.95}
|
|
7275
|
+
during its run; falls back to 0.95 when reducedMotion
|
|
7276
|
+
flips on (the animate node simply doesn't render).
|
|
7277
|
+
4s cycle is long enough to feel like ambient breath
|
|
7278
|
+
rather than a pulse, matching the "quiet" semantic.
|
|
7279
|
+
data-topo-hub-highlight-breath attr exposes the
|
|
7280
|
+
resolved gate state for tests. */}
|
|
7281
|
+
{breathActive && (
|
|
7282
|
+
<animate attributeName="opacity" values="0.85;1;0.85" dur="4s" repeatCount="indefinite" />
|
|
7283
|
+
)}
|
|
7284
|
+
</circle>
|
|
7285
|
+
);
|
|
7286
|
+
})()}
|
|
6566
7287
|
{/* R115 / Loop: hover hint ring. Stroke-only circle at r=14
|
|
6567
7288
|
that fades in when the hub is hovered — the same idea
|
|
6568
7289
|
R44 used for node avatars (group-hover stroke). r=14
|
|
@@ -6628,13 +7349,47 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6628
7349
|
data-topo-hub-hover-ring-radius={hoveredHub ? 17 : 14}
|
|
6629
7350
|
data-topo-hub-hover-ring-stroke-width="1.75"
|
|
6630
7351
|
data-topo-hub-hover-ring-opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}
|
|
6631
|
-
/* Round
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
7352
|
+
/* Round 535 / Loop — completes the hub-cluster glow
|
|
7353
|
+
QUARTET by adding drop-shadow to the hub-hover-ring.
|
|
7354
|
+
Pre-R535 the hub-hover trio (R476 digit + R532 highlight
|
|
7355
|
+
disc + R533 spokes) glowed in unified emerald (digit/
|
|
7356
|
+
disc) + cyan/teal (spokes) on hub-hover, but the ring
|
|
7357
|
+
itself — the outermost solid emerald boundary at
|
|
7358
|
+
r=14→17 — stayed flat. R535 adds the matching emerald
|
|
7359
|
+
drop-shadow to the ring so the FULL hub-cluster glows
|
|
7360
|
+
across all four concentric surfaces on hub-hover:
|
|
7361
|
+
digit (typography center) drop-shadow 0 0 3px emerald
|
|
7362
|
+
highlight disc (r=5.5/6) drop-shadow 0 0 3px emerald
|
|
7363
|
+
hover-ring (r=14/17) drop-shadow 0 0 3px emerald ← this round
|
|
7364
|
+
spokes (mesh) drop-shadow 0 0 1.5px cyan/teal
|
|
7365
|
+
The ring is only visible on hub-hover (opacity=0 rest);
|
|
7366
|
+
adding drop-shadow at the same gate means the glow shows
|
|
7367
|
+
the moment the ring shows — no extra state needed.
|
|
7368
|
+
Same R476/R532 emerald palette since the ring sits
|
|
7369
|
+
inside the focal-disc tier (its color is also emerald
|
|
7370
|
+
#059669/#10b981).
|
|
7371
|
+
transition list extends to include 'filter 200ms ease-
|
|
7372
|
+
out' alongside the existing 180ms opacity/r — slight
|
|
7373
|
+
cadence mismatch (180 vs 200) is acceptable; the filter
|
|
7374
|
+
only appears AFTER the ring fades in via opacity, and
|
|
7375
|
+
the 200ms vs 180ms 20ms tail difference is below
|
|
7376
|
+
perceptual threshold.
|
|
7377
|
+
Drop-shadow visual-polish family extension (11 anchors):
|
|
7378
|
+
the hub-cluster glow quartet (R476/R532/R533/R535) plus
|
|
7379
|
+
the 7 non-hub anchors (R477/R478/R479/R480/R481/R483/
|
|
7380
|
+
R534) makes for a thoroughly polished glow vocabulary
|
|
7381
|
+
across the canvas.
|
|
7382
|
+
data-topo-hub-hover-ring-glow attr exposes the gate
|
|
7383
|
+
state for tests. */
|
|
7384
|
+
data-topo-hub-hover-ring-glow={!reducedMotion && hoveredHub ? 'true' : 'false'}
|
|
6635
7385
|
style={{
|
|
6636
7386
|
pointerEvents: 'none',
|
|
6637
|
-
|
|
7387
|
+
filter: !reducedMotion && hoveredHub
|
|
7388
|
+
? (isLight
|
|
7389
|
+
? 'drop-shadow(0 0 3px rgba(16, 185, 129, 0.5))'
|
|
7390
|
+
: 'drop-shadow(0 0 3px rgba(52, 211, 153, 0.5))')
|
|
7391
|
+
: undefined,
|
|
7392
|
+
transition: 'opacity 180ms ease-out, r 180ms ease-out, stroke 200ms ease-out, filter 200ms ease-out',
|
|
6638
7393
|
}}
|
|
6639
7394
|
/>
|
|
6640
7395
|
</g>)}
|
|
@@ -7547,6 +8302,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7547
8302
|
const internByAlias = /书生|书小生|intern/i.test(session.alias);
|
|
7548
8303
|
|
|
7549
8304
|
if (isIntern || internByAlias || vendor.logo) {
|
|
8305
|
+
/* Round 501 / Loop — vendor avatar inside node circles
|
|
8306
|
+
gains a hover-gated brightness lift. Pre-R501 the
|
|
8307
|
+
avatar <image> was the only per-node surface with
|
|
8308
|
+
NO hover treatment: R26 lifted the card, R242 tinted
|
|
8309
|
+
the card stroke, R427 spread the alias letter-
|
|
8310
|
+
spacing, R500 added the alias drop-shadow, R208
|
|
8311
|
+
lifted the runtime badge ring, R443 thickened
|
|
8312
|
+
the badge icon stroke, R177 brightened the
|
|
8313
|
+
halo — but the most visually-prominent element
|
|
8314
|
+
(the vendor logo / 书生 coin centred in each node)
|
|
8315
|
+
stayed paint-static. R501 closes the per-node
|
|
8316
|
+
hover-affordance arc by adding a 15% brightness
|
|
8317
|
+
lift on hover.
|
|
8318
|
+
Implementation: CSS filter: brightness(1.15)
|
|
8319
|
+
when hoveredAlias === session.alias. Pure paint
|
|
8320
|
+
axis on the <image> element — no geometry change,
|
|
8321
|
+
no bbox shift. Modern-browser supported (Chrome 64+
|
|
8322
|
+
/ FF 56+ / Safari 9.1+).
|
|
8323
|
+
Hits 节点视觉 theme. data-node-avatar-hovered
|
|
8324
|
+
attr surfaces the gate for tests.
|
|
8325
|
+
Gated on !reducedMotion as a courtesy (brightness
|
|
8326
|
+
transition < ~50ms still feels instant; the gate
|
|
8327
|
+
avoids the transition cycle for a11y users). */
|
|
8328
|
+
const isAvatarHovered = !reducedMotion && hoveredAlias === session.alias;
|
|
7550
8329
|
return (
|
|
7551
8330
|
<image
|
|
7552
8331
|
href={vendor.logo ?? '/intern_avatar.png'}
|
|
@@ -7555,6 +8334,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7555
8334
|
width={size}
|
|
7556
8335
|
height={size}
|
|
7557
8336
|
preserveAspectRatio="xMidYMid meet"
|
|
8337
|
+
data-node-avatar={session.alias}
|
|
8338
|
+
data-node-avatar-hovered={isAvatarHovered ? 'true' : 'false'}
|
|
8339
|
+
style={{
|
|
8340
|
+
filter: isAvatarHovered ? 'brightness(1.15)' : undefined,
|
|
8341
|
+
transition: 'filter 200ms ease-out',
|
|
8342
|
+
}}
|
|
7558
8343
|
/>
|
|
7559
8344
|
);
|
|
7560
8345
|
}
|
|
@@ -7978,6 +8763,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7978
8763
|
R211 fill 300ms + R305 letter-spacing 200ms
|
|
7979
8764
|
transition list preserved; only the
|
|
7980
8765
|
conditional gets a middle case. */}
|
|
8766
|
+
{/* Round 500 / Loop — milestone round, opens
|
|
8767
|
+
per-node alias drop-shadow polish. Extends the
|
|
8768
|
+
R476-R481 drop-shadow visual-polish family to a
|
|
8769
|
+
7th anchor: hovered alias text gains a soft
|
|
8770
|
+
status-coloured text-glow. Pre-R500 hover on
|
|
8771
|
+
a node triggered card-lift (R26 translateY) +
|
|
8772
|
+
card-stroke (R242 tint) + alias letter-spacing
|
|
8773
|
+
(R427 0.3px tier) but the alias TEXT itself had
|
|
8774
|
+
no paint-axis cue beyond fill (R211). R500 adds
|
|
8775
|
+
a drop-shadow on the text glyph itself, so the
|
|
8776
|
+
identity glyph itself lights up under attention
|
|
8777
|
+
— matching the R476 idiom (hub-digit emerald
|
|
8778
|
+
glow on hover) at the per-node identity scope.
|
|
8779
|
+
2px blur radius at 50% alpha — subtler than the
|
|
8780
|
+
R476 hub-digit (3px at 60%) because the alias
|
|
8781
|
+
text is smaller and more numerous (1 per node)
|
|
8782
|
+
so an aggressive glow would multiply into
|
|
8783
|
+
visual noise. Status-coloured (status.text) so
|
|
8784
|
+
the glow inherits the node's working/idle/
|
|
8785
|
+
offline palette — green/cyan/gray respectively.
|
|
8786
|
+
Drop-shadow visual-polish family — 7 anchors:
|
|
8787
|
+
R476 hub digit hover-gated emerald
|
|
8788
|
+
R477 legend pin-ring pin-gated row.fill
|
|
8789
|
+
R478 recent-row pip fresh-gated cyan
|
|
8790
|
+
R479 group-label text pin-gated cyan
|
|
8791
|
+
R480 hot-lane edge hot-gated amber
|
|
8792
|
+
R481 zoom-state minimap zoom-gated cyan
|
|
8793
|
+
R500 node alias text hover-gated status.text ← this round
|
|
8794
|
+
Filter is paint-only; bbox unchanged; overlap-
|
|
8795
|
+
test invariants hold (R51 selector gated to
|
|
8796
|
+
g[data-node] descendants with strokeWidth
|
|
8797
|
+
sentinels; text element doesn't carry stroke).
|
|
8798
|
+
transition list extends to include 'filter
|
|
8799
|
+
200ms ease-out' alongside the existing fill
|
|
8800
|
+
300ms + letter-spacing 200ms tweens.
|
|
8801
|
+
data-node-alias-glow attr surfaces the hover
|
|
8802
|
+
gate for tests. */}
|
|
7981
8803
|
<text
|
|
7982
8804
|
x="0" y="1" textAnchor="middle"
|
|
7983
8805
|
fill={status.text}
|
|
@@ -7985,11 +8807,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7985
8807
|
data-node-alias-text={session.alias}
|
|
7986
8808
|
data-node-alias-chat-target={chatAlias === session.alias ? 'true' : 'false'}
|
|
7987
8809
|
data-node-alias-hovered={hoveredAlias === session.alias ? 'true' : 'false'}
|
|
8810
|
+
data-node-alias-glow={!reducedMotion && hoveredAlias === session.alias ? 'true' : 'false'}
|
|
7988
8811
|
style={{
|
|
7989
|
-
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',
|
|
8812
|
+
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out, filter 200ms ease-out',
|
|
7990
8813
|
letterSpacing:
|
|
7991
8814
|
chatAlias === session.alias ? '0.5px' :
|
|
7992
8815
|
hoveredAlias === session.alias ? '0.3px' : '0px',
|
|
8816
|
+
filter: !reducedMotion && hoveredAlias === session.alias
|
|
8817
|
+
? `drop-shadow(0 0 2px ${status.text}80)`
|
|
8818
|
+
: undefined,
|
|
7993
8819
|
}}
|
|
7994
8820
|
>
|
|
7995
8821
|
{truncate(session.alias, fullMax)}
|
|
@@ -9261,11 +10087,46 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9261
10087
|
tier without disturbing the surrounding family
|
|
9262
10088
|
baseline. data-recent-row-text-font-weight attr
|
|
9263
10089
|
exposes the value for tests. */
|
|
9264
|
-
|
|
10090
|
+
/* Round 530 / Loop — extends hover-fw family
|
|
10091
|
+
(R416/R420/R425/R520/R521/R522, 6 anchors) to
|
|
10092
|
+
a 7th anchor: recent-row alias text gains
|
|
10093
|
+
fontWeight 500 → 600 on (isRowHovered ||
|
|
10094
|
+
isRowPinned). Pre-R530 R363 set fw=500
|
|
10095
|
+
statically; hover/pin lifted other axes
|
|
10096
|
+
(R55 fill brighten / R434 letter-spacing
|
|
10097
|
+
3-tier / R143 translateY / R104 row bg-
|
|
10098
|
+
tint / R474 cadence) but the fw stayed
|
|
10099
|
+
flat — same asymmetry R520 closed at the
|
|
10100
|
+
+N more footer.
|
|
10101
|
+
R530 mirrors R520's pattern at the row-
|
|
10102
|
+
text scope. Hover OR pin (isRowActive
|
|
10103
|
+
union) lifts fw to 600, matching the count
|
|
10104
|
+
tspan's cold-state tier (R320 fw=600), so
|
|
10105
|
+
on active state the alias label reads at
|
|
10106
|
+
the same data tier as the count it sits
|
|
10107
|
+
next to. Inner count tspan has its own
|
|
10108
|
+
explicit fontWeight (600 or 700 per R320/
|
|
10109
|
+
R445) so parent fw lift doesn't bleed
|
|
10110
|
+
(inheritance overridden).
|
|
10111
|
+
Hover-fw family extension (7 anchors):
|
|
10112
|
+
R416 chip-row count digit
|
|
10113
|
+
R420 chrome zoom-level
|
|
10114
|
+
R425 hub-center digit
|
|
10115
|
+
R520 +N more flows footer
|
|
10116
|
+
R521 chrome nodeSize S/M/L inactive
|
|
10117
|
+
R522 chrome layout Ring/Grid inactive
|
|
10118
|
+
R530 recent-row alias text ← this round
|
|
10119
|
+
transition list extends to include
|
|
10120
|
+
'font-weight 200ms ease-out', matching the
|
|
10121
|
+
R474 cadence of the existing fill +
|
|
10122
|
+
letter-spacing axes on this element.
|
|
10123
|
+
data-recent-row-text-font-weight attr
|
|
10124
|
+
flips '500' → '600' on isRowActive. */
|
|
10125
|
+
fontWeight={(isRowHovered || isRowPinned) ? '600' : '500'}
|
|
9265
10126
|
data-recent-row-text={link.key}
|
|
9266
10127
|
data-recent-row-text-pinned={isRowPinned ? 'true' : 'false'}
|
|
9267
10128
|
data-recent-row-text-hovered={!isRowPinned && isRowHovered ? 'true' : 'false'}
|
|
9268
|
-
data-recent-row-text-font-weight=
|
|
10129
|
+
data-recent-row-text-font-weight={(isRowHovered || isRowPinned) ? '600' : '500'}
|
|
9269
10130
|
/* Round 434 / Loop: recent-signal row text extends
|
|
9270
10131
|
from R220's pin-only letter-spacing (0 → 0.5 on
|
|
9271
10132
|
isRowPinned) to a 3-tier scale matching R433
|
|
@@ -9316,7 +10177,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9316
10177
|
shifts. */
|
|
9317
10178
|
data-recent-row-text-transition="200ms"
|
|
9318
10179
|
style={{
|
|
9319
|
-
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
|
|
10180
|
+
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out',
|
|
9320
10181
|
letterSpacing: isRowPinned ? '0.5px' :
|
|
9321
10182
|
isRowHovered ? '0.25px' : '0px',
|
|
9322
10183
|
}}
|
|
@@ -9399,12 +10260,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9399
10260
|
under the same R320 fill cadence. data-
|
|
9400
10261
|
recent-row-count-pinned attr exposes the
|
|
9401
10262
|
pin gate for tests. */}
|
|
10263
|
+
{/* Round 498 / Loop — hot-count subtle pulse. Pre-
|
|
10264
|
+
R498 the hot row count signaled via color (R127
|
|
10265
|
+
amber fill) + weight (R320 fw-700) + (R445 pin
|
|
10266
|
+
lift) but stayed visually motionless. R498 adds
|
|
10267
|
+
a 3s opacity breath (0.85↔1.0) on the digit when
|
|
10268
|
+
isHot && !reducedMotion — gentle "alive" signal
|
|
10269
|
+
on the lane carrying ≥ 10 messages, drawing
|
|
10270
|
+
glance without becoming noisy. Sibling of R497
|
|
10271
|
+
hub-idle-breath in the 呼吸感 theme arc; same
|
|
10272
|
+
0.85↔1.0 amplitude. Class adds an animation-
|
|
10273
|
+
only paint axis; no layout / bbox change. R29
|
|
10274
|
+
blanket also catches `animation-duration` for
|
|
10275
|
+
reducedMotion users, but the component-side
|
|
10276
|
+
gate makes the intent explicit and avoids
|
|
10277
|
+
a node tree thrash for those users (className
|
|
10278
|
+
stays absent rather than present-but-paused). */}
|
|
9402
10279
|
<tspan
|
|
9403
10280
|
fill={isHot ? hotStroke : undefined}
|
|
9404
10281
|
fontWeight={(isHot || isRowPinned) ? '700' : '600'}
|
|
10282
|
+
className={isHot && !reducedMotion ? 'anet-recent-hot-pulse' : undefined}
|
|
9405
10283
|
data-recent-row-count
|
|
9406
10284
|
data-recent-row-count-pinned={isRowPinned ? 'true' : 'false'}
|
|
9407
10285
|
data-recent-row-count-font-weight={(isHot || isRowPinned) ? '700' : '600'}
|
|
10286
|
+
data-recent-row-count-hot-pulse={isHot && !reducedMotion ? 'true' : 'false'}
|
|
9408
10287
|
{...(isHot ? { 'data-recent-row-count-hot': 'true' } : {})}
|
|
9409
10288
|
style={{
|
|
9410
10289
|
transition: 'fill 300ms ease-out, font-weight 200ms ease-out',
|
|
@@ -9648,6 +10527,39 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9648
10527
|
stays as is, so the rest-vs-hover delta still
|
|
9649
10528
|
reads clearly. data-recent-panel-more-font-weight
|
|
9650
10529
|
attr exposes the value for tests. */}
|
|
10530
|
+
{/* Round 520 / Loop — extends the `+N more flows` footer
|
|
10531
|
+
to a 5-axis hover signature by adding fontWeight
|
|
10532
|
+
500 → 600 on hover. Pre-R520 the footer carried 4
|
|
10533
|
+
hover axes:
|
|
10534
|
+
R195 fill legendText → legendAccent
|
|
10535
|
+
R325 letter-spacing 0.2 → 0.3px (R344 tween)
|
|
10536
|
+
R325 opacity 0.55 → 0.85
|
|
10537
|
+
R133 underline none → underline
|
|
10538
|
+
R368 had set fontWeight 500 statically as a sibling
|
|
10539
|
+
to R363/R364/R366 small-text fw lift family — but
|
|
10540
|
+
the footer's hover state didn't carry a fontWeight
|
|
10541
|
+
DELTA the way other interactive surfaces do (chip-
|
|
10542
|
+
row counts R416, chrome zoom-level R420, hub digit
|
|
10543
|
+
R425). R520 adds the missing weight axis: fw 500
|
|
10544
|
+
→ 600 on hover, so the footer reads "thickening AND
|
|
10545
|
+
lighting up" under cursor — same idiom as the
|
|
10546
|
+
chrome zoom-level R420 / chip-row digit R416 hover-
|
|
10547
|
+
bold pattern, applied at the panel nav-action
|
|
10548
|
+
surface.
|
|
10549
|
+
data-recent-panel-more-font-weight attr value
|
|
10550
|
+
flips from '500' → '600' on hover (was static
|
|
10551
|
+
'500' pre-R520).
|
|
10552
|
+
Bonus closure — R475 panel-text cadence: pre-R520
|
|
10553
|
+
the footer's transition list had `opacity 150ms`
|
|
10554
|
+
while R475 unified panel-text transitions at
|
|
10555
|
+
200ms. R518 closed the same gap at legend-count.
|
|
10556
|
+
R520 closes the LAST panel-text 150ms holdout
|
|
10557
|
+
here AND adds the new font-weight 200ms axis. All
|
|
10558
|
+
4 transition properties (opacity / fill / letter-
|
|
10559
|
+
spacing / font-weight) now uniform 200ms at the
|
|
10560
|
+
footer — same cadence as legend-label / legend-
|
|
10561
|
+
count / recent-row alias / recent-row count /
|
|
10562
|
+
group-label. */}
|
|
9651
10563
|
<text
|
|
9652
10564
|
x="115" y="82"
|
|
9653
10565
|
textAnchor="middle"
|
|
@@ -9655,14 +10567,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9655
10567
|
fontSize="9"
|
|
9656
10568
|
fontFamily="monospace"
|
|
9657
10569
|
fontStyle="italic"
|
|
9658
|
-
fontWeight=
|
|
10570
|
+
fontWeight={hoveredRecentMore ? '600' : '500'}
|
|
9659
10571
|
letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}
|
|
9660
10572
|
opacity={hoveredRecentMore ? 0.85 : 0.55}
|
|
9661
10573
|
textDecoration={hoveredRecentMore ? 'underline' : 'none'}
|
|
9662
10574
|
data-recent-panel-more={moreCount}
|
|
9663
10575
|
data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}
|
|
9664
|
-
data-recent-panel-more-font-weight=
|
|
9665
|
-
|
|
10576
|
+
data-recent-panel-more-font-weight={hoveredRecentMore ? '600' : '500'}
|
|
10577
|
+
data-recent-panel-more-transition="200ms"
|
|
10578
|
+
style={{ transition: 'opacity 200ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out' }}
|
|
9666
10579
|
>
|
|
9667
10580
|
{`+ ${moreCount}`}
|
|
9668
10581
|
<tspan opacity="0.7" data-recent-panel-more-unit>{` more flow${moreCount === 1 ? '' : 's'}`}</tspan>
|
|
@@ -10198,11 +11111,42 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10198
11111
|
the value for tests. R219 letter-spacing pin
|
|
10199
11112
|
tween + R55 fill transition + R181 always-mount
|
|
10200
11113
|
pin ring all preserved. */
|
|
10201
|
-
|
|
11114
|
+
/* Round 531 / Loop — extends hover-fw family (R416/
|
|
11115
|
+
R420/R425/R520/R521/R522/R530, 7 anchors) to an
|
|
11116
|
+
8th anchor at the legend-row label. Pre-R531
|
|
11117
|
+
R364 set fw=500 statically; hover/pin lifted
|
|
11118
|
+
other axes (R55 fill brighten / R433 letter-
|
|
11119
|
+
spacing 3-tier / R181 pin ring) but the fw
|
|
11120
|
+
stayed flat. R531 mirrors R530's recent-row
|
|
11121
|
+
alias pattern at the legend-row label scope.
|
|
11122
|
+
Hover OR pin (hoveredStatus===row.key ||
|
|
11123
|
+
isPinned) lifts fw to 600, matching the
|
|
11124
|
+
legend-row count tier (R309 fw=600 / R446
|
|
11125
|
+
pin lift 600→700). Active label now reads at
|
|
11126
|
+
the count's data tier — sibling treatment to
|
|
11127
|
+
R530 recent-row.
|
|
11128
|
+
Hover-fw family extension (8 anchors):
|
|
11129
|
+
R416 chip-row count digit
|
|
11130
|
+
R420 chrome zoom-level
|
|
11131
|
+
R425 hub-center digit
|
|
11132
|
+
R520 +N more flows footer
|
|
11133
|
+
R521 chrome nodeSize S/M/L inactive
|
|
11134
|
+
R522 chrome layout Ring/Grid inactive
|
|
11135
|
+
R530 recent-row alias text
|
|
11136
|
+
R531 legend-row label ← this round
|
|
11137
|
+
Two panel-row label surfaces (R530 recent-
|
|
11138
|
+
row alias + R531 legend-row label) now have
|
|
11139
|
+
parallel hover-fw signatures. R475 cadence
|
|
11140
|
+
at 200ms already covers font-weight via the
|
|
11141
|
+
existing transition list extension at this
|
|
11142
|
+
element. data-legend-row-label-font-weight
|
|
11143
|
+
attr flips '500' → '600' on isActive (was
|
|
11144
|
+
static '500' pre-R531). */
|
|
11145
|
+
fontWeight={(hoveredStatus === row.key || isPinned) ? '600' : '500'}
|
|
10202
11146
|
data-legend-row-label={row.key}
|
|
10203
11147
|
data-legend-row-label-pinned={isPinned ? 'true' : 'false'}
|
|
10204
11148
|
data-legend-row-label-hovered={!isPinned && hoveredStatus === row.key ? 'true' : 'false'}
|
|
10205
|
-
data-legend-row-label-font-weight=
|
|
11149
|
+
data-legend-row-label-font-weight={(hoveredStatus === row.key || isPinned) ? '600' : '500'}
|
|
10206
11150
|
/* Round 433 / Loop: legend-row text extends from
|
|
10207
11151
|
R219's pin-only letter-spacing (0px → 0.5px on
|
|
10208
11152
|
isPinned) to a 3-tier scale matching the R432
|
|
@@ -10245,7 +11189,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10245
11189
|
the timing axis shifts. */
|
|
10246
11190
|
data-legend-row-label-transition="200ms"
|
|
10247
11191
|
style={{
|
|
10248
|
-
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
|
|
11192
|
+
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out',
|
|
10249
11193
|
letterSpacing: isPinned ? '0.5px' :
|
|
10250
11194
|
hoveredStatus === row.key ? '0.25px' : '0px',
|
|
10251
11195
|
}}
|
|
@@ -10379,7 +11323,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10379
11323
|
data-legend-count-pinned={isPinned ? 'true' : 'false'}
|
|
10380
11324
|
data-legend-count-font-weight={isPinned ? '700' : '600'}
|
|
10381
11325
|
data-legend-count-fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? 'tier' : 'neutral'}
|
|
10382
|
-
|
|
11326
|
+
/* Round 518 / Loop — extends R433's 3-tier hover-
|
|
11327
|
+
letter-spacing tween from the legend-row LABEL
|
|
11328
|
+
(text at x=30) to the SIBLING legend-row COUNT
|
|
11329
|
+
digit (this text at x=215). Pre-R518 the row's
|
|
11330
|
+
label spread on hover/pin (R433: 0/0.25/0.5px)
|
|
11331
|
+
while the count digit at the row's right edge
|
|
11332
|
+
stayed dead-typographic — same row, two halves,
|
|
11333
|
+
asymmetric kerning gesture. R518 mirrors the
|
|
11334
|
+
3-tier scale at the count so the WHOLE row's
|
|
11335
|
+
typography reads as one unit under cursor: label
|
|
11336
|
+
+ count spread together at matching values.
|
|
11337
|
+
Tabular-nums (R225) makes the kerning still
|
|
11338
|
+
visible on 2-digit counts — each digit cell
|
|
11339
|
+
keeps its fixed width, but the inter-digit
|
|
11340
|
+
advance grows. R518 also closes R475's panel-
|
|
11341
|
+
row TEXT cadence at the count surface — R475
|
|
11342
|
+
lifted the label text transitions to 200ms but
|
|
11343
|
+
the count was missed; R518 lifts opacity / fill
|
|
11344
|
+
/ font-weight from 150 → 200ms AND adds the new
|
|
11345
|
+
letter-spacing axis at 200ms. One transition
|
|
11346
|
+
list, one cadence, one motion-coherent multi-
|
|
11347
|
+
axis hover/pin signature across the row.
|
|
11348
|
+
Hover-letter-spacing family extension (10
|
|
11349
|
+
anchors now): R344/R345/R347/R420/R427/R431/
|
|
11350
|
+
R432/R433/R517/R518. R518 closes the legend-
|
|
11351
|
+
row pair (label R433 + count R518). data-
|
|
11352
|
+
legend-count-letter-spacing attr exposes the
|
|
11353
|
+
resolved value for tests. */
|
|
11354
|
+
data-legend-count-letter-spacing={isPinned ? '0.5px' : hoveredStatus === row.key ? '0.25px' : '0px'}
|
|
11355
|
+
data-legend-count-transition="200ms"
|
|
11356
|
+
style={{
|
|
11357
|
+
pointerEvents: 'none',
|
|
11358
|
+
transition: 'opacity 200ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out, letter-spacing 200ms ease-out',
|
|
11359
|
+
fontVariantNumeric: 'tabular-nums',
|
|
11360
|
+
letterSpacing: isPinned ? '0.5px' :
|
|
11361
|
+
hoveredStatus === row.key ? '0.25px' : '0px',
|
|
11362
|
+
}}
|
|
10383
11363
|
>{row.count}</text>
|
|
10384
11364
|
</g>
|
|
10385
11365
|
);
|
|
@@ -10438,6 +11418,74 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10438
11418
|
spacing as typographic intent. Stays well inside the
|
|
10439
11419
|
bottom-left corner; opacity 0.4 unchanged so the
|
|
10440
11420
|
watermark stays a watermark. */}
|
|
11421
|
+
{/* Round 519 / Loop — 呼吸感 family 3rd anchor. Pre-R519 the
|
|
11422
|
+
breath family had 2 anchors (R497 hub idle digit + R498
|
|
11423
|
+
recent-row hot pulse). Both signal active state — the
|
|
11424
|
+
digit when canvas is idle (no work pending), the recent
|
|
11425
|
+
row when fresh signal arrives. R519 adds a SLOW ambient
|
|
11426
|
+
breath to the brand watermark — present always, not gated
|
|
11427
|
+
on activity state. The watermark IS the canvas-corner
|
|
11428
|
+
register that says "the canvas is alive even when nothing
|
|
11429
|
+
is happening"; a 6s opacity pulse around its 0.4 mean
|
|
11430
|
+
(±0.08 swing → 0.32 ↔ 0.48) reads as ambient liveness
|
|
11431
|
+
rather than foreground signal.
|
|
11432
|
+
Why 6s (not R497's 4s): the breath family now spans
|
|
11433
|
+
activity registers (R497 4s — idle-focal: present and
|
|
11434
|
+
waiting; R498 ~3s — hot signal: just arrived) and now
|
|
11435
|
+
ambient register (R519 6s — corner watermark: always-on
|
|
11436
|
+
background). Slower cadence keeps the watermark in the
|
|
11437
|
+
background; ~10 pct slower than R497 keeps it out of
|
|
11438
|
+
phase so the two anchors never beat together visibly.
|
|
11439
|
+
Gate: !reducedMotion. Inside the prefers-reduced-motion
|
|
11440
|
+
media query, SMIL animate isn't covered by globals.css
|
|
11441
|
+
R29 (which only kills CSS animation property), so we
|
|
11442
|
+
gate at JSX level — when reducedMotion is true the
|
|
11443
|
+
<animate> child isn't mounted and opacity stays at the
|
|
11444
|
+
static 0.4. data-topo-brand-watermark-breath attr
|
|
11445
|
+
exposes the gate state for tests.
|
|
11446
|
+
呼吸感 family extension (3 anchors): R497 hub idle / R498
|
|
11447
|
+
recent-row hot / R519 brand watermark ambient. */}
|
|
11448
|
+
{/* Round 525 / Loop — focal-recede family 3rd anchor. R507
|
|
11449
|
+
receded the hub-center workingCount digit; R508 receded
|
|
11450
|
+
the hub-highlight disc; both fade to 0.85× when any non-
|
|
11451
|
+
hub canvas surface is hovered (alias / edge / group /
|
|
11452
|
+
status / vendor) — the "you're inspecting elsewhere"
|
|
11453
|
+
gesture. R525 extends the pattern to the brand watermark
|
|
11454
|
+
at canvas bottom-left, the always-on decorative brand
|
|
11455
|
+
element. Pre-R525 the watermark stayed at its R519
|
|
11456
|
+
breath baseline (0.32-0.48 SMIL pulse) regardless of
|
|
11457
|
+
canvas attention; post-R525 it fades to 70% wrapper
|
|
11458
|
+
opacity (effective 0.224-0.336 with breath) when canvas
|
|
11459
|
+
attention is elsewhere, matching the same focal-recede
|
|
11460
|
+
semantic R507/R508 establish at the hub focal cluster.
|
|
11461
|
+
Implementation: wrap the existing <text> in a <g>
|
|
11462
|
+
wrapper whose opacity multiplies with the inner text's
|
|
11463
|
+
SMIL-animated opacity. SVG opacity composes
|
|
11464
|
+
multiplicatively across the parent/child chain, so:
|
|
11465
|
+
normal: g.opacity=1.0 × text.opacity(SMIL 0.32-0.48) = 0.32-0.48
|
|
11466
|
+
recede: g.opacity=0.7 × text.opacity(SMIL 0.32-0.48) = 0.224-0.336
|
|
11467
|
+
SMIL on inner text continues running through both
|
|
11468
|
+
states; only the wrapper opacity flips. 300ms ease-out
|
|
11469
|
+
transition on wrapper (matches R508 hub-highlight recede
|
|
11470
|
+
transition).
|
|
11471
|
+
Gate matches R507/R508 — focal-recede is a UNIFIED
|
|
11472
|
+
non-hub-canvas-hover signal driving multiple anchors,
|
|
11473
|
+
so all three (hub digit / hub-highlight / brand
|
|
11474
|
+
watermark) fade together as the canvas's decorative
|
|
11475
|
+
register, leaving only the surface under inspection
|
|
11476
|
+
foregrounded.
|
|
11477
|
+
Focal-recede family extension (3 anchors): R507 hub
|
|
11478
|
+
digit / R508 hub-highlight / R525 brand watermark. */}
|
|
11479
|
+
<g
|
|
11480
|
+
opacity={(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
11481
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 0.7 : 1}
|
|
11482
|
+
data-topo-brand-watermark-wrapper
|
|
11483
|
+
data-topo-brand-watermark-recede={
|
|
11484
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
11485
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'
|
|
11486
|
+
}
|
|
11487
|
+
style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out' }}
|
|
11488
|
+
>
|
|
10441
11489
|
<text
|
|
10442
11490
|
x="16" y="672"
|
|
10443
11491
|
fontSize="11" fontFamily="monospace" fontWeight="600"
|
|
@@ -10445,8 +11493,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10445
11493
|
fill={pal.legendText}
|
|
10446
11494
|
opacity="0.4"
|
|
10447
11495
|
data-topo-brand-watermark
|
|
11496
|
+
data-topo-brand-watermark-breath={reducedMotion ? 'false' : 'true'}
|
|
10448
11497
|
style={{ pointerEvents: 'none', transition: 'fill 200ms ease-out' }}
|
|
10449
|
-
>sleep2agi
|
|
11498
|
+
>sleep2agi{!reducedMotion && (
|
|
11499
|
+
<animate attributeName="opacity" values="0.32;0.48;0.32" dur="6s" repeatCount="indefinite" />
|
|
11500
|
+
)}</text>
|
|
11501
|
+
</g>
|
|
10450
11502
|
{/* v0.10.0 Hero 3 Wave 1 / RFC §3.I (Vincent 5215 + 通信龙
|
|
10451
11503
|
lead-autonomy Q4 dual-anchor minimal): canvas top-left
|
|
10452
11504
|
crescent moon brand mark, visible ONLY when the
|
|
@@ -10481,10 +11533,47 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10481
11533
|
the R175 panel-fade-in uses for cascade rhythm. data-
|
|
10482
11534
|
topo-brand-canvas-mark-visible exposes the gate for
|
|
10483
11535
|
tests. */}
|
|
11536
|
+
{/* Round 526 / Loop — focal-recede family 4th anchor.
|
|
11537
|
+
Symmetric polish to R525 (watermark recede). The
|
|
11538
|
+
brand crescent at canvas top-left is the second
|
|
11539
|
+
decorative brand element on the canvas; pre-R526 it
|
|
11540
|
+
stayed at flat opacity 0.35 (when visible) regardless
|
|
11541
|
+
of canvas attention. R526 multiplies its visible
|
|
11542
|
+
opacity by 0.7 when ANY non-hub canvas surface is
|
|
11543
|
+
hovered, matching R525's deeper-recede semantic for
|
|
11544
|
+
decorative brand elements (vs hub focal cluster's
|
|
11545
|
+
0.85× recede at R507/R508).
|
|
11546
|
+
Composes cleanly with existing flowLinks gate:
|
|
11547
|
+
normal, flowLinks=0: opacity = 0.35 * 1.0 = 0.350
|
|
11548
|
+
recede, flowLinks=0: opacity = 0.35 * 0.7 = 0.245
|
|
11549
|
+
invisible, flowLinks>0: opacity = 0.00 * any = 0.000
|
|
11550
|
+
Multiplicative chain means recede only matters when
|
|
11551
|
+
crescent is visible (quiet canvas, flowLinks=0) —
|
|
11552
|
+
exactly when canvas attention elsewhere should
|
|
11553
|
+
dim the decorative register. 300ms transition
|
|
11554
|
+
already covers both axes (the existing visibility
|
|
11555
|
+
opacity ramp + the new recede multiplier easing).
|
|
11556
|
+
Focal-recede family extension (4 anchors): R507 hub
|
|
11557
|
+
digit / R508 hub-highlight / R525 watermark / R526
|
|
11558
|
+
crescent (this round). Canvas brand surfaces (R525
|
|
11559
|
+
watermark + R526 crescent) now BOTH carry focal-
|
|
11560
|
+
recede at the same 0.7 multiplier, fading as a
|
|
11561
|
+
decorative pair when the canvas's focal attention
|
|
11562
|
+
shifts elsewhere.
|
|
11563
|
+
data-topo-brand-canvas-mark-recede attr exposes the
|
|
11564
|
+
gate state for tests. */}
|
|
10484
11565
|
<g
|
|
10485
|
-
opacity={flowLinks.length === 0 ? 0.35 : 0
|
|
11566
|
+
opacity={(flowLinks.length === 0 ? 0.35 : 0) * (
|
|
11567
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
11568
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 0.7 : 1
|
|
11569
|
+
)}
|
|
10486
11570
|
data-topo-brand-canvas-mark
|
|
10487
11571
|
data-topo-brand-canvas-mark-visible={flowLinks.length === 0 ? 'true' : 'false'}
|
|
11572
|
+
data-topo-brand-canvas-mark-recede={
|
|
11573
|
+
(hoveredAlias || hoveredEdgeKey || hoveredGroupLabel ||
|
|
11574
|
+
hoveredStatus || hoveredVendor) && !hoveredHub ? 'true' : 'false'
|
|
11575
|
+
}
|
|
11576
|
+
data-topo-brand-canvas-mark-breath={reducedMotion ? 'false' : 'true'}
|
|
10488
11577
|
style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out, fill 200ms ease-out' }}
|
|
10489
11578
|
>
|
|
10490
11579
|
<defs>
|
|
@@ -10494,11 +11583,44 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10494
11583
|
<circle cx="17.5" cy="13" r="10" fill="black" />
|
|
10495
11584
|
</mask>
|
|
10496
11585
|
</defs>
|
|
11586
|
+
{/* Round 528 / Loop — 呼吸感 family 4th anchor. Symmetric
|
|
11587
|
+
polish to R519 watermark ambient breath. The brand
|
|
11588
|
+
crescent at canvas top-left is the second decorative
|
|
11589
|
+
canvas brand surface; pre-R528 it stayed at the static
|
|
11590
|
+
composed opacity (wrapper 0.35 × no inner anim = flat).
|
|
11591
|
+
Post-R528 the inner <rect>'s fill-opacity breathes
|
|
11592
|
+
0.8 ↔ 1.0 on a 7s cycle, composing multiplicatively
|
|
11593
|
+
with the wrapper's recede gate:
|
|
11594
|
+
normal visible: 0.35 × (0.8-1.0) = 0.280-0.350
|
|
11595
|
+
recede visible: 0.245 × (0.8-1.0) = 0.196-0.245
|
|
11596
|
+
invisible: 0 × any = 0
|
|
11597
|
+
7s cadence intentionally OUT OF PHASE with R519
|
|
11598
|
+
watermark's 6s — the two ambient anchors never beat
|
|
11599
|
+
together visibly when both visible. R497 hub idle
|
|
11600
|
+
breath (4s) is the loudest; R498 recent-row hot pulse
|
|
11601
|
+
(~3s) is the most-active; R519 watermark (6s) +
|
|
11602
|
+
R528 crescent (7s) are the quietest ambient pair.
|
|
11603
|
+
呼吸感 family extension (4 anchors):
|
|
11604
|
+
R497 hub idle digit 4s active-idle register
|
|
11605
|
+
R498 recent-row hot pulse 3s active-fresh register
|
|
11606
|
+
R519 watermark ambient 6s ambient (always-on)
|
|
11607
|
+
R528 crescent ambient 7s ambient (quiet-only) ← this round
|
|
11608
|
+
SMIL <animate> on fill-opacity (not parent opacity) so
|
|
11609
|
+
the wrapper's React-controlled gate compositions stay
|
|
11610
|
+
intact. Gated on !reducedMotion at JSX level —
|
|
11611
|
+
reducedMotion users see the inner rect at default
|
|
11612
|
+
fill-opacity=1.0 (no SMIL mounted, wrapper's static
|
|
11613
|
+
composed opacity wins). data-topo-brand-canvas-mark-
|
|
11614
|
+
breath attr exposes the gate state. */}
|
|
10497
11615
|
<rect
|
|
10498
11616
|
x="16" y="16" width="28" height="28"
|
|
10499
11617
|
fill={pal.legendText}
|
|
10500
11618
|
mask="url(#s2a-canvas-corner-mask)"
|
|
10501
|
-
|
|
11619
|
+
>
|
|
11620
|
+
{!reducedMotion && (
|
|
11621
|
+
<animate attributeName="fill-opacity" values="0.8;1;0.8" dur="7s" repeatCount="indefinite" />
|
|
11622
|
+
)}
|
|
11623
|
+
</rect>
|
|
10502
11624
|
</g>
|
|
10503
11625
|
</svg>
|
|
10504
11626
|
|
|
@@ -10988,7 +12110,49 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10988
12110
|
/ fullscreen) preview their active state on hover.
|
|
10989
12111
|
Pure actions (zoom -/+, reset) stay white — they
|
|
10990
12112
|
aren't toggles, have no active state to preview. */
|
|
10991
|
-
|
|
12113
|
+
// Round 493 / Loop — extends R492 chrome-strip press-feedback
|
|
12114
|
+
// family to nodeSize S/M/L buttons. Adds active:scale-95
|
|
12115
|
+
// alongside the existing color-deepen (R196) + chrome-pop
|
|
12116
|
+
// (R249). transition-transform + duration-200 + ease-out
|
|
12117
|
+
// + transform-gpu added since this className previously had
|
|
12118
|
+
// transition-colors only — without the transform transition,
|
|
12119
|
+
// active:scale-95 would hard-cut. transform-gpu promotes the
|
|
12120
|
+
// layer so scale doesn't trigger paint thrash.
|
|
12121
|
+
/* Round 521 / Loop — extends R270's hover-preview pattern
|
|
12122
|
+
(inactive toggle hover previews the active state's
|
|
12123
|
+
visual register) to the TYPOGRAPHY axis at the chrome
|
|
12124
|
+
nodeSize S/M/L surface. Pre-R521 the inactive variant
|
|
12125
|
+
had `hover:bg-cyan-500/5` (R270 bg preview) but no
|
|
12126
|
+
typography preview — active variant uses `font-medium`
|
|
12127
|
+
(fw 500), inactive variant sat at default fw 400 even
|
|
12128
|
+
on hover.
|
|
12129
|
+
R521 adds `hover:font-medium` + `transition-[font-
|
|
12130
|
+
weight]` to the inactive variant so hovering an
|
|
12131
|
+
inactive S/M/L letter thickens the glyph 400 → 500,
|
|
12132
|
+
previewing the typography of the active state the
|
|
12133
|
+
click would commit to. Sibling to R421 chrome zoom-
|
|
12134
|
+
level fontWeight hover delta (rest 500 → hover 600)
|
|
12135
|
+
and R520 footer fontWeight hover (500 → 600) — same
|
|
12136
|
+
idiom: thicken-on-hover for chrome surfaces with a
|
|
12137
|
+
pre-commit gesture.
|
|
12138
|
+
`font-medium` (500) matches the ACTIVE variant's
|
|
12139
|
+
fw exactly — the inactive hover landing weight equals
|
|
12140
|
+
the active locked weight, so clicking commits to a
|
|
12141
|
+
typography state the eye already saw 'on the way in'.
|
|
12142
|
+
Hover-fw family extension (5 anchors now):
|
|
12143
|
+
R416 chip-row count digit rest 500 → hover 700/600
|
|
12144
|
+
R420 chrome zoom-level rest 500 → hover 600
|
|
12145
|
+
R425 hub-center digit rest 700 → hover 800
|
|
12146
|
+
R520 +N more flows footer rest 500 → hover 600
|
|
12147
|
+
R521 chrome nodeSize S/M/L inactive rest 400 → hover 500 ← this round
|
|
12148
|
+
Active variant `font-medium` unchanged so the rest-vs-
|
|
12149
|
+
active typography distinction stays intact when the
|
|
12150
|
+
user IS clicked-in (active stays at fw 500, inactive
|
|
12151
|
+
rest at fw 400, inactive hover preview at fw 500).
|
|
12152
|
+
data-topo-chrome-nodesize-hover-preview-fw="500" attr
|
|
12153
|
+
exposes the polish for tests. */
|
|
12154
|
+
className={`px-2 py-1 transition-colors transition-transform transition-[font-weight] duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15 hover:font-medium'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}
|
|
12155
|
+
data-topo-chrome-nodesize-hover-preview-fw={nodeScale === v ? null : '500'}
|
|
10992
12156
|
style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}
|
|
10993
12157
|
>
|
|
10994
12158
|
{lbl}
|
|
@@ -11030,7 +12194,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11030
12194
|
// → white/10) so mouse-down has a tactile dim before the
|
|
11031
12195
|
// R186 icon pop fires on release.
|
|
11032
12196
|
// R352: `group` lets the inner svg respond via group-hover.
|
|
11033
|
-
|
|
12197
|
+
// R493 — zoom +/− buttons join the chrome-strip active:scale-95
|
|
12198
|
+
// press-feedback family (R492 + nodeSize above). transition-
|
|
12199
|
+
// transform + duration-200 + ease-out + transform-gpu added
|
|
12200
|
+
// since the className had only transition-colors.
|
|
12201
|
+
className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors transition-transform duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
|
|
11034
12202
|
style={{ color: pal.legendText }}
|
|
11035
12203
|
aria-label="Zoom out"
|
|
11036
12204
|
title="Zoom out (−)"
|
|
@@ -11122,10 +12290,32 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11122
12290
|
? 'true' : 'false'
|
|
11123
12291
|
}
|
|
11124
12292
|
data-topo-chrome-zoom-level-hover={hoveredZoomLevel ? 'true' : 'false'}
|
|
12293
|
+
/* Round 517 / Loop — extends the chrome zoom-level readout
|
|
12294
|
+
from 2-axis (R347 letter-spacing + R420 fontWeight) to
|
|
12295
|
+
3-axis hover signature by adding a color brighten to
|
|
12296
|
+
pal.legendHeadline. Pre-R517 the readout's color stayed
|
|
12297
|
+
at pal.legendText on hover; the digits got tighter
|
|
12298
|
+
kerning (0→0.5px) and heavier weight (500→600) but
|
|
12299
|
+
stayed the same legendText gray tone. R517 lifts color
|
|
12300
|
+
to legendHeadline on hover so the readout brightens
|
|
12301
|
+
into the headline tier at the same beat — matching the
|
|
12302
|
+
R55/R197/R239 hover-deepen-own-hue idiom that legend-
|
|
12303
|
+
row label + count carry at panel scope. Chrome strip's
|
|
12304
|
+
only data display now has full 3-axis hover signature
|
|
12305
|
+
(letter-spacing + fontWeight + color), parity with the
|
|
12306
|
+
chip-row chips' own hover-brighten pattern.
|
|
12307
|
+
Implementation: inline color uses the same hoveredZoom-
|
|
12308
|
+
Level state as R347/R420 — no new state. Transition
|
|
12309
|
+
already includes 'color 200ms ease-out' (R264) so the
|
|
12310
|
+
brighten eases under the same cadence as the kerning +
|
|
12311
|
+
weight tweens — one motion-coherent 3-axis lift.
|
|
12312
|
+
data-topo-chrome-zoom-level-color attr exposes the
|
|
12313
|
+
resolved color string for tests. */
|
|
12314
|
+
data-topo-chrome-zoom-level-color={hoveredZoomLevel ? 'headline' : 'text'}
|
|
11125
12315
|
onMouseEnter={() => setHoveredZoomLevel(true)}
|
|
11126
12316
|
onMouseLeave={() => setHoveredZoomLevel(false)}
|
|
11127
12317
|
style={{
|
|
11128
|
-
color: pal.legendText,
|
|
12318
|
+
color: hoveredZoomLevel ? pal.legendHeadline : pal.legendText,
|
|
11129
12319
|
borderColor: pal.containerBorder,
|
|
11130
12320
|
minWidth: 46,
|
|
11131
12321
|
display: 'inline-block',
|
|
@@ -11170,7 +12360,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11170
12360
|
data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}
|
|
11171
12361
|
// R196: press-state (mirror of zoom-out above).
|
|
11172
12362
|
// R352: `group` lets the inner svg respond via group-hover.
|
|
11173
|
-
|
|
12363
|
+
// R493 — zoom +/− buttons join the chrome-strip active:scale-95
|
|
12364
|
+
// press-feedback family (R492 + nodeSize above). transition-
|
|
12365
|
+
// transform + duration-200 + ease-out + transform-gpu added
|
|
12366
|
+
// since the className had only transition-colors.
|
|
12367
|
+
className="group px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors transition-transform duration-200 ease-out transform-gpu active:scale-95 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
|
|
11174
12368
|
style={{ color: pal.legendText }}
|
|
11175
12369
|
aria-label="Zoom in"
|
|
11176
12370
|
title="Zoom in (+)"
|
|
@@ -11222,7 +12416,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11222
12416
|
Every standalone interactive HTML surface in TopoGraph
|
|
11223
12417
|
now lifts on hover. data-topo-chrome-reset-hover-lift
|
|
11224
12418
|
attr surfaces the lift for tests. */
|
|
11225
|
-
|
|
12419
|
+
// R493 — reset button joins the chrome-strip active:scale-95
|
|
12420
|
+
// press-feedback family. The button already has transition-
|
|
12421
|
+
// transform + transform-gpu (R350 reset spin + R400 hover lift),
|
|
12422
|
+
// so just appending active:scale-95 plugs straight in. Compound
|
|
12423
|
+
// active state during press = hover-lift (-1px) + scale-95
|
|
12424
|
+
// composes as translateY(-1px) scale(0.95) — lift-and-compress
|
|
12425
|
+
// for tactile click feel.
|
|
12426
|
+
className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px active:scale-95 transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
|
|
11226
12427
|
data-topo-chrome-reset-hover-lift="true"
|
|
11227
12428
|
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
|
|
11228
12429
|
aria-label="Reset view"
|
|
@@ -11270,8 +12471,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11270
12471
|
// owns transform during its 450ms run. transformOrigin
|
|
11271
12472
|
// 'center' so rotation pivots around the icon's centre
|
|
11272
12473
|
// (default would be top-left and the icon would arc).
|
|
12474
|
+
/* Round 514 / Loop — extends R352/R353 chrome icon hover-
|
|
12475
|
+
scale family to the reset button. Pre-R514 the reset
|
|
12476
|
+
icon had hover-rotate (-8°, R350) + hover-sw (2.5→2.8,
|
|
12477
|
+
R453) but no hover-scale, while zoom-out (R352), zoom-
|
|
12478
|
+
in (R352), and fullscreen (R353) icons all carried
|
|
12479
|
+
`group-hover:scale-110`. R514 brings the reset icon
|
|
12480
|
+
into the same 3-axis hover signature (rotate + sw +
|
|
12481
|
+
scale) as the rest of the chrome strip.
|
|
12482
|
+
Implementation: inline transform composes rotate +
|
|
12483
|
+
scale into one string. `transform: rotate(-8deg)
|
|
12484
|
+
scale(1.1)` on hover; `rotate(0) scale(1)` at rest.
|
|
12485
|
+
transformOrigin 'center' applies to both — rotation
|
|
12486
|
+
pivots around centre AND scale grows from centre.
|
|
12487
|
+
The Tailwind `group-hover:scale-110` approach can't
|
|
12488
|
+
work here because inline `style.transform` overrides
|
|
12489
|
+
className-based transforms; compose the multi-axis
|
|
12490
|
+
transform inline instead.
|
|
12491
|
+
Chrome icon hover gesture parity (post-R514):
|
|
12492
|
+
zoom-out scale-110 + sw-lift (R352/R454)
|
|
12493
|
+
zoom-in scale-110 + sw-lift (R352/R454)
|
|
12494
|
+
fullscreen scale-110 + sw-lift (R353/R455)
|
|
12495
|
+
reset scale-1.1 + sw-lift + rotate -8°
|
|
12496
|
+
(R514 + R453 + R350)
|
|
12497
|
+
reset gets the EXTRA rotate axis because R350's spin
|
|
12498
|
+
preview semantic is reset-specific — the rotation
|
|
12499
|
+
hints at the click-spin (R184) the button will fire. */
|
|
11273
12500
|
style={{
|
|
11274
|
-
transform: hoveredReset && !resetSpinning ? 'rotate(-8deg)' : 'rotate(0deg)',
|
|
12501
|
+
transform: hoveredReset && !resetSpinning ? 'rotate(-8deg) scale(1.1)' : 'rotate(0deg) scale(1)',
|
|
11275
12502
|
transformOrigin: 'center',
|
|
11276
12503
|
transition: 'transform 200ms ease-out, stroke-width 200ms ease-out',
|
|
11277
12504
|
}}
|
|
@@ -11315,7 +12542,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
11315
12542
|
// fullscreen now all carry an icon-level hover gesture in
|
|
11316
12543
|
// addition to the bg hover).
|
|
11317
12544
|
// R400: hover translateY(-1px) lift — see reset button above for family doc.
|
|
11318
|
-
|
|
12545
|
+
// R493 — fullscreen joins active:scale-95 press family (same as
|
|
12546
|
+
// reset above: lift-and-compress compound transform on press).
|
|
12547
|
+
className={`group p-1.5 rounded-md border hover:-translate-y-px active:scale-95 transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
|
|
11319
12548
|
isFullscreen
|
|
11320
12549
|
? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'
|
|
11321
12550
|
: 'hover:bg-cyan-500/5 active:bg-cyan-500/15'
|