@sleep2agi/agent-network-dashboard 0.5.1-preview.14 → 0.5.1-preview.140
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/prerender-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.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +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]__03b.f76._.js → [root-of-the-server]__0sv~g.o._.js} +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__0sv~g.o._.js.map +1 -0
- 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/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{0l_~q07bhpkcx.js → 03a4--7ncekmk.js} +1 -1
- package/.next/static/chunks/0a4hmfvj-81x5.css +2 -0
- package/.next/static/chunks/0hkc0z2pgava~.js +1 -0
- package/.next/static/chunks/0xv-z5onm7655.js +1 -0
- package/.next/static/chunks/{03~~oirxz7~vc.js → 0xv9kehogd-35.js} +1 -1
- package/.next/static/chunks/18aaelbu9gx4w.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/ServersDrawer.tsx +17 -0
- package/app/components/TopoGraph.tsx +2538 -187
- package/package.json +1 -1
- package/screenshots/v0.10.2-disk-verify/disk-render-full.png +0 -0
- package/screenshots/v0.10.2-disk-verify/disk-render.png +0 -0
- package/screenshots/v0.11.0-147/after.png +0 -0
- package/scripts/p0-147-screenshot.mjs +53 -0
- package/scripts/p0-servers-drawer-screenshot.mjs +2 -0
- package/scripts/topo-active-links-chip-hover-lift-test.mjs +93 -0
- package/scripts/topo-chip-digit-fontweight-test.mjs +105 -0
- package/scripts/topo-chip-digit-hover-bold-test.mjs +94 -0
- package/scripts/topo-chip-row-group-hover-brighten-test.mjs +107 -0
- package/scripts/topo-chip-row-hover-lift-test.mjs +95 -0
- package/scripts/topo-chrome-button-hover-lift-test.mjs +94 -0
- package/scripts/topo-chrome-fullscreen-icon-sw-test.mjs +92 -0
- package/scripts/topo-chrome-reset-icon-sw-test.mjs +80 -0
- package/scripts/topo-chrome-segmented-radius-test.mjs +100 -0
- package/scripts/topo-chrome-zoom-icon-sw-test.mjs +90 -0
- package/scripts/topo-click-ripple-opacity-test.mjs +99 -0
- package/scripts/topo-dashboard-version-attr-test.mjs +69 -0
- package/scripts/topo-dense-alias-opacity-test.mjs +68 -0
- package/scripts/topo-edge-badge-digit-fw-test.mjs +103 -0
- package/scripts/topo-edge-badge-fontsize-test.mjs +90 -0
- package/scripts/topo-edge-badge-hover-opacity-test.mjs +94 -0
- package/scripts/topo-edge-badge-hover-stroke-test.mjs +92 -0
- package/scripts/topo-edge-badge-letter-spacing-test.mjs +86 -0
- package/scripts/topo-edge-badge-opacity-test.mjs +80 -0
- package/scripts/topo-edge-badge-pin-opacity-test.mjs +86 -0
- package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
- package/scripts/topo-edge-endpoint-hover-sw-test.mjs +127 -0
- package/scripts/topo-edge-freshness-floor-test.mjs +99 -0
- package/scripts/topo-edge-particle-hover-r-test.mjs +113 -0
- package/scripts/topo-edge-particle-radius-test.mjs +76 -0
- package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
- package/scripts/topo-endpoint-ring-r-hover-test.mjs +89 -0
- package/scripts/topo-filter-pill-hover-lift-test.mjs +101 -0
- package/scripts/topo-filter-pill-hover-opacity-test.mjs +110 -0
- package/scripts/topo-filter-pill-value-fw-test.mjs +88 -0
- package/scripts/topo-filter-pill-x-hover-scale-test.mjs +99 -0
- package/scripts/topo-flow-rail-hover-sw-test.mjs +113 -0
- package/scripts/topo-flow-rail-linecap-test.mjs +79 -0
- package/scripts/topo-freshness-chip-hierarchy-test.mjs +93 -0
- package/scripts/topo-freshness-chip-tabular-test.mjs +41 -0
- package/scripts/topo-freshness-floor-lift-test.mjs +92 -0
- package/scripts/topo-freshness-suffix-tabular-test.mjs +88 -0
- package/scripts/topo-group-box-geom-transition-test.mjs +110 -0
- package/scripts/topo-group-box-rx-pin-test.mjs +103 -0
- package/scripts/topo-group-box-stroke-test.mjs +105 -0
- package/scripts/topo-group-label-count-fontweight-test.mjs +108 -0
- package/scripts/topo-group-label-count-fw-test.mjs +100 -0
- package/scripts/topo-group-label-fw-pin-test.mjs +99 -0
- package/scripts/topo-group-label-letter-spacing-test.mjs +113 -0
- package/scripts/topo-group-label-tint-geom-test.mjs +94 -0
- package/scripts/topo-group-label-tint-transition-test.mjs +97 -0
- package/scripts/topo-group-pip-fontsize-test.mjs +106 -0
- package/scripts/topo-hover-detail-body-fw-test.mjs +101 -0
- package/scripts/topo-hover-detail-model-fw-test.mjs +98 -0
- package/scripts/topo-hover-detail-opacity-test.mjs +98 -0
- package/scripts/topo-hover-detail-rx-test.mjs +81 -0
- package/scripts/topo-hub-core-fill-hover-test.mjs +85 -0
- package/scripts/topo-hub-digit-fontsize-test.mjs +86 -0
- package/scripts/topo-hub-digit-fw-hover-test.mjs +102 -0
- package/scripts/topo-hub-halo-light-trough-test.mjs +88 -0
- package/scripts/topo-hub-halo-r-hover-test.mjs +82 -0
- package/scripts/topo-hub-halo-radius-test.mjs +86 -0
- package/scripts/topo-hub-halo-trough-test.mjs +83 -0
- package/scripts/topo-hub-highlight-opacity-test.mjs +88 -0
- package/scripts/topo-hub-highlight-radius-test.mjs +90 -0
- package/scripts/topo-hub-hover-ring-opacity-test.mjs +96 -0
- package/scripts/topo-hub-hover-ring-stroke-test.mjs +86 -0
- package/scripts/topo-hub-spoke-hover-opacity-test.mjs +119 -0
- package/scripts/topo-hub-spoke-hover-sw-test.mjs +121 -0
- package/scripts/topo-hub-spoke-linecap-test.mjs +80 -0
- package/scripts/topo-label-card-opacity-hover-test.mjs +99 -0
- package/scripts/topo-layout-toggle-radius-test.mjs +87 -0
- package/scripts/topo-legend-count-active-opacity-test.mjs +102 -0
- package/scripts/topo-legend-count-pin-fw-test.mjs +90 -0
- package/scripts/topo-legend-label-fontweight-test.mjs +94 -0
- package/scripts/topo-legend-pin-ring-stroke-test.mjs +101 -0
- package/scripts/topo-legend-row-letter-spacing-test.mjs +101 -0
- package/scripts/topo-minimap-offline-opacity-test.mjs +90 -0
- package/scripts/topo-minimap-online-hover-opacity-test.mjs +92 -0
- package/scripts/topo-minimap-online-opacity-test.mjs +93 -0
- package/scripts/topo-minimap-online-radius-test.mjs +85 -0
- package/scripts/topo-minimap-viewport-linejoin-test.mjs +75 -0
- package/scripts/topo-minimap-viewport-opacity-test.mjs +96 -0
- package/scripts/topo-minimap-viewport-rx-test.mjs +85 -0
- package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
- package/scripts/topo-node-alias-letter-spacing-test.mjs +112 -0
- package/scripts/topo-node-halo-hover-opacity-test.mjs +104 -0
- package/scripts/topo-node-halo-light-offline-test.mjs +80 -0
- package/scripts/topo-node-halo-offline-opacity-test.mjs +87 -0
- package/scripts/topo-node-label-card-rx-test.mjs +85 -0
- package/scripts/topo-node-pulse-peak-test.mjs +89 -0
- package/scripts/topo-node-pulse-trough-test.mjs +91 -0
- package/scripts/topo-node-sub-text-fw-test.mjs +75 -0
- package/scripts/topo-node-sub-text-letter-spacing-test.mjs +115 -0
- package/scripts/topo-overlap-stale-build-guard-test.mjs +66 -0
- package/scripts/topo-overlap-test.mjs +42 -1
- package/scripts/topo-panel-count-fw-hover-test.mjs +105 -0
- package/scripts/topo-panel-stroke-hover-test.mjs +110 -0
- package/scripts/topo-pressure-bar-height-test.mjs +92 -0
- package/scripts/topo-pressure-kicker-fontweight-test.mjs +76 -0
- package/scripts/topo-recent-pip-radius-2-test.mjs +72 -0
- package/scripts/topo-recent-pip-radius-test.mjs +76 -0
- package/scripts/topo-recent-row-content-opacity-test.mjs +81 -0
- package/scripts/topo-recent-row-count-pin-fw-test.mjs +106 -0
- package/scripts/topo-recent-row-letter-spacing-test.mjs +109 -0
- package/scripts/topo-recent-row-pip-hover-r-test.mjs +104 -0
- package/scripts/topo-recent-row-text-fontweight-test.mjs +90 -0
- package/scripts/topo-runtime-icon-hover-test.mjs +96 -0
- package/scripts/topo-spoke-active-opacity-test.mjs +104 -0
- package/scripts/topo-spoke-active-stroke-test.mjs +95 -0
- package/scripts/topo-spoke-idle-opacity-test.mjs +91 -0
- package/scripts/topo-status-ring-hover-sw-test.mjs +105 -0
- package/scripts/topo-vendor-chip-hover-lift-test.mjs +87 -0
- package/scripts/topo-vendor-glyph-fontweight-test.mjs +102 -0
- package/scripts/topo-vendor-letter-hover-scale-test.mjs +129 -0
- package/scripts/topo-vendor-suffix-hover-brighten-test.mjs +87 -0
- package/scripts/topo-zoom-level-hover-fw-test.mjs +95 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +0 -1
- package/.next/static/chunks/03mfrl9fd9smg.js +0 -1
- package/.next/static/chunks/0kt7qs66ra2.2.css +0 -2
- package/.next/static/chunks/116zy6jdhuaa2.js +0 -1
- package/.next/static/chunks/12m~64tlig.y4.js +0 -4
- /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → nLppKA36xKrZ-Bv6UcCHa}/_buildManifest.js +0 -0
- /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → nLppKA36xKrZ-Bv6UcCHa}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{rDAS8dVgx4ldg7AtRGdUB → nLppKA36xKrZ-Bv6UcCHa}/_ssgManifest.js +0 -0
|
@@ -8,6 +8,7 @@ import { aliasAvatarColors, aliasInitial } from './AliasAvatar';
|
|
|
8
8
|
import { ChatPopover } from './ChatPopover';
|
|
9
9
|
import { vendorForModel, runtimeIdentity, identityLine } from '../lib/vendorIdentity';
|
|
10
10
|
import { parseHubTime, relativeAgo } from '../lib/time';
|
|
11
|
+
import { DASHBOARD_VERSION } from '../lib/version';
|
|
11
12
|
|
|
12
13
|
/** v0.10.0 Hero 1+2 / §3.F server-health hook — fetches the normalized
|
|
13
14
|
* /api/hub/servers payload (preview.370 unblocked real-data via the
|
|
@@ -194,7 +195,27 @@ function FreshnessChip({ sessions }: { sessions: unknown }) {
|
|
|
194
195
|
// tier (R312-R314 family); the amber bg/text/border still does
|
|
195
196
|
// the warning-state work, the weight just keeps the chip in the
|
|
196
197
|
// same data-typography ladder as its siblings.
|
|
197
|
-
|
|
198
|
+
// Round 377 / Loop: FreshnessChip baseClass picks up `tabular-nums`.
|
|
199
|
+
// The chip-row's last untouched chip joins the R224-R357 broader
|
|
200
|
+
// tabular-nums sweep:
|
|
201
|
+
// R224 edge badge digit
|
|
202
|
+
// R225 hub digit / panel header counts / recent row count
|
|
203
|
+
// R232 chip row counts (working / online / active-links)
|
|
204
|
+
// R321 recent row timestamp
|
|
205
|
+
// R322 recent panel hot count
|
|
206
|
+
// R323 filter pin pill counts
|
|
207
|
+
// R333 vendor count suffix
|
|
208
|
+
// R357 active-links freshness suffix wrapper
|
|
209
|
+
// R377 FreshnessChip body (this round)
|
|
210
|
+
// `font-mono` already gives equal-width glyphs but `tabular-nums`
|
|
211
|
+
// is the explicit-invariant the rest of the chip row carries.
|
|
212
|
+
// FreshnessChip body reads `lag · {sec}s` — the {sec} digit grows
|
|
213
|
+
// every second; tabular-nums explicitly locks digit width so the
|
|
214
|
+
// chip stays planted as the seconds counter ticks past 9 → 10 →
|
|
215
|
+
// 99 → 100. R187 transition-colors duration-300 + R275 stale-only
|
|
216
|
+
// render gate + R315 font-medium R313 family alignment all
|
|
217
|
+
// preserved.
|
|
218
|
+
const baseClass = "hidden sm:inline px-2.5 py-1 rounded-md font-mono font-medium tabular-nums border transition-colors duration-300";
|
|
198
219
|
const colorClass = stale
|
|
199
220
|
? "bg-amber-500/10 text-amber-300 border-amber-500/20"
|
|
200
221
|
: "bg-gray-500/10 text-gray-400 border-gray-500/20";
|
|
@@ -237,7 +258,21 @@ function FreshnessChip({ sessions }: { sessions: unknown }) {
|
|
|
237
258
|
duration-300 still eases the bg/color flip. Title (hover
|
|
238
259
|
tooltip) still spells out the full meaning in either
|
|
239
260
|
state. */}
|
|
240
|
-
{
|
|
261
|
+
{/* Round 410 / Loop: FreshnessChip body picks up the chip-
|
|
262
|
+
internal-hierarchy arc. Pre-R410 the body rendered as a
|
|
263
|
+
single text node `lag · {sec}s` with the parent's font-
|
|
264
|
+
medium (fw=500) applied uniformly. R410 splits the digit
|
|
265
|
+
and unit into separate spans so the chip's internal
|
|
266
|
+
typography mirrors the family pattern R333-R341/R362/R369/
|
|
267
|
+
R389 established for the chip row:
|
|
268
|
+
digit (fw=600) data tier
|
|
269
|
+
unit (fw=500 + opacity=0.7) label tier
|
|
270
|
+
The `lag` prefix stays at the chip's baseline (fw=500
|
|
271
|
+
from parent font-medium) — it labels the state, not a
|
|
272
|
+
data value. data-freshness-chip-digit / -unit attrs
|
|
273
|
+
surface the spans for tests. tabular-nums + transition-
|
|
274
|
+
colors + R275 stale-only gate all preserved. */}
|
|
275
|
+
{stale ? 'lag' : 'live'} · <span className="font-semibold" data-freshness-chip-digit>{sec}</span><span className="opacity-70" data-freshness-chip-unit>s</span>
|
|
241
276
|
</span>
|
|
242
277
|
);
|
|
243
278
|
}
|
|
@@ -1823,12 +1858,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1823
1858
|
while honoring R328's wider baseline rhythm. data-topo-
|
|
1824
1859
|
chrome-layout-trailer attr unchanged — it still marks
|
|
1825
1860
|
the boundary surface for the gap probe. */}
|
|
1861
|
+
{/* Round 375 / Loop: Layout-toggle wrapper rounded-md → rounded-
|
|
1862
|
+
lg (6 → 8 px). Extends the corner-radius cascade family
|
|
1863
|
+
to the chrome-strip layout-toggle wrapper:
|
|
1864
|
+
R330 canvas wrapper rounded-xl 12 px
|
|
1865
|
+
R331 SVG panels rx=10 10 px
|
|
1866
|
+
R332 minimap container rounded-lg 8 px
|
|
1867
|
+
R375 Layout-toggle wrapper rounded-lg 8 px (this round)
|
|
1868
|
+
Pre-R375 the wrapper at rounded-md (6 px) was the only
|
|
1869
|
+
chrome-strip container still using the smaller corner
|
|
1870
|
+
radius — both R330 outer wrapper and R332 minimap sit at
|
|
1871
|
+
≥ 8 px, so the Layout toggle's 6 px stood out as a
|
|
1872
|
+
tighter corner against the family. R375 brings it into
|
|
1873
|
+
the rounded-lg tier where the minimap already lives.
|
|
1874
|
+
Pure paint change — overflow-hidden still clips the
|
|
1875
|
+
inner buttons' bg-cyan-500/15 tints; no layout shift.
|
|
1876
|
+
R268 border-color + 200ms transition + R329 mr-0.5 +
|
|
1877
|
+
data-topo-chrome-layout-trailer all preserved. */}
|
|
1826
1878
|
<div
|
|
1827
|
-
className="mr-0.5 inline-flex rounded-
|
|
1879
|
+
className="mr-0.5 inline-flex rounded-lg border overflow-hidden"
|
|
1828
1880
|
style={{ borderColor: pal.containerBorder, transition: 'border-color 200ms ease-out' }}
|
|
1829
1881
|
role="group"
|
|
1830
1882
|
aria-label="Topology layout"
|
|
1831
1883
|
data-topo-chrome-layout-trailer
|
|
1884
|
+
data-topo-chrome-layout-radius="rounded-lg"
|
|
1832
1885
|
>
|
|
1833
1886
|
<button
|
|
1834
1887
|
onClick={() => { popChrome('layout-ring'); if (layout !== 'ring') toggleLayout(); }}
|
|
@@ -1989,11 +2042,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1989
2042
|
7th surface in the info-density tabular-nums
|
|
1990
2043
|
sweep — and the first on the HTML side
|
|
1991
2044
|
(previous 6 were SVG <text>/<tspan>). */
|
|
1992
|
-
|
|
2045
|
+
/* Round 398 / Loop: chip-row chips gain hover translateY
|
|
2046
|
+
(-1px) lift on the CLICKABLE variant only (workingCount
|
|
2047
|
+
> 0 here / onlineNodes.length > 0 below / activeLinks
|
|
2048
|
+
> 0 deeper). Pre-R398 the chips brightened bg + border
|
|
2049
|
+
on hover (R201) but didn't lift — only their clickable
|
|
2050
|
+
siblings (filter pin pills R397, recent rows R143,
|
|
2051
|
+
legend rows R144) acknowledged cursor entry with a
|
|
2052
|
+
translate-y. R398 closes the chip-row by extending
|
|
2053
|
+
the same gesture to the static header chips, gated
|
|
2054
|
+
on the clickable role so empty chips (which have
|
|
2055
|
+
no role="button") stay planted at their R205
|
|
2056
|
+
opacity-50 receded paint. transition-transform
|
|
2057
|
+
+ duration-200 + ease-out + transform-gpu added
|
|
2058
|
+
alongside existing transition-colors so the lift
|
|
2059
|
+
and the color tween share rhythm.
|
|
2060
|
+
Gesture-vocabulary table (post-R398):
|
|
2061
|
+
recent-signal row -1 px (R143)
|
|
2062
|
+
legend row -1 px (R144)
|
|
2063
|
+
group cluster box fill+sw lift (R142)
|
|
2064
|
+
filter pin pills -1 px (R397)
|
|
2065
|
+
chip-row chips -1 px (R398, this round)
|
|
2066
|
+
Empty chips: no lift. Pin-mirror chips: no
|
|
2067
|
+
conflict (R180 inset double-ring is a box-shadow
|
|
2068
|
+
not a transform). new data-chip-hover-lift attr
|
|
2069
|
+
surfaces the lift surface for tests. */
|
|
2070
|
+
// R414: chip-row chips gain `group` so inner unit
|
|
2071
|
+
// span brightens via group-hover:opacity-100 — sibling
|
|
2072
|
+
// to R355 filter pin pill inner-span hover-brighten.
|
|
2073
|
+
// Hover-brighten family extends from filter pills to
|
|
2074
|
+
// chip-row chips at the inner-span scope.
|
|
2075
|
+
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 ${
|
|
1993
2076
|
workingCount > 0
|
|
1994
|
-
? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30'
|
|
2077
|
+
? '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'
|
|
1995
2078
|
: 'bg-green-500/10 text-green-300 border-green-500/20'
|
|
1996
2079
|
}`}
|
|
2080
|
+
data-chip-hover-lift={workingCount > 0 ? 'true' : 'false'}
|
|
2081
|
+
data-chip-group-hover-brighten="true"
|
|
1997
2082
|
data-working-chip
|
|
1998
2083
|
data-working-chip-aliases={workingAliases.join(',')}
|
|
1999
2084
|
data-pin-mirror={pinnedStatus === 'working' ? 'true' : 'false'}
|
|
@@ -2063,7 +2148,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2063
2148
|
pattern: small label spans demote, value stays
|
|
2064
2149
|
prominent. data-working-chip-unit exposes the
|
|
2065
2150
|
span for tests. */}
|
|
2066
|
-
{
|
|
2151
|
+
{/* Round 362 / Loop: digit picks up font-semibold
|
|
2152
|
+
(fw 500 → 600) for within-chip weight tier. The
|
|
2153
|
+
chip's outer className stays at font-medium (R313
|
|
2154
|
+
data-weight baseline); the digit overrides to
|
|
2155
|
+
semibold so it reads heavier than its " working"
|
|
2156
|
+
unit (which keeps fw 500 + R338 opacity-70).
|
|
2157
|
+
Joins the R333-R341 chip-internal-hierarchy arc
|
|
2158
|
+
at the chip-count scope. Sibling edits on the
|
|
2159
|
+
online + active-links chip digits below. data-
|
|
2160
|
+
working-chip-digit attr exposes the digit span. */}
|
|
2161
|
+
<span className="font-semibold transition-[font-weight] duration-200 group-hover:font-bold" data-working-chip-digit>{workingCount}</span><span className="opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-working-chip-unit> working</span>
|
|
2067
2162
|
</span>
|
|
2068
2163
|
<span
|
|
2069
2164
|
// Round 201 / Loop: online chip — mirror of the working
|
|
@@ -2077,11 +2172,15 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2077
2172
|
/* Round 232 / Loop: tabular-nums on online chip
|
|
2078
2173
|
(sibling treatment to working chip — same row,
|
|
2079
2174
|
same digit-jitter physics on count crossings). */
|
|
2080
|
-
|
|
2175
|
+
// R398: hover translate-y lift on clickable variant — see working chip above.
|
|
2176
|
+
// R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
|
|
2177
|
+
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 ${
|
|
2081
2178
|
onlineNodes.length > 0
|
|
2082
|
-
? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30'
|
|
2179
|
+
? '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'
|
|
2083
2180
|
: 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'
|
|
2084
2181
|
}`}
|
|
2182
|
+
data-chip-hover-lift={onlineNodes.length > 0 ? 'true' : 'false'}
|
|
2183
|
+
data-chip-group-hover-brighten="true"
|
|
2085
2184
|
data-online-chip
|
|
2086
2185
|
data-online-chip-aliases={onlineAliases.join(',')}
|
|
2087
2186
|
data-pin-mirror={pinnedStatus === 'idle' ? 'true' : 'false'}
|
|
@@ -2126,7 +2225,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2126
2225
|
}}
|
|
2127
2226
|
>
|
|
2128
2227
|
{/* R337 sibling — online chip unit demotion. */}
|
|
2129
|
-
{
|
|
2228
|
+
{/* R362 sibling — online-chip digit gains font-semibold. */}
|
|
2229
|
+
<span className="font-semibold transition-[font-weight] duration-200 group-hover:font-bold" data-online-chip-digit>{onlineNodes.length}</span><span className="opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-online-chip-unit> online</span>
|
|
2130
2230
|
</span>
|
|
2131
2231
|
</>
|
|
2132
2232
|
);
|
|
@@ -2253,8 +2353,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2253
2353
|
title={`${w} working · ${i} idle · ${o} offline`}
|
|
2254
2354
|
data-fleet-pressure
|
|
2255
2355
|
>
|
|
2256
|
-
|
|
2257
|
-
|
|
2356
|
+
{/* Round 373 / Loop: pressure-bar kicker label gains
|
|
2357
|
+
font-medium (fw 400 → 500). Sibling small-text fw
|
|
2358
|
+
lift family with R363 recent-row alias + R364
|
|
2359
|
+
legend-row label + R366 group-label count + R368
|
|
2360
|
+
+N more flows footer — extends to a 5th surface
|
|
2361
|
+
(the chip-row's 'pressure' label). At fontSize
|
|
2362
|
+
10 px tracking-wide against the chip's gray bg,
|
|
2363
|
+
the default fw 400 sat below the deliberate-data
|
|
2364
|
+
band; fw 500 brings it into parity with the
|
|
2365
|
+
chip-row 'working / online / active links' unit
|
|
2366
|
+
spans (chip-level font-medium R313). data-fleet-
|
|
2367
|
+
pressure-kicker attr exposes the kicker for tests. */}
|
|
2368
|
+
<span className="text-[10px] tracking-wide font-medium" data-fleet-pressure-kicker>pressure</span>
|
|
2369
|
+
{/* Round 374 / Loop: pressure-bar height h-1.5 → h-2
|
|
2370
|
+
(6 → 8 px) — sibling visual-weight bump (8th anchor
|
|
2371
|
+
in the family):
|
|
2372
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
2373
|
+
R295 legend swatch base radius 5.5 → 6
|
|
2374
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
2375
|
+
R360 hub digit fontSize 11 → 12
|
|
2376
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
2377
|
+
R365 hub-highlight base radius 5 → 5.5
|
|
2378
|
+
R367 edge-badge rest stroke 1 → 1.25
|
|
2379
|
+
R374 pressure-bar height 1.5 → 2 (this round)
|
|
2380
|
+
+33 % bar height gives the working/idle/offline
|
|
2381
|
+
segments more visibility — at h-1.5 the 3-segment
|
|
2382
|
+
proportion bar was readable but slim; at h-2 the
|
|
2383
|
+
segments parse cleanly even when one tier is
|
|
2384
|
+
< 10 % share. Geometry-safe: items-center flex
|
|
2385
|
+
centers the bar inside the chip's py-1 (4 px top +
|
|
2386
|
+
4 px bottom) — bar at 8 px stays comfortably
|
|
2387
|
+
inside the 10-px text-row height. R165 segment
|
|
2388
|
+
width transitions + R210 brightness hover + R83
|
|
2389
|
+
pin-mirror box-shadow on segments all preserved
|
|
2390
|
+
(segments inherit width from parent so the height
|
|
2391
|
+
bump propagates without segment-side edits).
|
|
2392
|
+
data-fleet-pressure-bar-height attr exposes the
|
|
2393
|
+
height token for tests. */}
|
|
2394
|
+
<span className="inline-flex h-2 w-16 rounded-full overflow-hidden" style={{ background: 'rgb(75 85 99 / 0.25)' }} data-fleet-pressure-bar-height="h-2">
|
|
2258
2395
|
{seg(w, isLight ? '#059669' : '#22c55e', 'working', 'working')}
|
|
2259
2396
|
{seg(i, isLight ? '#0d9488' : '#2dd4bf', 'idle', 'idle')}
|
|
2260
2397
|
{seg(o, isLight ? '#94a3b8' : '#6b7280', 'offline', 'offline')}
|
|
@@ -2311,7 +2448,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2311
2448
|
data-active-filter="status"
|
|
2312
2449
|
data-filter-match-count={matchCount}
|
|
2313
2450
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2314
|
-
|
|
2451
|
+
// R355: `group` lets the inner opacity-70 spans (prefix
|
|
2452
|
+
// `filter:` + count `· N`) brighten to 100 % on pill hover.
|
|
2453
|
+
// Sibling treatment on group + vendor pills below.
|
|
2454
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2315
2455
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2316
2456
|
onClick={() => setPinnedStatus(null)}
|
|
2317
2457
|
style={{
|
|
@@ -2325,12 +2465,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2325
2465
|
cursor: 'pointer',
|
|
2326
2466
|
}}
|
|
2327
2467
|
>
|
|
2328
|
-
|
|
2468
|
+
{/* Round 412 / Loop: filter pin pill VALUE picks up the
|
|
2469
|
+
chip-internal-hierarchy arc. Pre-R412 the value span
|
|
2470
|
+
(pinnedStatus / pinnedGroup / pinnedVendor) inherited
|
|
2471
|
+
the parent's font-medium (fw=500); prefix and suffix
|
|
2472
|
+
were opacity-70 label-tier but the VALUE itself sat
|
|
2473
|
+
at the same baseline weight. R412 wraps the value in
|
|
2474
|
+
a font-semibold span (fw=600) so the pill now reads
|
|
2475
|
+
with proper data-tier emphasis — sibling treatment
|
|
2476
|
+
to R333/R335-R341/R362/R369/R389/R410. data-filter-
|
|
2477
|
+
value attr surfaces the value span for tests.
|
|
2478
|
+
4-pill replace family — status / group / vendor / edge. */}
|
|
2479
|
+
<span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedStatus}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
2329
2480
|
<button
|
|
2330
2481
|
type="button"
|
|
2331
2482
|
aria-label={`Clear ${pinnedStatus} filter`}
|
|
2332
2483
|
onClick={(e) => { e.stopPropagation(); setPinnedStatus(null); }}
|
|
2333
|
-
|
|
2484
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2485
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2486
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2487
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2488
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2489
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2490
|
+
scale on hover so the click-target reads as "press me"
|
|
2491
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2492
|
+
button to its own compositor layer for crisper edges
|
|
2493
|
+
during the scale tween. transition-transform duration-
|
|
2494
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2495
|
+
inline-block is default for <button> so no display
|
|
2496
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2497
|
+
pills (status / group / vendor / edge) at once. */
|
|
2498
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2334
2499
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2335
2500
|
>×</button>
|
|
2336
2501
|
</span>
|
|
@@ -2349,7 +2514,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2349
2514
|
data-active-filter="group"
|
|
2350
2515
|
data-filter-match-count={matchCount}
|
|
2351
2516
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2352
|
-
|
|
2517
|
+
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2518
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2353
2519
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}
|
|
2354
2520
|
onClick={() => setPinnedGroup(null)}
|
|
2355
2521
|
style={{
|
|
@@ -2359,12 +2525,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2359
2525
|
cursor: 'pointer',
|
|
2360
2526
|
}}
|
|
2361
2527
|
>
|
|
2362
|
-
|
|
2528
|
+
{/* R412: see status pill above — filter value fw=600 data tier. */}
|
|
2529
|
+
<span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedGroup}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
2363
2530
|
<button
|
|
2364
2531
|
type="button"
|
|
2365
2532
|
aria-label={`Clear group filter ${pinnedGroup}`}
|
|
2366
2533
|
onClick={(e) => { e.stopPropagation(); setPinnedGroup(null); }}
|
|
2367
|
-
|
|
2534
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2535
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2536
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2537
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2538
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2539
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2540
|
+
scale on hover so the click-target reads as "press me"
|
|
2541
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2542
|
+
button to its own compositor layer for crisper edges
|
|
2543
|
+
during the scale tween. transition-transform duration-
|
|
2544
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2545
|
+
inline-block is default for <button> so no display
|
|
2546
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2547
|
+
pills (status / group / vendor / edge) at once. */
|
|
2548
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2368
2549
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2369
2550
|
>×</button>
|
|
2370
2551
|
</span>
|
|
@@ -2399,7 +2580,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2399
2580
|
data-active-filter="vendor"
|
|
2400
2581
|
data-filter-match-count={matchCount}
|
|
2401
2582
|
data-filter-match-aliases={matchAliases.join(',')}
|
|
2402
|
-
|
|
2583
|
+
// R355 sibling — `group` parent + group-hover on inner spans.
|
|
2584
|
+
className="group inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2403
2585
|
title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}
|
|
2404
2586
|
onClick={() => setPinnedVendor(null)}
|
|
2405
2587
|
style={{
|
|
@@ -2409,12 +2591,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2409
2591
|
cursor: 'pointer',
|
|
2410
2592
|
}}
|
|
2411
2593
|
>
|
|
2412
|
-
|
|
2594
|
+
{/* R412: see status pill above — filter value fw=600 data tier. */}
|
|
2595
|
+
<span><span className="hidden sm:inline opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-filter-prefix>filter: </span><span className="font-semibold" data-filter-value>{pinnedVendor}</span><span className="opacity-70 tabular-nums transition-opacity duration-200 group-hover:opacity-100" data-filter-pill-count> · {matchCount}</span></span>
|
|
2413
2596
|
<button
|
|
2414
2597
|
type="button"
|
|
2415
2598
|
aria-label={`Clear vendor filter ${pinnedVendor}`}
|
|
2416
2599
|
onClick={(e) => { e.stopPropagation(); setPinnedVendor(null); }}
|
|
2417
|
-
|
|
2600
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2601
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2602
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2603
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2604
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2605
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2606
|
+
scale on hover so the click-target reads as "press me"
|
|
2607
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2608
|
+
button to its own compositor layer for crisper edges
|
|
2609
|
+
during the scale tween. transition-transform duration-
|
|
2610
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2611
|
+
inline-block is default for <button> so no display
|
|
2612
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2613
|
+
pills (status / group / vendor / edge) at once. */
|
|
2614
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2418
2615
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2419
2616
|
>×</button>
|
|
2420
2617
|
</span>
|
|
@@ -2447,7 +2644,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2447
2644
|
data-filter-match-count={link.count}
|
|
2448
2645
|
data-filter-match-aliases={`${link.from},${link.to}`}
|
|
2449
2646
|
data-active-filter-edge-hot={isHot ? 'true' : 'false'}
|
|
2450
|
-
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"
|
|
2647
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus transition-transform duration-200 ease-out hover:-translate-y-px transform-gpu" data-topo-filter-pill-hover-lift="true"
|
|
2451
2648
|
title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}
|
|
2452
2649
|
onClick={() => setPinnedEdgeKey(null)}
|
|
2453
2650
|
style={{
|
|
@@ -2457,9 +2654,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2457
2654
|
cursor: 'pointer',
|
|
2458
2655
|
}}
|
|
2459
2656
|
>
|
|
2657
|
+
{/* R412: filter pin pill value (edge variant) picks up fw=600.
|
|
2658
|
+
Sibling treatment to the status/group/vendor pills above. */}
|
|
2460
2659
|
<span>
|
|
2461
2660
|
<span className="hidden sm:inline opacity-70" data-filter-prefix>filter: </span>
|
|
2462
|
-
{link.from}→{link.to}
|
|
2661
|
+
<span className="font-semibold" data-filter-value>{link.from}→{link.to}</span>
|
|
2463
2662
|
{/* Round 323 / Loop: edge filter pill count digit picks
|
|
2464
2663
|
up tabular-nums (Tailwind class on both cold +
|
|
2465
2664
|
hot branches). Sibling treatment to the status /
|
|
@@ -2491,7 +2690,21 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2491
2690
|
type="button"
|
|
2492
2691
|
aria-label={`Clear edge filter ${link.from} → ${link.to}`}
|
|
2493
2692
|
onClick={(e) => { e.stopPropagation(); setPinnedEdgeKey(null); }}
|
|
2494
|
-
|
|
2693
|
+
/* Round 356 / Loop: filter pin pill × buttons gain
|
|
2694
|
+
hover:scale-110 (Tailwind 4 modern CSS `scale` property,
|
|
2695
|
+
not legacy transform). Sibling polish to R354 vendor
|
|
2696
|
+
letter glyph + R350/R352/R353 chrome icon hover-scales.
|
|
2697
|
+
Pre-R356 the × had only hover:opacity-70 — the target
|
|
2698
|
+
dimmed under cursor but didn't lift. R356 adds a 10 %
|
|
2699
|
+
scale on hover so the click-target reads as "press me"
|
|
2700
|
+
alongside the dim. transform-gpu hint promotes the
|
|
2701
|
+
button to its own compositor layer for crisper edges
|
|
2702
|
+
during the scale tween. transition-transform duration-
|
|
2703
|
+
200 matches the chrome icon hover-scale timing family.
|
|
2704
|
+
inline-block is default for <button> so no display
|
|
2705
|
+
tweak needed. replace_all covers all 4 filter pin
|
|
2706
|
+
pills (status / group / vendor / edge) at once. */
|
|
2707
|
+
className="ml-0.5 leading-none hover:opacity-70 transition-transform duration-200 ease-out hover:scale-110 transform-gpu"
|
|
2495
2708
|
style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}
|
|
2496
2709
|
>×</button>
|
|
2497
2710
|
</span>
|
|
@@ -2756,9 +2969,35 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2756
2969
|
vendor letter chips ('A:N', 'O:N', '书:N',
|
|
2757
2970
|
'?:N'). They display vendor-distribution
|
|
2758
2971
|
data; same tier as the sibling data chips. */
|
|
2759
|
-
|
|
2972
|
+
/* Round 401 / Loop: vendor letter chip closes the
|
|
2973
|
+
hover-lift gesture family at its last unaddressed
|
|
2974
|
+
interactive HTML surface. R397/R398/R399 lifted
|
|
2975
|
+
filter pin pills + chip-row chips (working /
|
|
2976
|
+
online / active-links); R400 lifted standalone
|
|
2977
|
+
chrome buttons (reset / fullscreen). The vendor
|
|
2978
|
+
letter chips (A:N / O:N / 书:N / ?:N) are
|
|
2979
|
+
sibling interactive chips in the same chip-row
|
|
2980
|
+
— clickable to toggle the vendor filter pin —
|
|
2981
|
+
but were not yet on the hover-lift family.
|
|
2982
|
+
R401 closes the gap with hover:-translate-y-px
|
|
2983
|
+
+ transition-transform + transform-gpu added
|
|
2984
|
+
to the className. The inline transition list
|
|
2985
|
+
(box-shadow + background-color) keeps eaching
|
|
2986
|
+
independently — different property axes compose
|
|
2987
|
+
cleanly. Existing R354 glyph scale-1.1 (inner
|
|
2988
|
+
span) + R202 chip bg color-mix + R180 pin-mirror
|
|
2989
|
+
box-shadow + R354 glyph hover transform all
|
|
2990
|
+
preserved. data-vendor-letter-hover-lift attr
|
|
2991
|
+
surfaces the lift for tests. */
|
|
2992
|
+
// R417: `group` parent enables the count suffix to
|
|
2993
|
+
// brighten on chip hover via group-hover:opacity-100
|
|
2994
|
+
// — sibling to R355 filter-pill prefix/suffix + R414
|
|
2995
|
+
// chip-row unit brighten. Closes the inner-span
|
|
2996
|
+
// hover-brighten family at the vendor chip surface.
|
|
2997
|
+
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"
|
|
2760
2998
|
data-vendor-letter={v.initial}
|
|
2761
2999
|
data-vendor-letter-count={v.count}
|
|
3000
|
+
data-vendor-letter-hover-lift="true"
|
|
2762
3001
|
data-vendor-pinned={isPinned ? 'true' : 'false'}
|
|
2763
3002
|
data-vendor-hovered={hoveredVendor === v.initial ? 'true' : 'false'}
|
|
2764
3003
|
data-vendor-aliases={aliases.join(',')}
|
|
@@ -2802,7 +3041,64 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2802
3041
|
}
|
|
2803
3042
|
}}
|
|
2804
3043
|
>
|
|
2805
|
-
|
|
3044
|
+
{/* Round 354 / Loop: vendor letter glyph scales
|
|
3045
|
+
1.0 → 1.1 on hover. R88 already dims OTHER
|
|
3046
|
+
vendors on hover via canvas-wide opacity
|
|
3047
|
+
masking; R202 added a chip-level bg tint
|
|
3048
|
+
(color-mix 12 % alpha) so the chip itself
|
|
3049
|
+
responds. R354 closes the trio with a glyph-
|
|
3050
|
+
level lift: the focused vendor LETTER actively
|
|
3051
|
+
rises (transform scale) rather than the chip
|
|
3052
|
+
merely changing colour. Three layers of positive
|
|
3053
|
+
feedback on the hovered vendor + canvas-wide
|
|
3054
|
+
negative feedback on the others — a clean
|
|
3055
|
+
figure/ground separation.
|
|
3056
|
+
|
|
3057
|
+
display: inline-block is required for transform
|
|
3058
|
+
to apply (inline elements ignore transform).
|
|
3059
|
+
transformOrigin: 'center' so the glyph pivots
|
|
3060
|
+
around its centre instead of arcing from the
|
|
3061
|
+
baseline anchor. transition rides the existing
|
|
3062
|
+
Tailwind 4 transform/scale list (no new
|
|
3063
|
+
property — Tailwind already lists transform in
|
|
3064
|
+
the default transition-property set). 200ms
|
|
3065
|
+
matches the R202 chip bg-tint timing so the
|
|
3066
|
+
glyph lift and chip background ease in concert. */}
|
|
3067
|
+
{/* Round 369 / Loop: vendor letter glyph picks up
|
|
3068
|
+
fontWeight 600 (font-semibold). The glyph is the
|
|
3069
|
+
vendor identifier — the DATA the operator scans
|
|
3070
|
+
in this chip (A / O / 书 / C / G / ?). R333 set
|
|
3071
|
+
the count suffix `:N` to text-gray-400 + tabular-
|
|
3072
|
+
nums and (via parent inheritance) fw 500. Pre-
|
|
3073
|
+
R369 the LETTER also inherited fw 500 from the
|
|
3074
|
+
chip's font-medium — letter and count read at
|
|
3075
|
+
the same weight, contradicting the data-vs-label
|
|
3076
|
+
hierarchy the rest of the chip-row already speaks.
|
|
3077
|
+
R369 lifts the letter to fw 600 so the chip now
|
|
3078
|
+
reads as the same two-tier pattern R362 closed
|
|
3079
|
+
on the working / online / active-links chips:
|
|
3080
|
+
chip digit/letter fw 600 (data)
|
|
3081
|
+
chip unit/count fw 500 (label)
|
|
3082
|
+
Sibling treatment to R362 — extends the R333-R341
|
|
3083
|
+
chip-internal-hierarchy arc to the vendor-letter
|
|
3084
|
+
chip surface (9th surface family). R354 transform-
|
|
3085
|
+
scale-on-hover + R88 canvas-dim-others + R202
|
|
3086
|
+
chip bg color-mix all preserved on the same span.
|
|
3087
|
+
data-vendor-letter-glyph-font-weight attr exposes
|
|
3088
|
+
the value for tests. */}
|
|
3089
|
+
<span
|
|
3090
|
+
data-vendor-letter-glyph={v.initial}
|
|
3091
|
+
data-vendor-letter-glyph-hover={hoveredVendor === v.initial ? 'true' : 'false'}
|
|
3092
|
+
data-vendor-letter-glyph-font-weight="600"
|
|
3093
|
+
style={{
|
|
3094
|
+
color: v.color,
|
|
3095
|
+
display: 'inline-block',
|
|
3096
|
+
fontWeight: 600,
|
|
3097
|
+
transform: hoveredVendor === v.initial ? 'scale(1.1)' : 'scale(1)',
|
|
3098
|
+
transformOrigin: 'center',
|
|
3099
|
+
transition: 'transform 200ms ease-out',
|
|
3100
|
+
}}
|
|
3101
|
+
>{v.initial}</span>
|
|
2806
3102
|
{/* Round 333 / Loop: vendor count suffix `:{N}` joins
|
|
2807
3103
|
the R317 subordinate-text-lift family (gray-500 →
|
|
2808
3104
|
gray-400) plus picks up tabular-nums for digit
|
|
@@ -2815,8 +3111,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2815
3111
|
reads it as "deliberate subordinate metadata"
|
|
2816
3112
|
rather than near-invisible chrome. data-vendor-
|
|
2817
3113
|
letter-count exposes the span for tests. */}
|
|
3114
|
+
{/* R417: count suffix opacity-70 + group-hover:
|
|
3115
|
+
opacity-100 brightens on chip hover. Inner-span
|
|
3116
|
+
hover-brighten family (3rd anchor) — sibling to
|
|
3117
|
+
R355 filter pill prefix/suffix and R414 chip-row
|
|
3118
|
+
unit. Effective shade at rest: text-gray-400 ×
|
|
3119
|
+
70 % alpha; on hover: full gray-400. The label-
|
|
3120
|
+
tier-vs-glyph differentiation persists on hover
|
|
3121
|
+
since the glyph (R369 fw=600) stays at full
|
|
3122
|
+
opacity. R333 :{count} format preserved. */}
|
|
2818
3123
|
<span
|
|
2819
|
-
className="text-gray-400 tabular-nums"
|
|
3124
|
+
className="text-gray-400 tabular-nums opacity-70 transition-opacity duration-200 group-hover:opacity-100"
|
|
2820
3125
|
data-vendor-letter-count-suffix
|
|
2821
3126
|
>:{v.count}</span>
|
|
2822
3127
|
</span>
|
|
@@ -2904,11 +3209,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2904
3209
|
(third chip in the row — matches working + online
|
|
2905
3210
|
chip treatment so all three digits in the chip row
|
|
2906
3211
|
stay width-stable across counter crossings). */
|
|
2907
|
-
|
|
3212
|
+
/* Round 399 / Loop: active-links chip closes the 3-chip
|
|
3213
|
+
chip-row by extending R398's hover translateY(-1px)
|
|
3214
|
+
lift onto the third (rightmost) chip. The R398 family
|
|
3215
|
+
already covered working + online chips on the
|
|
3216
|
+
clickable variant; R399 adds the same gate (isInter-
|
|
3217
|
+
active = flowLinks.length > 0) so empty active-links
|
|
3218
|
+
stays planted at R206's opacity-50 receded paint.
|
|
3219
|
+
transition-transform + ease-out + transform-gpu join
|
|
3220
|
+
the inline transition list (different property axes
|
|
3221
|
+
compose cleanly: inline handles color/bg/border/
|
|
3222
|
+
opacity, className handles transform).
|
|
3223
|
+
Gesture-vocabulary table (post-R399 — now complete
|
|
3224
|
+
across the chip-row):
|
|
3225
|
+
working chip -1 px (R398)
|
|
3226
|
+
online chip -1 px (R398)
|
|
3227
|
+
active-links chip -1 px (R399, this round)
|
|
3228
|
+
filter pin pills -1 px (R397)
|
|
3229
|
+
recent-signal row -1 px (R143)
|
|
3230
|
+
legend row -1 px (R144)
|
|
3231
|
+
Every interactive chip in TopoGraph lifts on hover.
|
|
3232
|
+
data-chip-hover-lift attr exposes the lift surface
|
|
3233
|
+
state ('true' clickable, 'false' empty) for tests. */
|
|
3234
|
+
// R414: `group` parent + inner unit span group-hover-brighten — see working chip above.
|
|
3235
|
+
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 ${
|
|
2908
3236
|
isInteractive
|
|
2909
|
-
? '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'
|
|
3237
|
+
? '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'
|
|
2910
3238
|
: 'bg-gray-500/10 text-gray-400 border-gray-500/20'
|
|
2911
3239
|
}`}
|
|
3240
|
+
data-chip-hover-lift={isInteractive ? 'true' : 'false'}
|
|
3241
|
+
data-chip-group-hover-brighten="true"
|
|
2912
3242
|
data-active-links-chip
|
|
2913
3243
|
data-active-links-flow-count={flowLinks.length}
|
|
2914
3244
|
data-active-links-clickable={isInteractive ? 'true' : 'false'}
|
|
@@ -2936,7 +3266,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2936
3266
|
the 5th chip surface in the R333/R335/R336/R337
|
|
2937
3267
|
chip-internal-hierarchy arc. data-active-links-
|
|
2938
3268
|
chip-unit exposes the unit span for tests. */}
|
|
2939
|
-
{
|
|
3269
|
+
{/* R362 sibling — active-links chip digit gains font-semibold. */}
|
|
3270
|
+
<span className="font-semibold transition-[font-weight] duration-200 group-hover:font-bold" data-active-links-chip-digit>{flowLinks.length}</span><span className="opacity-70 transition-opacity duration-200 group-hover:opacity-100" data-active-links-chip-unit> active link{flowLinks.length === 1 ? '' : 's'}</span>
|
|
2940
3271
|
{rel ? (() => {
|
|
2941
3272
|
// Round 161 / Loop: extend R160's recency-pip
|
|
2942
3273
|
// vocabulary up one scope — from per-flow row to
|
|
@@ -2961,8 +3292,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2961
3292
|
const alpha = ageSec <= 30
|
|
2962
3293
|
? 1
|
|
2963
3294
|
: ageSec <= 300
|
|
2964
|
-
? 1 - ((ageSec - 30) / 270) * 0.
|
|
2965
|
-
: 0.25
|
|
3295
|
+
? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
|
|
3296
|
+
: 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
|
|
2966
3297
|
// Cyan dark / teal light to match palette legendAccent.
|
|
2967
3298
|
const dotColor = isLight
|
|
2968
3299
|
? `rgba(13, 148, 136, ${alpha.toFixed(2)})`
|
|
@@ -2979,7 +3310,28 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2979
3310
|
// color: dotColor — the lift only affects the trailing
|
|
2980
3311
|
// literal "last {rel}" text.
|
|
2981
3312
|
return (
|
|
2982
|
-
|
|
3313
|
+
// Round 357 / Loop: active-links chip freshness
|
|
3314
|
+
// suffix wrapper picks up `tabular-nums` for digit
|
|
3315
|
+
// width-lock. Pre-R357 the literal "last {rel}"
|
|
3316
|
+
// text (e.g. "last 5s ago", "last 10s ago",
|
|
3317
|
+
// "last 1m ago") had natural-figure digits — the
|
|
3318
|
+
// freshness ticker updates every second, so the
|
|
3319
|
+
// 9→10 boundary on the seconds counter and the
|
|
3320
|
+
// 59→60s → 1m flip both jittered ~1-2 px of glyph
|
|
3321
|
+
// width which propagated through the chip-row's
|
|
3322
|
+
// inline-flex layout, nudging the freshness DOT
|
|
3323
|
+
// and the chip's left edge. Tabular-nums on the
|
|
3324
|
+
// wrapper applies to all descendant digits only
|
|
3325
|
+
// (letters render at natural widths) so the
|
|
3326
|
+
// ticker stays planted across every count cross.
|
|
3327
|
+
// Joins the R224-R232 info-density tabular-nums
|
|
3328
|
+
// sweep at the chip-row freshness scope. Pure
|
|
3329
|
+
// paint-level change, no geometry shift on rest.
|
|
3330
|
+
// The R342 text-gray-400 lift + R161 dot freshness
|
|
3331
|
+
// alpha ramp + R317 subordinate-text-lift family
|
|
3332
|
+
// all preserved. data-active-links-freshness-
|
|
3333
|
+
// wrapper attr exposes the wrapper for tests.
|
|
3334
|
+
<span className="text-gray-400 tabular-nums" data-active-links-freshness-wrapper>
|
|
2983
3335
|
<span
|
|
2984
3336
|
data-active-links-freshness-dot
|
|
2985
3337
|
data-active-links-freshness-alpha={alpha.toFixed(2)}
|
|
@@ -3106,6 +3458,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3106
3458
|
return `Agent network topology — ${parts.join(' · ')}. Tab to navigate nodes, double-click canvas to reset view.`;
|
|
3107
3459
|
})()}
|
|
3108
3460
|
data-topo-canvas-aria
|
|
3461
|
+
/* Round 462 / Loop — surface DASHBOARD_VERSION on the root SVG
|
|
3462
|
+
element as `data-dashboard-version`. Directly closes the
|
|
3463
|
+
feedback_dash_zombie_port_3000.md memory rule: "verify ships
|
|
3464
|
+
via SVG DOM, not tmux 'Ready' — zombie next-servers + stale
|
|
3465
|
+
global installs silently serve old code". Pre-R462 the only
|
|
3466
|
+
ways to know which preview the dash was serving were:
|
|
3467
|
+
1. parse the npm registry for the latest tag (network)
|
|
3468
|
+
2. fetch /api/dashboard/version (API surface, no DOM)
|
|
3469
|
+
3. inspect the /login footer or /settings page (off-route)
|
|
3470
|
+
Test scripts that probe TopoGraph DOM (overlap, group-label
|
|
3471
|
+
tint, pip strip, etc.) couldn't tell whether the dash was
|
|
3472
|
+
actually serving the build they expected to verify. R462
|
|
3473
|
+
threads DASHBOARD_VERSION through to the root <svg> so:
|
|
3474
|
+
- Playwright probes can read svg[data-dashboard-version]
|
|
3475
|
+
directly + fail-fast on stale-build mismatch
|
|
3476
|
+
- the memory rule's manual zombie check ("inspect SVG
|
|
3477
|
+
dom") becomes a one-attr probe
|
|
3478
|
+
- operators DOM-inspect to confirm the live version
|
|
3479
|
+
matches the npm tag without leaving the topology page
|
|
3480
|
+
Geometry/visual impact: ZERO (data-* attrs don't paint).
|
|
3481
|
+
The version string is build-time injected via the existing
|
|
3482
|
+
DASHBOARD_VERSION constant (R51 footer + R51 settings page
|
|
3483
|
+
already consume it from app/lib/version.ts → reads
|
|
3484
|
+
package.json pkg.version). No business logic added. */
|
|
3485
|
+
data-dashboard-version={DASHBOARD_VERSION}
|
|
3109
3486
|
onPointerDown={onPointerDown}
|
|
3110
3487
|
onPointerMove={onPointerMove}
|
|
3111
3488
|
onPointerUp={onPointerUp}
|
|
@@ -3643,19 +4020,174 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3643
4020
|
active surfaces the activity state for test probes
|
|
3644
4021
|
(active spokes don't carry the bucket/dur attrs so
|
|
3645
4022
|
they need their own data anchor). */
|
|
4023
|
+
// Round 382 / Loop: hub-spoke path picks up
|
|
4024
|
+
// strokeLinecap='round'. Sibling polish to R378 flow-
|
|
4025
|
+
// rail dashes + R380 group box dashes — three dashed-
|
|
4026
|
+
// stroke surfaces now share 'round' linecap:
|
|
4027
|
+
// R378 flow-rail '2 12' -> soft 3-px pills
|
|
4028
|
+
// R380 group box '6 6' -> soft 7.5-px pills
|
|
4029
|
+
// R382 hub spoke '6 14' -> soft 7-px pills (this round)
|
|
4030
|
+
// For idle spokes (dashed at sw=1), each 6-px dash gains
|
|
4031
|
+
// 0.5-px round caps and reads as a soft pill instead of
|
|
4032
|
+
// a sharp 6 x 1 rectangle. Active spokes (solid, no
|
|
4033
|
+
// dasharray) have caps mostly hidden by the hub center +
|
|
4034
|
+
// node radius. Geometry-safe; paint-only. R51 sentinel
|
|
4035
|
+
// strokeWidth 1.5/3 untouched (idle=1, active=2). data-
|
|
4036
|
+
// topo-hub-spoke-linecap attr exposes the value for tests.
|
|
4037
|
+
// Round 419 / Loop: hub-spoke idle opacity 0.45 → 0.50.
|
|
4038
|
+
// Stale-state legibility lift family 9th anchor — pairs
|
|
4039
|
+
// with R391 (active 0.7 → 0.8) and R415 (active sw 2 →
|
|
4040
|
+
// 2.25) so the same spoke path is now polished on BOTH
|
|
4041
|
+
// active AND idle tiers. Pre-R419 idle spokes painted
|
|
4042
|
+
// at α=0.45 with R46 anet-topo-spoke-flow dashed
|
|
4043
|
+
// animation; the dashed pulses sat at the "background
|
|
4044
|
+
// chatter" floor — visible but understated. R419
|
|
4045
|
+
// lifts to 0.50 so idle spokes read more confidently
|
|
4046
|
+
// while the active/idle contrast ratio stays clear
|
|
4047
|
+
// (0.8/0.50 = 1.6× vs prior 0.8/0.45 = 1.78×; still
|
|
4048
|
+
// a sharp two-tier distinction).
|
|
4049
|
+
// Stale-state legibility lift family (9 anchors now):
|
|
4050
|
+
// R317 subordinate-text gray-500 → gray-400
|
|
4051
|
+
// R358 freshness floor 0.25 → 0.30
|
|
4052
|
+
// R372 minimap offline-dot 0.5 → 0.6
|
|
4053
|
+
// R404 hub-halo cyber trough 0.08 → 0.10
|
|
4054
|
+
// R405 hub-halo light trough 0.32 → 0.34
|
|
4055
|
+
// R406 edge freshness floor 0.35 → 0.40
|
|
4056
|
+
// R407 node halo offline opacity (cyber + light)
|
|
4057
|
+
// R413 active-node pulse trough (cyber + light)
|
|
4058
|
+
// R419 hub-spoke idle opacity 0.45 → 0.50 (this round)
|
|
4059
|
+
// data-topo-hub-spoke-opacity attr (R391) updates to
|
|
4060
|
+
// surface the resolved per-state value.
|
|
4061
|
+
//
|
|
4062
|
+
// Round 415 / Loop: hub-spoke active strokeWidth 2 → 2.25.
|
|
4063
|
+
// Pairs with R391 (active opacity 0.7 → 0.8) so the same
|
|
4064
|
+
// active-state path lifts BOTH stroke weight AND opacity
|
|
4065
|
+
// in concert. Pre-R415 active strokes sat at sw=2 — clear
|
|
4066
|
+
// step over idle sw=1, but a touch lighter than the
|
|
4067
|
+
// weight family's other "active" indicators (R385 hub
|
|
4068
|
+
// hover-ring sw=1.75 / R402 legend pin-ring sw=1.75 /
|
|
4069
|
+
// R367 edge-badge rest sw=1.25). R415 bumps to 2.25 so
|
|
4070
|
+
// the active spoke reads with proportional weight to its
|
|
4071
|
+
// role — the line connecting the focal point to the
|
|
4072
|
+
// active node deserves the heaviest active stroke in the
|
|
4073
|
+
// family (after pin/hot edge-badge sw=2). Stays clear of
|
|
4074
|
+
// R51 sentinels (1.5 / 3) at 2.25.
|
|
4075
|
+
// Visual-weight bump family (14 anchors now):
|
|
4076
|
+
// R287 minimap viewport stroke 1 → 1.5
|
|
4077
|
+
// R295 legend swatch radius 5.5 → 6
|
|
4078
|
+
// R359 recent-row pip radius 1.6 → 1.8
|
|
4079
|
+
// R360 hub digit fontSize 11 → 12
|
|
4080
|
+
// R361 edge-badge digit fontSize 10 → 11
|
|
4081
|
+
// R365 hub-highlight radius 5 → 5.5
|
|
4082
|
+
// R367 edge-badge rest stroke 1 → 1.25
|
|
4083
|
+
// R374 pressure-bar height 1.5 → 2
|
|
4084
|
+
// R383 recent-row pip radius 1.8 → 2.0
|
|
4085
|
+
// R384 minimap online dot 1.7 → 1.9
|
|
4086
|
+
// R385 hub hover-ring stroke 1.5 → 1.75
|
|
4087
|
+
// R402 legend pin-ring stroke 1.5 → 1.75
|
|
4088
|
+
// R408 hub-halo radius 18 → 20
|
|
4089
|
+
// R415 hub-spoke active stroke 2 → 2.25 (this round)
|
|
4090
|
+
// R382 strokeLinecap='round' + R391 opacity 0.45/0.8 +
|
|
4091
|
+
// R51-safe idle sw=1 all preserved. 250ms transition
|
|
4092
|
+
// list already covers stroke-width — the new tier eases
|
|
4093
|
+
// naturally. data-topo-hub-spoke-stroke-width-active
|
|
4094
|
+
// attr surfaces the active value for tests.
|
|
4095
|
+
//
|
|
4096
|
+
// Round 391 / Loop: hub-spoke active opacity 0.7 → 0.8.
|
|
4097
|
+
// Pre-R391 active spokes (the spoke connecting the hub
|
|
4098
|
+
// to the currently-active alias — hovered or pinned)
|
|
4099
|
+
// lifted opacity from rest 0.45 to active 0.7 — a clear
|
|
4100
|
+
// step but slightly understated against the canvas
|
|
4101
|
+
// chrome. R391 lifts active to 0.8 so the "this spoke
|
|
4102
|
+
// connects to your active node" signal reads with
|
|
4103
|
+
// matching weight to the R370 hub hover-ring opacity
|
|
4104
|
+
// (0.7 → 0.8 cyber) — paired canvas signals now share
|
|
4105
|
+
// the same active-state alpha (0.8) so when a user
|
|
4106
|
+
// hovers a node, both the spoke and the hub-ring lift
|
|
4107
|
+
// to identical opacity. Rest 0.45 invariant preserved.
|
|
4108
|
+
// Theme-consistency / canvas-presence polish family
|
|
4109
|
+
// (6th anchor):
|
|
4110
|
+
// R370 hub hover-ring opacity 0.7 → 0.8 cyber
|
|
4111
|
+
// R371 edge-badge rest opacity 0.82 → 0.85 cyber
|
|
4112
|
+
// R372 minimap offline-dot opacity 0.5 → 0.6
|
|
4113
|
+
// R386 hub-highlight idle opacity 0.9 → 0.95
|
|
4114
|
+
// R387 hover-detail panel opacity 0.94 → 0.97 cyber
|
|
4115
|
+
// R391 hub-spoke active opacity 0.7 → 0.8 (this round)
|
|
4116
|
+
// Idle path (45% alpha + dashed flow animation) entirely
|
|
4117
|
+
// untouched — R391 is an active-state-only lift.
|
|
4118
|
+
// data-topo-hub-spoke-opacity attr exposes the resolved
|
|
4119
|
+
// value for tests. R382 strokeLinecap='round' + R51
|
|
4120
|
+
// sentinel-safe sw (1 idle / 2 active) preserved.
|
|
4121
|
+
/* Round 430 / Loop: hub-spoke opacity hover lift on
|
|
4122
|
+
hoveredAlias === session.alias. Adds a "this node's
|
|
4123
|
+
spoke" affordance to the node-hover gesture — in a
|
|
4124
|
+
dense ring layout the spokes are visually quiet
|
|
4125
|
+
(idle α=0.50 dashed, active α=0.80 solid) so hovering
|
|
4126
|
+
a node didn't telegraph which line connects to it.
|
|
4127
|
+
R430 lifts the matched spoke's opacity:
|
|
4128
|
+
idle 0.50 → 0.70 (hover-α=0.70, +0.20)
|
|
4129
|
+
active 0.80 → 0.95 (hover-α=0.95, +0.15)
|
|
4130
|
+
The +0.15-to-0.20 lift keeps the active/idle two-tier
|
|
4131
|
+
distinction (0.95 vs 0.70 still a clear gap) while
|
|
4132
|
+
making the hovered-node's spoke visibly brighter than
|
|
4133
|
+
every other spoke at its own activity tier. R241
|
|
4134
|
+
transition list already covers opacity 250ms so the
|
|
4135
|
+
lift eases for free. Sibling to R429 label-card body
|
|
4136
|
+
solidity lift — both surface a single-node-focused
|
|
4137
|
+
attention cue with the same easing cadence.
|
|
4138
|
+
Stacks with the 6-layer node hover cue stack at the
|
|
4139
|
+
inter-node-link scope:
|
|
4140
|
+
R26 group translateY -2px (per-node)
|
|
4141
|
+
R217 stroke tint legendAccent (per-node card)
|
|
4142
|
+
R142 drop-shadow boost (per-node card)
|
|
4143
|
+
R427 alias letter-spacing (per-node text)
|
|
4144
|
+
R428 sub-text letter-spacing (per-node text)
|
|
4145
|
+
R429 body opacity 0.94 → 1.0 (per-node card)
|
|
4146
|
+
R430 spoke opacity α+ (this round) (link to hub)
|
|
4147
|
+
data-topo-hub-spoke-hovered exposes the gate. */
|
|
4148
|
+
const isHoveredSpoke = !reducedMotion && hoveredAlias === session.alias;
|
|
4149
|
+
const spokeOpacity = isActiveSpoke
|
|
4150
|
+
? (isHoveredSpoke ? 0.95 : 0.80)
|
|
4151
|
+
: (isHoveredSpoke ? 0.70 : 0.50);
|
|
4152
|
+
/* Round 435 / Loop: hub-spoke stroke-width hover lift —
|
|
4153
|
+
sibling to R430 opacity hover at the same surface. When
|
|
4154
|
+
hoveredAlias matches, BOTH opacity AND stroke-width
|
|
4155
|
+
lift on the matched spoke so the eye registers a
|
|
4156
|
+
2-axis "this node's spoke" gesture (paint + geometry).
|
|
4157
|
+
idle 1.00 → 1.25 (Δ +0.25, +25%)
|
|
4158
|
+
active 2.25 → 2.50 (Δ +0.25, +11%)
|
|
4159
|
+
Same +0.25 absolute delta keeps the idle/active visual
|
|
4160
|
+
progression consistent — at rest sw ratio 2.25:1 = 2.25,
|
|
4161
|
+
on hover 2.50:1.25 = 2.0; both still clearly two-tier.
|
|
4162
|
+
R241 transition list already covers stroke-width 250ms
|
|
4163
|
+
so the lift eases for free.
|
|
4164
|
+
R51 sentinel-safe: spoke is canvas <path>, not
|
|
4165
|
+
data-node <circle> (the sentinel selector is gated to
|
|
4166
|
+
g[data-node] descendants). 1.25 and 2.5 are not in the
|
|
4167
|
+
reserved {1.5, 3} set so the overlap-test sentinel
|
|
4168
|
+
attribute selector wouldn't match either way. */
|
|
4169
|
+
const spokeStrokeWidth = isActiveSpoke
|
|
4170
|
+
? (isHoveredSpoke ? 2.5 : 2.25)
|
|
4171
|
+
: (isHoveredSpoke ? 1.25 : 1);
|
|
3646
4172
|
return (
|
|
3647
4173
|
<path
|
|
3648
4174
|
key={`hub-${session.alias}`}
|
|
3649
4175
|
d={path}
|
|
3650
4176
|
fill="none"
|
|
3651
4177
|
stroke={isActiveSpoke ? pal.spokeStroke.active : pal.spokeStroke.idle}
|
|
3652
|
-
strokeWidth={
|
|
4178
|
+
strokeWidth={spokeStrokeWidth}
|
|
3653
4179
|
strokeDasharray={isActiveSpoke ? 'none' : '6 14'}
|
|
3654
|
-
|
|
4180
|
+
strokeLinecap="round"
|
|
4181
|
+
opacity={spokeOpacity}
|
|
3655
4182
|
className={isActiveSpoke ? undefined : 'anet-topo-spoke-flow'}
|
|
3656
4183
|
data-topo-spoke-bucket={isActiveSpoke ? undefined : busy}
|
|
3657
4184
|
data-topo-spoke-dur={isActiveSpoke ? undefined : spokeDur}
|
|
3658
4185
|
data-topo-hub-spoke-active={isActiveSpoke ? 'true' : 'false'}
|
|
4186
|
+
data-topo-hub-spoke-hovered={isHoveredSpoke ? 'true' : 'false'}
|
|
4187
|
+
data-topo-hub-spoke-opacity={spokeOpacity}
|
|
4188
|
+
data-topo-hub-spoke-stroke-width={spokeStrokeWidth}
|
|
4189
|
+
data-topo-hub-spoke-stroke-width-active="2.25"
|
|
4190
|
+
data-topo-hub-spoke-linecap="round"
|
|
3659
4191
|
style={{
|
|
3660
4192
|
transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out',
|
|
3661
4193
|
...(isActiveSpoke ? {} : {
|
|
@@ -3735,7 +4267,29 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3735
4267
|
y={box.y}
|
|
3736
4268
|
width={box.w}
|
|
3737
4269
|
height={box.h}
|
|
3738
|
-
rx
|
|
4270
|
+
/* Round 464 / Loop: group-box rx 14 → 16 on isPinned.
|
|
4271
|
+
Geometric softening at the corner radius — locked
|
|
4272
|
+
groups read with subtly rounder shoulders than
|
|
4273
|
+
hovered/idle. +2px reads as a calm \"settled in\"
|
|
4274
|
+
posture (subtler than a fill or stroke bump but
|
|
4275
|
+
unmistakable across the whole cluster boundary).
|
|
4276
|
+
Pin signature on the group-box rect now spans 7
|
|
4277
|
+
axes:
|
|
4278
|
+
R63 text fill brighten
|
|
4279
|
+
R142 drop-shadow filter
|
|
4280
|
+
R432 text letter-spacing 0→0.5
|
|
4281
|
+
R444 count tspan fw 500→600
|
|
4282
|
+
R457 parent text fw 700→800
|
|
4283
|
+
codex p.125 text opacity 0.55→1
|
|
4284
|
+
R464 corner rx 14→16 (this round)
|
|
4285
|
+
transition list (R461) already covers x/y/width/
|
|
4286
|
+
height 200ms ease-out; appended `rx 200ms ease-
|
|
4287
|
+
out` so the rounding eases alongside the geometry
|
|
4288
|
+
axes. SVG2 CSS animation on rx: Chrome 95+ /
|
|
4289
|
+
Safari 16+ / FF 70+ (same matrix as x/y/w/h).
|
|
4290
|
+
data-group-box-rx exposes the resolved value. */
|
|
4291
|
+
rx={isPinned ? '16' : '14'}
|
|
4292
|
+
data-group-box-rx={isPinned ? '16' : '14'}
|
|
3739
4293
|
fill={isLight ? '#0f172a' : '#a5b4fc'}
|
|
3740
4294
|
// R68: 3-tier opacity + stroke ladder.
|
|
3741
4295
|
// pinned → fill 0.08 / 0.13, stroke 3 px (locked)
|
|
@@ -3747,7 +4301,35 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3747
4301
|
stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}
|
|
3748
4302
|
strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}
|
|
3749
4303
|
strokeDasharray={(isPinned || isHovered) ? 'none' : '6 6'}
|
|
4304
|
+
/* Round 380 / Loop: cluster box stroke gets round
|
|
4305
|
+
linecap + round linejoin. Sibling SVG stroke-
|
|
4306
|
+
softening polish to R378 flow-rail linecap + R379
|
|
4307
|
+
minimap viewport linejoin — extends the family to
|
|
4308
|
+
the group cluster boundary box (grid layout only):
|
|
4309
|
+
R288 chrome icons strokeLinecap='round'
|
|
4310
|
+
R378 flow-rail dashes strokeLinecap='round'
|
|
4311
|
+
R380 group box dashes strokeLinecap='round' (this round)
|
|
4312
|
+
R379 viewport rect strokeLinejoin='round'
|
|
4313
|
+
R380 group box corners strokeLinejoin='round' (this round)
|
|
4314
|
+
Linecap rounds the R85 '6 6' marching-ants dash
|
|
4315
|
+
pills at rest — each 6 px dash gains a ~0.75 px
|
|
4316
|
+
round cap (sw=1.5 idle), reading as soft pills
|
|
4317
|
+
instead of sharp 6 × 1.5 px rectangles. Linejoin
|
|
4318
|
+
rounds the 4 sharp 90° corners (any state — solid
|
|
4319
|
+
or dashed); at sw=1.5 the join arc is ~0.75 px,
|
|
4320
|
+
matching R379 viewport vocabulary. Geometry-safe:
|
|
4321
|
+
stroke-* properties only affect paint, not bbox.
|
|
4322
|
+
The R51 sentinel 1.5/3 strokeWidth values stay
|
|
4323
|
+
intact (the overlap probe is gated to g[data-
|
|
4324
|
+
node], so this cluster-internal rect is invisible
|
|
4325
|
+
to it anyway). data-group-box-linecap + -linejoin
|
|
4326
|
+
attrs expose the values for tests. */
|
|
4327
|
+
strokeLinecap="round"
|
|
4328
|
+
strokeLinejoin="round"
|
|
3750
4329
|
data-group-box-pinned={isPinned ? 'true' : 'false'}
|
|
4330
|
+
data-group-box-linecap="round"
|
|
4331
|
+
data-group-box-linejoin="round"
|
|
4332
|
+
data-group-box-geom-transition="x,y,width,height"
|
|
3751
4333
|
// R85: ambient "marching ants" drift on the perimeter
|
|
3752
4334
|
// when this group has at least one working member, and
|
|
3753
4335
|
// neither pin nor hover is active (those treatments
|
|
@@ -3785,8 +4367,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3785
4367
|
while stroke / fill-opacity / filter all eased.
|
|
3786
4368
|
Closes the last theme-toggle snap on the group
|
|
3787
4369
|
box surface — same idiom R246 + R247 used at
|
|
3788
|
-
per-node label-card and side-panel scopes.
|
|
3789
|
-
|
|
4370
|
+
per-node label-card and side-panel scopes.
|
|
4371
|
+
Round 461 / Loop: extend the transition list to
|
|
4372
|
+
all 4 geometry axes (x, y, width, height) so
|
|
4373
|
+
when a cluster grows / shrinks (member joins,
|
|
4374
|
+
leaves, prefix rebalance, dense toggle, status
|
|
4375
|
+
flip) the BIG outer container slides into the
|
|
4376
|
+
new bounds at the same 200ms cadence the R460
|
|
4377
|
+
inner hitbox tint rect now uses. Pre-R461 the
|
|
4378
|
+
outer 200×140 px box snap-jumped on cluster
|
|
4379
|
+
resize while the inner 160×18 hitbox slid —
|
|
4380
|
+
jarring two-rate motion at the same surface.
|
|
4381
|
+
R461 unifies both rects to slide as one, with
|
|
4382
|
+
the parent box driving the visual envelope and
|
|
4383
|
+
the inner hitbox tracking the bottom-edge tint.
|
|
4384
|
+
Hero D #147 motion-coherence at the FULL cluster
|
|
4385
|
+
container tier (not just the label tint).
|
|
4386
|
+
data-group-box-geom-transition attr exposed. */
|
|
4387
|
+
transition: 'stroke 200ms ease-out, stroke-width 200ms ease-out, fill-opacity 200ms ease-out, filter 200ms ease-out, fill 200ms ease-out, x 200ms ease-out, y 200ms ease-out, width 200ms ease-out, height 200ms ease-out, rx 200ms ease-out',
|
|
3790
4388
|
pointerEvents: 'none',
|
|
3791
4389
|
// CSS var consumed by `.anet-topo-groupbox-live`
|
|
3792
4390
|
// (line 877 of globals.css). React's CSSProperties
|
|
@@ -3864,27 +4462,64 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3864
4462
|
].filter(Boolean).join('\n')}</title>
|
|
3865
4463
|
);
|
|
3866
4464
|
})()}
|
|
4465
|
+
{/* v0.11.0 #147 Hero D — Vincent 5401: "太大太丑,
|
|
4466
|
+
都放到框的右下角的小字". First-cut bottom-right
|
|
4467
|
+
placement collided with bottom-row nodes (cluster
|
|
4468
|
+
geometry has no bottom padding; only GROUP_TOP=12
|
|
4469
|
+
top band). Pivoted to Option C from #147 spec:
|
|
4470
|
+
keep top-left anchor BUT shrink fontSize (13 → 9)
|
|
4471
|
+
and dim default opacity (1 → 0.55, hover/pin
|
|
4472
|
+
restore to 1). Satisfies "太大太丑" via the size +
|
|
4473
|
+
opacity axes while keeping the existing geometry
|
|
4474
|
+
contract that topo-overlap-test gates. Hitbox
|
|
4475
|
+
rect width tightens to min(box.w-12, 160) to
|
|
4476
|
+
track the narrower label render. */}
|
|
3867
4477
|
<rect
|
|
3868
4478
|
x={box.x + 6}
|
|
3869
4479
|
y={box.y + 2}
|
|
3870
|
-
width={Math.min(box.w - 12,
|
|
3871
|
-
height={
|
|
4480
|
+
width={Math.min(box.w - 12, 160)}
|
|
4481
|
+
height={18}
|
|
3872
4482
|
rx="4"
|
|
3873
|
-
/* R107 / Loop: list-item tint extends to the SVG
|
|
3874
|
-
group labels — same idiom R104 added to recent-
|
|
3875
|
-
signal rows and R105 to the legend rows. The
|
|
3876
|
-
tint colour is pal.legendAccent (cyan) since
|
|
3877
|
-
groups don't carry an inherent swatch the way
|
|
3878
|
-
legend rows do; this matches R68's group-box
|
|
3879
|
-
isPinned/isHovered accent stroke for consistency.
|
|
3880
|
-
hover < pinned opacity so locked vs preview is
|
|
3881
|
-
discriminable at a glance. */
|
|
3882
4483
|
fill={pinnedGroup === box.key || hoveredGroupLabel === box.key ? pal.legendAccent : 'transparent'}
|
|
3883
4484
|
opacity={pinnedGroup === box.key ? (isLight ? 0.16 : 0.20)
|
|
3884
4485
|
: hoveredGroupLabel === box.key ? (isLight ? 0.09 : 0.13)
|
|
3885
4486
|
: 1}
|
|
3886
4487
|
data-group-label-tinted={pinnedGroup === box.key ? 'pinned' : hoveredGroupLabel === box.key ? 'hover' : 'none'}
|
|
3887
|
-
|
|
4488
|
+
/* Round 459 / Loop — cadence-sync follow-on to codex
|
|
4489
|
+
preview.125 (Hero D #147). Codex's parent <text>
|
|
4490
|
+
transition list now reads:
|
|
4491
|
+
'fill 200ms, letter-spacing 200ms,
|
|
4492
|
+
font-weight 200ms, opacity 200ms'
|
|
4493
|
+
— 200ms ease-out across every axis. The label
|
|
4494
|
+
hitbox tint rect underneath was still at 150ms
|
|
4495
|
+
(legacy R107 cadence), so the tint snapped in
|
|
4496
|
+
50ms ahead of the parent label brightening —
|
|
4497
|
+
a small but perceivable mistimed cascade when
|
|
4498
|
+
hovering or clicking to pin a cluster. R459
|
|
4499
|
+
lifts both axes to 200ms to lock the tint
|
|
4500
|
+
under the label as one motion-coherent state
|
|
4501
|
+
flip. Hover/pin/unpin all feel as a single
|
|
4502
|
+
unified ease rather than "tint pops, label
|
|
4503
|
+
follows". data-group-label-tint-transition
|
|
4504
|
+
attr exposes the timing for tests. */
|
|
4505
|
+
/* Round 460 / Loop — extend the R459-200ms tint rect
|
|
4506
|
+
transition list to include `x` + `width` so the
|
|
4507
|
+
hitbox slides into place when a cluster grows or
|
|
4508
|
+
shrinks (member joins / leaves / status change
|
|
4509
|
+
re-pricing box.w). Pre-R460 every resize snap-
|
|
4510
|
+
jumped the hitbox bounds — a small but visible
|
|
4511
|
+
glitch right at the moment the operator's
|
|
4512
|
+
attention is on the cluster. SVG2 CSS animation
|
|
4513
|
+
on geometry attrs has shipped in Chrome 95+ /
|
|
4514
|
+
Safari 16+ / FF 70+; the runtime gracefully
|
|
4515
|
+
no-ops on older browsers. Sibling motion idiom
|
|
4516
|
+
to R134 / R141 / R142 (panel rect transitions)
|
|
4517
|
+
at the group-label hitbox tier.
|
|
4518
|
+
data-group-label-tint-geom-transition attr
|
|
4519
|
+
exposes the geometry-axis presence for tests. */
|
|
4520
|
+
data-group-label-tint-transition="200ms"
|
|
4521
|
+
data-group-label-tint-geom-transition="x,width"
|
|
4522
|
+
style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out, x 200ms ease-out, width 200ms ease-out' }}
|
|
3888
4523
|
/>
|
|
3889
4524
|
{/* Round 218 / Loop: group label gains a letter-spacing
|
|
3890
4525
|
transition on pin — the text subtly spaces out
|
|
@@ -3904,16 +4539,75 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3904
4539
|
matches R142 fill timing so all the group-label
|
|
3905
4540
|
state-flip channels (fill colour, rect stroke,
|
|
3906
4541
|
rect drop-shadow, label tracking) ease as one. */}
|
|
4542
|
+
{/* Round 432 / Loop: extend the group-label letter-
|
|
4543
|
+
spacing tween from 2-tier (rest/pin) to 3-tier
|
|
4544
|
+
(rest/hover/pin → 0/0.25/0.5). Pre-R432 R218
|
|
4545
|
+
spread the text only on pin; hover got an
|
|
4546
|
+
R63 fill brighten (legendText → legendHeadline)
|
|
4547
|
+
but no typographic axis of its own. R432 adds
|
|
4548
|
+
the missing mid tier so hover telegraphs through
|
|
4549
|
+
BOTH the fill brighten AND a subtle kerning
|
|
4550
|
+
spread — sibling pattern to R427 node-alias
|
|
4551
|
+
(0/0.3/0.5) and R431 edge-badge (0/0.2/0.4) at
|
|
4552
|
+
group-label scope. Pin tier (0.5) still wins.
|
|
4553
|
+
Subtler mid tier (0.25 vs alias 0.3) because the
|
|
4554
|
+
group label is a structural anchor — too much
|
|
4555
|
+
spread would steal weight from the per-node
|
|
4556
|
+
alias identity it groups. Hover-letter-spacing
|
|
4557
|
+
family extension (8 anchors now):
|
|
4558
|
+
R344 chip count digit
|
|
4559
|
+
R345 panel title
|
|
4560
|
+
R347 active-links chip
|
|
4561
|
+
R351 vendor chip
|
|
4562
|
+
R420 zoom-level chip
|
|
4563
|
+
R427 node alias text
|
|
4564
|
+
R431 edge-badge digit
|
|
4565
|
+
R432 group label text (this round)
|
|
4566
|
+
R218 transition list ('fill 200ms, letter-spacing
|
|
4567
|
+
200ms') untouched — additive conditional case. */}
|
|
4568
|
+
{/* Round 457 / Loop: group label parent text fontWeight
|
|
4569
|
+
700 → 800 on isPinned. Adds typographic weight axis
|
|
4570
|
+
to the group-label parent text, sibling to R432
|
|
4571
|
+
letter-spacing tween at the same surface. Pre-R457
|
|
4572
|
+
pin lifted ls 0 → 0.5px (R218→R432 3-tier) but the
|
|
4573
|
+
fw stayed planted at R63's 700 — locked groups
|
|
4574
|
+
read as wider-but-same-weight. R457 adds the
|
|
4575
|
+
weight axis so pinned groups read as tightened
|
|
4576
|
+
AND wider, matching the R416/R424/R425/R426/R444/
|
|
4577
|
+
R445/R446 "data tightens under attention" idiom
|
|
4578
|
+
(now extended to the parent-text scope at the
|
|
4579
|
+
group-label tier). R63 fill brighten + R432
|
|
4580
|
+
letter-spacing 0/0.25/0.5 3-tier + R55 transition
|
|
4581
|
+
list all preserved; extends to include 'font-
|
|
4582
|
+
weight 200ms ease-out' so the bump eases under
|
|
4583
|
+
the same cadence. */}
|
|
4584
|
+
{/* v0.11.0 #147 Hero D — Vincent 5401 ask: "dash 网络
|
|
4585
|
+
图里面这个工程的名字也太大了, 超级丑". Per Vincent
|
|
4586
|
+
screenshot 实测. Initial attempt moved label to
|
|
4587
|
+
bottom-right (#147 spec Option A); topo-overlap-test
|
|
4588
|
+
caught 7 grid collisions because cluster boxes have
|
|
4589
|
+
no bottom padding. Pivot to Option C: keep top-left
|
|
4590
|
+
anchor, shrink fontSize 13 → 9 (-31%, watermark
|
|
4591
|
+
register), dim default opacity 1 → 0.55 (hover/pin
|
|
4592
|
+
restore to 1). Net Twitter-grok improvement:
|
|
4593
|
+
cluster labels no longer dominate the canvas at
|
|
4594
|
+
rest; operator still hovers to find specific groups.
|
|
4595
|
+
Position unchanged to preserve the existing
|
|
4596
|
+
geometry that overlap-test gates. */}
|
|
3907
4597
|
<text
|
|
3908
4598
|
x={box.x + 12}
|
|
3909
|
-
y={box.y +
|
|
4599
|
+
y={box.y + 12}
|
|
3910
4600
|
fill={isHovered ? pal.legendHeadline : pal.legendText}
|
|
3911
|
-
fontSize="
|
|
4601
|
+
fontSize="9"
|
|
3912
4602
|
fontFamily="monospace"
|
|
3913
|
-
fontWeight=
|
|
4603
|
+
fontWeight={isPinned ? '800' : '700'}
|
|
4604
|
+
opacity={isPinned || isHovered ? 1 : 0.55}
|
|
4605
|
+
data-group-label-hovered={isHovered && !isPinned ? 'true' : 'false'}
|
|
4606
|
+
data-group-label-font-weight={isPinned ? '800' : '700'}
|
|
3914
4607
|
style={{
|
|
3915
|
-
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
|
|
3916
|
-
letterSpacing: isPinned ? '0.5px' :
|
|
4608
|
+
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out',
|
|
4609
|
+
letterSpacing: isPinned ? '0.5px' :
|
|
4610
|
+
isHovered ? '0.25px' : '0px',
|
|
3917
4611
|
}}
|
|
3918
4612
|
data-group-label={box.key}
|
|
3919
4613
|
data-group-label-pinned={isPinned ? 'true' : 'false'}
|
|
@@ -3947,13 +4641,67 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3947
4641
|
lives at a fixed dx=6 offset from the name, so a
|
|
3948
4642
|
digit-width jitter at 9→10 used to shift the
|
|
3949
4643
|
whole count visibly. Tabular locks it. */}
|
|
4644
|
+
{/* Round 366 / Loop: group label member-count tspan
|
|
4645
|
+
fontWeight 400 → 500. Sibling polish to R363
|
|
4646
|
+
recent-row alias text fw 400 → 500 + R364 legend-
|
|
4647
|
+
row label fw 400 → 500 — closes the per-row 'count
|
|
4648
|
+
is fw 500 against label-tier fw 700' pattern at
|
|
4649
|
+
the group-label scope (grid layout cluster mark).
|
|
4650
|
+
Hierarchy snapshot post-R366 across all 3 row
|
|
4651
|
+
surfaces:
|
|
4652
|
+
recent count(hot/cold) fw 700/600 (R320)
|
|
4653
|
+
recent alias fw 500 (R363)
|
|
4654
|
+
legend count fw 600 (R309)
|
|
4655
|
+
legend label fw 500 (R364)
|
|
4656
|
+
group name fw 700 (legacy)
|
|
4657
|
+
group count fw 500 (R366, this round)
|
|
4658
|
+
Monospace family + R225 tabular-nums lock digit
|
|
4659
|
+
width, so the fw bump is paint-only — bbox
|
|
4660
|
+
unchanged + overlap-test invariants hold. R229
|
|
4661
|
+
fill-inherit from parent label (hover-deepen-own-
|
|
4662
|
+
hue family) preserved. data-group-label-count-
|
|
4663
|
+
font-weight attr exposes the value for tests. */}
|
|
4664
|
+
{/* Round 444 / Loop: group label count tspan
|
|
4665
|
+
fontWeight 500 → 600 on isPinned. Extends the
|
|
4666
|
+
"data tightens under attention" typographic-
|
|
4667
|
+
weight pattern to a 5th anchor at the group-
|
|
4668
|
+
label-count scope:
|
|
4669
|
+
R416 chip-digit (chip hover)
|
|
4670
|
+
R424 panel-digit (panel hover)
|
|
4671
|
+
R425 hub-digit (hub hover)
|
|
4672
|
+
R426 edge-badge-digit (pin/hot)
|
|
4673
|
+
R444 group-label-count (pinned) ← this round
|
|
4674
|
+
Same idiom — when the group is locked, its
|
|
4675
|
+
member-count tightens typographically alongside
|
|
4676
|
+
the R432 letter-spacing spread (0 → 0.5px) on
|
|
4677
|
+
the parent label. Hover keeps rest fw (500) so
|
|
4678
|
+
the locked vs preview distinction at the type
|
|
4679
|
+
level stays intact — same gate R432 used.
|
|
4680
|
+
Monospace + R225 tabular-nums lock the digit
|
|
4681
|
+
width across fw changes; bbox unchanged; overlap-
|
|
4682
|
+
test invariants hold. transition list adds
|
|
4683
|
+
'font-weight 200ms ease-out' matching R432
|
|
4684
|
+
letter-spacing cadence. R229 fill-inherit
|
|
4685
|
+
preserved (parent text fill still drives the
|
|
4686
|
+
hover/pin color). data-group-label-count-font-
|
|
4687
|
+
weight + -pinned attrs exposed for tests. */}
|
|
4688
|
+
{/* v0.11.0 #147 — count tspan tracks parent fontSize:
|
|
4689
|
+
11 → 8 to match the new 9px label scale (parent
|
|
4690
|
+
dropped 13 → 9 with same -2px gap to the count
|
|
4691
|
+
suffix). dx="4" replaces dx="6" — the smaller
|
|
4692
|
+
glyph baseline doesn't need the wider gutter. */}
|
|
3950
4693
|
<tspan
|
|
3951
|
-
dx="
|
|
3952
|
-
fontSize="
|
|
3953
|
-
fontWeight=
|
|
4694
|
+
dx="4"
|
|
4695
|
+
fontSize="8"
|
|
4696
|
+
fontWeight={isPinned ? '600' : '500'}
|
|
3954
4697
|
data-group-label-count={box.key}
|
|
3955
4698
|
data-group-label-count-value={box.count}
|
|
3956
|
-
|
|
4699
|
+
data-group-label-count-pinned={isPinned ? 'true' : 'false'}
|
|
4700
|
+
data-group-label-count-font-weight={isPinned ? '600' : '500'}
|
|
4701
|
+
style={{
|
|
4702
|
+
fontVariantNumeric: 'tabular-nums',
|
|
4703
|
+
transition: 'font-weight 200ms ease-out',
|
|
4704
|
+
}}
|
|
3957
4705
|
>· {box.count}</tspan>
|
|
3958
4706
|
{/* Round 58 / Loop: status mix pip strip. Compact text-
|
|
3959
4707
|
based chips (e.g. "2w 1i") so the strip stays inside
|
|
@@ -4010,11 +4758,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4010
4758
|
breakdown. Multi-tier groups (e.g. `alpha · 3
|
|
4011
4759
|
2w 1i`) render unchanged — those pips genuinely
|
|
4012
4760
|
add breakdown info that the total doesn't carry. */}
|
|
4761
|
+
{/* Round 458 / Loop — Hero D #147 finishing polish on top of
|
|
4762
|
+
N站牛/codex preview.125 (Option C: top-left label fontSize
|
|
4763
|
+
13→9 + opacity 0.55 rest / 1 hover+pin, count tspan 11→8).
|
|
4764
|
+
That ship left the 3 status pips at fontSize=11 — visibly
|
|
4765
|
+
DOMINATING the now-9px parent label they trail. Result on
|
|
4766
|
+
a 5-member cluster: `alpha · 5 3w 2i` renders inside-out
|
|
4767
|
+
as "tiny name + tiny count + BIG bright pips" rather than
|
|
4768
|
+
a coherent right-tail of metadata. R458 scales the 3 pips
|
|
4769
|
+
to fontSize=8 (matches count tspan) and tightens dx 8/4/4
|
|
4770
|
+
→ 6/3/3 (gutter ratio 0.73/0.36 glyph-widths @ 11px ≈
|
|
4771
|
+
0.75/0.38 glyph-widths @ 8px — same visual rhythm at the
|
|
4772
|
+
smaller scale). The whole group-label bottom-right strip
|
|
4773
|
+
now reads as a unified 9/8/8/8 typographic ladder:
|
|
4774
|
+
name (parent <text>) fontSize 9 fw 700/800
|
|
4775
|
+
· count (1st tspan) fontSize 8 fw 500/600
|
|
4776
|
+
Nw (2nd tspan) fontSize 8 fw 600
|
|
4777
|
+
Ni (3rd tspan) fontSize 8 fw 600
|
|
4778
|
+
No (4th tspan) fontSize 8 fw 600
|
|
4779
|
+
Closes Vincent /goal 5401 ("太大太丑") at the pip-strip
|
|
4780
|
+
tier; with codex preview.125 the spec is fully realized.
|
|
4781
|
+
Geometry-only attribute changes — bbox tightens slightly
|
|
4782
|
+
(8px chars vs 11px chars stay inside the original 240px
|
|
4783
|
+
hitbox max) so topo-overlap-test invariants hold.
|
|
4784
|
+
tabular-nums + anet-fade-in + theme-eased fill 200ms
|
|
4785
|
+
preserved on every tspan. */}
|
|
4013
4786
|
{box.statuses.working > 0 && box.statuses.working !== box.count && (
|
|
4014
4787
|
<tspan
|
|
4015
|
-
dx="
|
|
4788
|
+
dx="6"
|
|
4016
4789
|
fill={isLight ? '#059669' : '#22c55e'}
|
|
4017
|
-
fontSize="
|
|
4790
|
+
fontSize="8"
|
|
4018
4791
|
fontWeight="600"
|
|
4019
4792
|
className="anet-fade-in"
|
|
4020
4793
|
data-group-pip="working"
|
|
@@ -4023,9 +4796,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4023
4796
|
)}
|
|
4024
4797
|
{box.statuses.idle > 0 && box.statuses.idle !== box.count && (
|
|
4025
4798
|
<tspan
|
|
4026
|
-
dx="
|
|
4799
|
+
dx="3"
|
|
4027
4800
|
fill={isLight ? '#0d9488' : '#2dd4bf'}
|
|
4028
|
-
fontSize="
|
|
4801
|
+
fontSize="8"
|
|
4029
4802
|
fontWeight="600"
|
|
4030
4803
|
className="anet-fade-in"
|
|
4031
4804
|
data-group-pip="idle"
|
|
@@ -4034,9 +4807,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4034
4807
|
)}
|
|
4035
4808
|
{box.statuses.offline > 0 && box.statuses.offline !== box.count && (
|
|
4036
4809
|
<tspan
|
|
4037
|
-
dx="
|
|
4810
|
+
dx="3"
|
|
4038
4811
|
fill={isLight ? '#94a3b8' : '#6b7280'}
|
|
4039
|
-
fontSize="
|
|
4812
|
+
fontSize="8"
|
|
4040
4813
|
fontWeight="600"
|
|
4041
4814
|
className="anet-fade-in"
|
|
4042
4815
|
data-group-pip="offline"
|
|
@@ -4083,13 +4856,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4083
4856
|
// synchronised per-edge animation set.
|
|
4084
4857
|
const stagger = (index * 0.37) % duration;
|
|
4085
4858
|
// Round 10 / Loop: freshness fade. An edge that fired ≤30s ago
|
|
4086
|
-
// stays at full intensity; over 5 minutes it decays to
|
|
4087
|
-
// Surfaces "what's happening now" vs background
|
|
4088
|
-
// hiding old flow entirely (some context
|
|
4089
|
-
// captured at useMemo-recompute time
|
|
4090
|
-
// — accuracy is within the poll
|
|
4859
|
+
// stays at full intensity; over 5 minutes it decays to a
|
|
4860
|
+
// floor. Surfaces "what's happening now" vs background
|
|
4861
|
+
// chatter without hiding old flow entirely (some context
|
|
4862
|
+
// still useful). `now` captured at useMemo-recompute time
|
|
4863
|
+
// (every 5s message refresh) — accuracy is within the poll
|
|
4864
|
+
// interval, plenty.
|
|
4865
|
+
//
|
|
4866
|
+
// Round 406 / Loop: edge freshness fade floor 0.35 → 0.40.
|
|
4867
|
+
// Stale-state legibility lift family (6th anchor) — pre-
|
|
4868
|
+
// R406 edges older than 5 minutes faded to α=0.35 (a 65 %
|
|
4869
|
+
// dim against full intensity). The decay rate is the same
|
|
4870
|
+
// 1 - ageMs/300s curve; only the FLOOR shifts. Sibling
|
|
4871
|
+
// treatment to:
|
|
4872
|
+
// R317 subordinate-text gray-500 → gray-400
|
|
4873
|
+
// R358 freshness ramp floor 0.25 → 0.30
|
|
4874
|
+
// R372 minimap offline-dot opacity 0.5 → 0.6
|
|
4875
|
+
// R404 hub-halo cyber trough 0.08 → 0.10
|
|
4876
|
+
// R405 hub-halo light trough 0.32 → 0.34
|
|
4877
|
+
// R406 edge freshness floor 0.35 → 0.40 (this round)
|
|
4878
|
+
// Edges past 5min now sit at 40% intensity instead of 35%
|
|
4879
|
+
// — they still recede against fresh edges but read
|
|
4880
|
+
// legibly enough to convey "this conversation existed".
|
|
4881
|
+
// ageMs threshold for the 5-minute decay unchanged; the
|
|
4882
|
+
// decay curve shape (linear) unchanged. The visual delta
|
|
4883
|
+
// is most pronounced on edges between 5-60 minutes old —
|
|
4884
|
+
// where the floor was binding pre-R406.
|
|
4091
4885
|
const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;
|
|
4092
|
-
const fresh = Math.max(0.
|
|
4886
|
+
const fresh = Math.max(0.40, 1 - ageMs / (5 * 60 * 1000));
|
|
4093
4887
|
// Round 16 arrow-tier binning — keep `topo-arrow` as the
|
|
4094
4888
|
// medium tier id so the legend swatch picks it up unchanged.
|
|
4095
4889
|
const arrowId = link.count <= 2 ? 'topo-arrow-s'
|
|
@@ -4154,7 +4948,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4154
4948
|
// tracks it even at low message counts (width was 3 → 4.5 on
|
|
4155
4949
|
// hover). 1.4× is enough to read as "lifted" without
|
|
4156
4950
|
// breaching the 16-px hitbox bound.
|
|
4157
|
-
|
|
4951
|
+
// Round 436 / Loop: extend the thickening to the
|
|
4952
|
+
// "hovered-endpoint" case — when hoveredAlias matches one
|
|
4953
|
+
// of this edge's endpoints, lift width by 1.15× (capped at
|
|
4954
|
+
// 8 px to stay clear of the 16-px hitbox). Pre-R436 the
|
|
4955
|
+
// edgeOpacityMul=1.7 (line 4703) lifted the matched edge's
|
|
4956
|
+
// OPACITY when an endpoint was hovered but the stroke-WIDTH
|
|
4957
|
+
// stayed at base — so the edge faded brighter without
|
|
4958
|
+
// thickening, leaving the paint+geometry axes mismatched.
|
|
4959
|
+
// Mirror of R430/R435 hub-spoke pattern (opacity + stroke-
|
|
4960
|
+
// width co-lift on hoveredAlias); R436 brings the same
|
|
4961
|
+
// dual-axis "this node's link" gesture to the edge scope.
|
|
4962
|
+
// 1.15× is subtler than isHoveredEdge=1.4× because
|
|
4963
|
+
// endpoint-hover lifts MANY edges at once (every edge
|
|
4964
|
+
// incident on the hovered node) while edge-hover lifts ONE
|
|
4965
|
+
// — the gesture should read as "highlighted" not "loud".
|
|
4966
|
+
// R166 stroke-width 300ms transition already in the
|
|
4967
|
+
// visible-path style list so the lift eases for free.
|
|
4968
|
+
const isEndpointHoveredEdge = !!hoveredAlias && (link.from === hoveredAlias || link.to === hoveredAlias);
|
|
4969
|
+
const renderWidth = isHoveredEdge ? Math.min(width * 1.4, 10)
|
|
4970
|
+
: isEndpointHoveredEdge ? Math.min(width * 1.15, 8)
|
|
4971
|
+
: width;
|
|
4158
4972
|
return (
|
|
4159
4973
|
<g
|
|
4160
4974
|
key={link.key}
|
|
@@ -4241,30 +5055,89 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4241
5055
|
online chips to splice in additional properties
|
|
4242
5056
|
beside Tailwind's). data-edge-flow-rail attr
|
|
4243
5057
|
surfaces the path for test introspection. */}
|
|
5058
|
+
{/* Round 381 / Loop: edge visible flow path picks up
|
|
5059
|
+
strokeLinecap='round'. Sibling polish to R378
|
|
5060
|
+
flow-rail dashed linecap — both flow-element paths
|
|
5061
|
+
(visible primary + dashed secondary rail) now share
|
|
5062
|
+
'round' linecap vocabulary. The visible path runs
|
|
5063
|
+
source-node → dest-node as one continuous line, so
|
|
5064
|
+
the dest-end is covered by the markerEnd arrow and
|
|
5065
|
+
the source-end usually sits inside the source-node
|
|
5066
|
+
circle. At certain alignments (post-zoom, post-
|
|
5067
|
+
layout-switch transitions), the source-end may peek
|
|
5068
|
+
out by a fraction of a px past the node edge —
|
|
5069
|
+
round caps render that overshoot as a smooth half-
|
|
5070
|
+
disc instead of a sharp rectangle. Pure paint
|
|
5071
|
+
refinement, geometry-safe (bbox of the stroke
|
|
5072
|
+
unchanged at the join with the arrow marker).
|
|
5073
|
+
data-edge-visible-linecap attr exposes the value
|
|
5074
|
+
for tests. */}
|
|
4244
5075
|
<path
|
|
4245
5076
|
d={path}
|
|
4246
5077
|
fill="none"
|
|
4247
5078
|
stroke={pal.flowEdge}
|
|
4248
5079
|
strokeWidth={renderWidth}
|
|
5080
|
+
strokeLinecap="round"
|
|
4249
5081
|
opacity={Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)}
|
|
4250
5082
|
filter={isLight ? undefined : 'url(#topo-glow)'}
|
|
4251
5083
|
markerEnd={`url(#${arrowId})`}
|
|
4252
5084
|
data-edge-visible={link.key}
|
|
5085
|
+
data-edge-visible-linecap="round"
|
|
5086
|
+
data-edge-visible-endpoint-hovered={isEndpointHoveredEdge ? 'true' : 'false'}
|
|
5087
|
+
data-edge-visible-stroke-width={renderWidth}
|
|
4253
5088
|
style={{
|
|
4254
5089
|
pointerEvents: 'none',
|
|
4255
5090
|
transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out',
|
|
4256
5091
|
}}
|
|
4257
5092
|
/>
|
|
5093
|
+
{/* Round 378 / Loop: edge flow-path dashed-rail picks
|
|
5094
|
+
up strokeLinecap='round'. Pre-R378 the rail
|
|
5095
|
+
rendered '2 12' dashes as sharp 1×2 rectangles
|
|
5096
|
+
against the canvas backdrop; default 'butt' caps
|
|
5097
|
+
leave dash ends square. R378 rounds each cap so
|
|
5098
|
+
the dashes read as soft 3-px pills (1 px stroke +
|
|
5099
|
+
0.5 px round cap each end). The flow-rail is the
|
|
5100
|
+
secondary 'invisible-spine' line that gives the
|
|
5101
|
+
R57 spoke flow a directional rail to slide along
|
|
5102
|
+
— rounding the dashes softens its presence
|
|
5103
|
+
against the primary visible flow path (R245 has
|
|
5104
|
+
no strokeLinecap so it inherits 'butt' on a
|
|
5105
|
+
continuous line, irrelevant). Geometry-safe:
|
|
5106
|
+
round caps only widen the visible dash; the
|
|
5107
|
+
bbox of the path is unchanged so overlap-test
|
|
5108
|
+
invariants hold. data-edge-flow-rail-linecap
|
|
5109
|
+
attr exposes the value for tests. */}
|
|
4258
5110
|
<path
|
|
4259
5111
|
id={`flow-path-${index}`}
|
|
4260
5112
|
d={path}
|
|
4261
5113
|
fill="none"
|
|
4262
5114
|
stroke={pal.flowPath}
|
|
4263
|
-
strokeWidth
|
|
5115
|
+
/* Round 437 / Loop: flow-rail strokeWidth hover lift —
|
|
5116
|
+
1 → 1.5 on (isHoveredEdge || isEndpointHoveredEdge).
|
|
5117
|
+
Pre-R437 the dashed rail sat at sw=1 always while the
|
|
5118
|
+
visible flow path above it lifted (R50 ×1.4 on
|
|
5119
|
+
isHoveredEdge / R436 ×1.15 on isEndpointHoveredEdge).
|
|
5120
|
+
The two edge paint layers were mismatched on hover:
|
|
5121
|
+
top layer thickened, underline stayed thin — so the
|
|
5122
|
+
hover gesture lifted only half the edge surface.
|
|
5123
|
+
R437 lifts the underline too so the whole edge
|
|
5124
|
+
reads as "raised" on hover, not just its bright
|
|
5125
|
+
top stripe. Same +0.5 absolute delta R435 used at
|
|
5126
|
+
hub-spoke scope (1→1.25 there, slightly bigger
|
|
5127
|
+
here because the rail's base 1 is at the kerning
|
|
5128
|
+
floor and needs more lift to register).
|
|
5129
|
+
Transition list extends to include stroke-width
|
|
5130
|
+
300ms so the new lift eases under the same R166
|
|
5131
|
+
cadence as the visible path's stroke-width. */
|
|
5132
|
+
strokeWidth={(isHoveredEdge || isEndpointHoveredEdge) ? 1.5 : 1}
|
|
4264
5133
|
strokeDasharray="2 12"
|
|
5134
|
+
strokeLinecap="round"
|
|
4265
5135
|
opacity={Math.min(1, (isLight ? 0.4 : 0.75) * fresh * edgeOpacityMul)}
|
|
4266
5136
|
data-edge-flow-rail={link.key}
|
|
4267
|
-
|
|
5137
|
+
data-edge-flow-rail-linecap="round"
|
|
5138
|
+
data-edge-flow-rail-stroke-width={(isHoveredEdge || isEndpointHoveredEdge) ? 1.5 : 1}
|
|
5139
|
+
data-edge-flow-rail-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
|
|
5140
|
+
style={{ transition: 'opacity 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out' }}
|
|
4268
5141
|
/>
|
|
4269
5142
|
{!reducedMotion && (
|
|
4270
5143
|
/* Round 103 / Loop: phase-stagger the particles so
|
|
@@ -4279,25 +5152,40 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4279
5152
|
multiple). Edge order is stable (sorted by recent
|
|
4280
5153
|
activity), so the offsets feel calm rather than
|
|
4281
5154
|
reshuffling each refresh. */
|
|
5155
|
+
/* Round 422 / Loop: edge flow particle radius 4 → 4.5.
|
|
5156
|
+
Visual-weight bump family (15th anchor) — particles
|
|
5157
|
+
riding along the edge animateMotion path get +0.5px
|
|
5158
|
+
radius lift, increasing visual area by ~27%
|
|
5159
|
+
(π·4.5² / π·4² = 1.27). Sibling magnitude to R383
|
|
5160
|
+
recent-row pip 1.8 → 2.0 (+25% area), R384 minimap
|
|
5161
|
+
online dot 1.7 → 1.9 (+25% area). R251 fill +
|
|
5162
|
+
R252 transitions + R103 phase-stagger animateMotion
|
|
5163
|
+
all preserved. data-edge-particle-radius attr
|
|
5164
|
+
exposes the value for tests. */
|
|
4282
5165
|
<circle
|
|
4283
|
-
|
|
5166
|
+
/* Round 439 / Loop: edge flow particle radius hover
|
|
5167
|
+
lift — r 4.5 → 5.5 on (isHoveredEdge ||
|
|
5168
|
+
isEndpointHoveredEdge). Continues edge paint-
|
|
5169
|
+
layer parity arc (R436 visible path sw / R437
|
|
5170
|
+
flow-rail sw / R439 particle r) so the whole
|
|
5171
|
+
edge surface — including the moving particle —
|
|
5172
|
+
lifts on hover, not just the static stripes.
|
|
5173
|
+
+1px radius gives ~50% area boost. Subtler than
|
|
5174
|
+
1.4× sw bump on visible path because the
|
|
5175
|
+
particle is already small + motion-bright;
|
|
5176
|
+
+1px reads as "the dot caught attention"
|
|
5177
|
+
without overshadowing the path lift. R252
|
|
5178
|
+
transition list extends to include r 200ms so
|
|
5179
|
+
the size change eases under the same fill/
|
|
5180
|
+
opacity cadence. */
|
|
5181
|
+
r={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}
|
|
4284
5182
|
fill={pal.flowParticle}
|
|
4285
5183
|
filter={isLight ? undefined : 'url(#topo-glow)'}
|
|
4286
5184
|
opacity={Math.min(1, fresh * edgeOpacityMul)}
|
|
4287
5185
|
data-edge-particle={link.key}
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
↔ light #f59e0b amber) snapped on theme toggle
|
|
4292
|
-
while every other edge element eased (R245
|
|
4293
|
-
paths, R251 badge, R242 chat-target, R233
|
|
4294
|
-
endpoint ring). opacity is freshness-driven so
|
|
4295
|
-
it transitions per-frame as fresh decays anyway
|
|
4296
|
-
— but adding opacity to the explicit transition
|
|
4297
|
-
list also covers theme toggle (R3 className
|
|
4298
|
-
transition-opacity duration-300 was previously
|
|
4299
|
-
absent on this circle). */
|
|
4300
|
-
style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out' }}
|
|
5186
|
+
data-edge-particle-radius={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}
|
|
5187
|
+
data-edge-particle-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
|
|
5188
|
+
style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out, r 200ms ease-out' }}
|
|
4301
5189
|
>
|
|
4302
5190
|
<animateMotion
|
|
4303
5191
|
dur={`${duration}s`}
|
|
@@ -4628,14 +5516,137 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4628
5516
|
R251 closes the per-edge surface theme-toggle
|
|
4629
5517
|
smoothness — every theme-driven property on
|
|
4630
5518
|
every edge element now eases under cyber↔light. */}
|
|
5519
|
+
{/* Round 367 / Loop: edge midpoint badge rest
|
|
5520
|
+
stroke-width 1 → 1.25. Sibling visual-weight
|
|
5521
|
+
bump family (7th canvas anchor now):
|
|
5522
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
5523
|
+
R295 legend swatch base radius 5.5 → 6
|
|
5524
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
5525
|
+
R360 hub digit fontSize 11 → 12
|
|
5526
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
5527
|
+
R365 hub-highlight base radius 5 → 5.5
|
|
5528
|
+
R367 edge-badge rest stroke 1 → 1.25 (this round)
|
|
5529
|
+
Cold edge badges gain ~25 % stroke presence
|
|
5530
|
+
(1.25/1.0 = 1.25). Stays clear of the R51
|
|
5531
|
+
overlap-test sentinel values (1.5 / 3 reserved
|
|
5532
|
+
for node strokes — the test selector is gated
|
|
5533
|
+
to g[data-node] ancestors so this edge-internal
|
|
5534
|
+
circle is invisible to that probe anyway, but
|
|
5535
|
+
1.25 is a safe non-sentinel value regardless).
|
|
5536
|
+
R188 transition list 'stroke-width 300ms ease-
|
|
5537
|
+
out' still smoothes the hot/pin flip — now
|
|
5538
|
+
1.25 → 2 instead of 1 → 2, same ease pace.
|
|
5539
|
+
data-edge-badge-stroke-width-rest exposes the
|
|
5540
|
+
new baseline for tests. */}
|
|
5541
|
+
{/* Round 371 / Loop: edge-badge cyber opacity 0.82
|
|
5542
|
+
→ 0.85. Sibling theme-consistency polish to R370
|
|
5543
|
+
hub hover-ring 0.7 → 0.8. R251 designed this
|
|
5544
|
+
badge with opacity 0.82 (cyber) / 0.95 (light)
|
|
5545
|
+
— 13 % delta. Cyber-theme dark bg needs more
|
|
5546
|
+
alpha to read as 'present'; R371 narrows the
|
|
5547
|
+
gap to 10 %, bringing the badge closer to light
|
|
5548
|
+
theme's 0.95 floor. Light stays at 0.95
|
|
5549
|
+
(already in the legibility band). data-edge-
|
|
5550
|
+
badge-opacity attr exposes the resolved value.
|
|
5551
|
+
Theme-consistency polish family:
|
|
5552
|
+
R246/R247 panel transition family
|
|
5553
|
+
R251 edge badge fill/opacity baseline
|
|
5554
|
+
R370 hub hover-ring cyber 0.7 → 0.8
|
|
5555
|
+
R371 edge badge cyber 0.82 → 0.85 (this round)
|
|
5556
|
+
R164 r=9/10.5 hover-lift + R188/R251 transition
|
|
5557
|
+
list + R367 strokeWidth=1.25 cold rest preserved. */}
|
|
5558
|
+
{/* Round 394 / Loop: edge-badge gains a hover
|
|
5559
|
+
strokeWidth tier (1.5) between cold rest
|
|
5560
|
+
(R367 1.25) and pin/hot (2). Pre-R394 the
|
|
5561
|
+
badge lifted only its radius on hover (R164
|
|
5562
|
+
9 → 10.5); the stroke stayed at cold rest
|
|
5563
|
+
1.25 unless pin/hot kicked in, so a plain
|
|
5564
|
+
hover felt half-lifted — geometry expanded
|
|
5565
|
+
while the contour stayed thin. R394 adds
|
|
5566
|
+
strokeWidth=1.5 on isHoveredEdge so hover
|
|
5567
|
+
now lifts both r AND stroke in concert —
|
|
5568
|
+
same pattern R385 used for the hub hover-
|
|
5569
|
+
ring (1.5 → 1.75) where the ring's three
|
|
5570
|
+
hover axes (r grow / opacity fade-in /
|
|
5571
|
+
stroke thicken) all rise together.
|
|
5572
|
+
Three-tier stroke hierarchy now:
|
|
5573
|
+
cold rest 1.25 (R367)
|
|
5574
|
+
hovered 1.5 (R394 — this round)
|
|
5575
|
+
pinned / hot 2.0 (R188)
|
|
5576
|
+
R51 sentinel concern: strokeWidth=1.5 is
|
|
5577
|
+
one of the two sentinels reserved for node
|
|
5578
|
+
detection, but the R51 selector is gated
|
|
5579
|
+
to `g[data-node]` ancestors so this edge-
|
|
5580
|
+
internal circle is invisible to the probe
|
|
5581
|
+
(same lesson R177 hub hover-ring + R367
|
|
5582
|
+
cold rest documented). 300ms strokeWidth
|
|
5583
|
+
transition already in the style list eases
|
|
5584
|
+
the new tier naturally. data-edge-badge-
|
|
5585
|
+
stroke-width-hover attr exposes the hover
|
|
5586
|
+
value for tests. */}
|
|
5587
|
+
{/* Round 395 / Loop: edge-badge gains a third
|
|
5588
|
+
hover axis — opacity 0.85 (cyber) / 0.95
|
|
5589
|
+
(light) → 1.0 on isHoveredEdge. Pre-R395
|
|
5590
|
+
hovering thickened the stroke (R394 1.25 →
|
|
5591
|
+
1.5) and grew the radius (R164 9 → 10.5)
|
|
5592
|
+
but the badge's translucency stayed put at
|
|
5593
|
+
R371's rest alpha (cyber 0.85 / light 0.95).
|
|
5594
|
+
R395 lifts hover to a clean 1.0 — fully
|
|
5595
|
+
opaque — so the hovered badge reads as
|
|
5596
|
+
"in focus" against the dim siblings.
|
|
5597
|
+
Three-axis hover-lift parity now complete:
|
|
5598
|
+
hub hover-ring (R177/R370/R385):
|
|
5599
|
+
r 14 → 17, opacity 0 → 0.8 cyber, sw 1.5 → 1.75
|
|
5600
|
+
edge badge (R164/R394/R395):
|
|
5601
|
+
r 9 → 10.5, sw 1.25 → 1.5, opacity → 1.0
|
|
5602
|
+
200ms opacity transition (already in the
|
|
5603
|
+
style list) eases the new axis naturally.
|
|
5604
|
+
R371 rest opacity (0.85 cyber / 0.95 light)
|
|
5605
|
+
preserved as the resting alpha — R395
|
|
5606
|
+
adds an isHoveredEdge override on top.
|
|
5607
|
+
data-edge-badge-opacity-hover attr exposes
|
|
5608
|
+
the hover value for tests. */}
|
|
5609
|
+
{/* Round 396 / Loop: extend the R395 opacity → 1.0
|
|
5610
|
+
lift to the pinned state. Pre-R396 the badge
|
|
5611
|
+
shared `r=10.5` on both hover AND pin (R164
|
|
5612
|
+
unified-lift) but R395's opacity lift fired
|
|
5613
|
+
ONLY on isHoveredEdge — pinned badges stayed
|
|
5614
|
+
at R371 rest alpha (cyber 0.85 / light 0.95).
|
|
5615
|
+
That left pin (sticky selection) reading
|
|
5616
|
+
softer than hover (transient preview), even
|
|
5617
|
+
though pin is the stronger commitment.
|
|
5618
|
+
R396 unifies hover + pin at opacity=1.0
|
|
5619
|
+
so the same data-edge-badge-lifted='true'
|
|
5620
|
+
surface uniformly carries full alpha. Pin
|
|
5621
|
+
stroke (R188 sw=2 + pal.legendHeadline color)
|
|
5622
|
+
continues to differentiate pin from hover —
|
|
5623
|
+
the opacity track now closes the lift parity.
|
|
5624
|
+
The new gate (isHoveredEdge || isPinned)
|
|
5625
|
+
mirrors the existing R164 r-lift gate, so
|
|
5626
|
+
the badge has a single "active state"
|
|
5627
|
+
signature across r + opacity.
|
|
5628
|
+
200ms opacity transition (already in style
|
|
5629
|
+
list) eases pin/unpin naturally. R371 rest
|
|
5630
|
+
opacity preserved as the resting alpha.
|
|
5631
|
+
data-edge-badge-opacity-hover renamed
|
|
5632
|
+
semantically to -active (covers hover+pin)
|
|
5633
|
+
via the new -opacity-active attr; the
|
|
5634
|
+
legacy -opacity-hover attr kept for R395
|
|
5635
|
+
test compatibility. */}
|
|
4631
5636
|
<circle
|
|
4632
5637
|
cx={badgeX} cy={badgeY}
|
|
4633
5638
|
r={isHoveredEdge || isPinned ? 10.5 : 9}
|
|
4634
5639
|
fill={pal.legendBox.fill}
|
|
4635
5640
|
stroke={isPinned ? pal.legendHeadline : isHot ? hotStroke : pal.flowEdge}
|
|
4636
|
-
strokeWidth={isPinned ? 2 : isHot ? 2 : 1}
|
|
4637
|
-
opacity={isLight ? 0.95 : 0.
|
|
5641
|
+
strokeWidth={isPinned ? 2 : isHot ? 2 : isHoveredEdge ? 1.5 : 1.25}
|
|
5642
|
+
opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}
|
|
4638
5643
|
data-edge-badge-lifted={(isHoveredEdge || isPinned) ? 'true' : 'false'}
|
|
5644
|
+
data-edge-badge-stroke-width-rest="1.25"
|
|
5645
|
+
data-edge-badge-stroke-width-hover="1.5"
|
|
5646
|
+
data-edge-badge-opacity={(isHoveredEdge || isPinned) ? 1 : (isLight ? 0.95 : 0.85)}
|
|
5647
|
+
data-edge-badge-opacity-rest={isLight ? 0.95 : 0.85}
|
|
5648
|
+
data-edge-badge-opacity-hover="1"
|
|
5649
|
+
data-edge-badge-opacity-active="1"
|
|
4639
5650
|
style={{ transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out' }}
|
|
4640
5651
|
/>
|
|
4641
5652
|
{/* Round 224 / Loop: edge badge text gains the 4th
|
|
@@ -4674,16 +5685,75 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4674
5685
|
x={badgeX} y={badgeY + 3}
|
|
4675
5686
|
textAnchor="middle"
|
|
4676
5687
|
fill={pal.legendHeadline}
|
|
4677
|
-
|
|
5688
|
+
/* Round 361 / Loop: edge midpoint badge text
|
|
5689
|
+
fontSize 10 → 11. Sibling visual-weight bump
|
|
5690
|
+
to R360 hub digit 11 → 12. The badge digit
|
|
5691
|
+
is the per-edge equivalent of the hub digit
|
|
5692
|
+
— a high-information scalar (link.count) at
|
|
5693
|
+
a stable canvas position. Pre-R361 fontSize=
|
|
5694
|
+
10 + R220 letter-spacing 0.4 + R224 tabular-
|
|
5695
|
+
nums made the digit READABLE but small
|
|
5696
|
+
against the r=9 / 18-px badge envelope;
|
|
5697
|
+
fontSize=11 nudges the glyph ~10 % bigger
|
|
5698
|
+
(bbox ~7×10 px from ~6×9 px) so the count
|
|
5699
|
+
reads more cleanly at glance — still well
|
|
5700
|
+
inside the r=9 idle circle and the r=10.5
|
|
5701
|
+
hover/pin lift (R164). y=badgeY+3 empirical
|
|
5702
|
+
vertical centring kept (1px drift at the
|
|
5703
|
+
bumped size is below the noise floor in
|
|
5704
|
+
the on-curve flow path).
|
|
5705
|
+
Visual-weight bump family:
|
|
5706
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
5707
|
+
R295 legend swatch base radius 5.5 → 6
|
|
5708
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
5709
|
+
R360 hub digit fontSize 11 → 12
|
|
5710
|
+
R361 edge-badge digit fontSize 10 → 11 (this round)
|
|
5711
|
+
data-edge-badge-text-font-size attr exposes
|
|
5712
|
+
the value for tests. R220 pin/hot letter-
|
|
5713
|
+
spacing tween + R224 tabular-nums + R188
|
|
5714
|
+
stroke-width pin/hot transitions all preserved. */
|
|
5715
|
+
fontSize="11"
|
|
4678
5716
|
fontFamily="monospace"
|
|
4679
|
-
fontWeight
|
|
5717
|
+
/* R426 — edge-badge digit fontWeight 700 → 800 on
|
|
5718
|
+
(isPinned || isHot). 4th anchor on the "data
|
|
5719
|
+
tightens under attention" typographic-weight
|
|
5720
|
+
pattern:
|
|
5721
|
+
R416 chip-digit (chip-row hover trigger)
|
|
5722
|
+
R424 panel-digit (panel hover trigger)
|
|
5723
|
+
R425 hub-digit (hub hover trigger)
|
|
5724
|
+
R426 edge-badge-digit (pin/hot trigger) ← this
|
|
5725
|
+
The badge digit is the per-edge equivalent of
|
|
5726
|
+
the hub digit (R361 sibling fontSize bump
|
|
5727
|
+
reasoning). Stacks with R188 stroke-width pin/
|
|
5728
|
+
hot lift (1.25 → 1.5) + R220 letter-spacing pin/
|
|
5729
|
+
hot tween (0 → 0.4) for a 3-axis pin/hot signa-
|
|
5730
|
+
ture (edge structure + text spacing + text
|
|
5731
|
+
weight). The R408 transition is letter-spacing
|
|
5732
|
+
300ms; R426 appends font-weight 300ms so the
|
|
5733
|
+
weight bump co-eases under the same cadence. */
|
|
5734
|
+
fontWeight={(isPinned || isHot) ? '800' : '700'}
|
|
4680
5735
|
data-edge-badge-text={link.key}
|
|
4681
5736
|
data-edge-badge-text-pin={(isPinned || isHot) ? 'true' : 'false'}
|
|
5737
|
+
data-edge-badge-text-font-size="11"
|
|
4682
5738
|
style={{
|
|
4683
5739
|
pointerEvents: 'none',
|
|
4684
5740
|
fontVariantNumeric: 'tabular-nums',
|
|
4685
|
-
|
|
4686
|
-
|
|
5741
|
+
/* R431 — edge-badge digit 3-tier letter-spacing:
|
|
5742
|
+
rest 0 / isHoveredEdge 0.2 / (isPinned || isHot) 0.4.
|
|
5743
|
+
Mirrors R427 node-alias 3-tier (rest/hover/chat-
|
|
5744
|
+
target → 0/0.3/0.5) at edge-badge scope. Pre-R431
|
|
5745
|
+
letter-spacing only fired on pin/hot (R220) while
|
|
5746
|
+
pure edge hover lifted stroke (R394) + opacity
|
|
5747
|
+
(R395) + radius (R164) but left the text dead-
|
|
5748
|
+
typographic. R431 adds the missing typographic
|
|
5749
|
+
spacing axis to the edge-hover gesture so the
|
|
5750
|
+
text rises with the badge geometry. Pin/hot
|
|
5751
|
+
tier (0.4) still wins; hover is the mid step.
|
|
5752
|
+
Hover-letter-spacing family extension (7 anchors
|
|
5753
|
+
now): R344/R345/R347/R351/R420/R427/R431. */
|
|
5754
|
+
letterSpacing: (isPinned || isHot) ? '0.4px' :
|
|
5755
|
+
isHoveredEdge ? '0.2px' : '0px',
|
|
5756
|
+
transition: 'letter-spacing 300ms ease-out, font-weight 300ms ease-out',
|
|
4687
5757
|
}}
|
|
4688
5758
|
>{link.count}</text>
|
|
4689
5759
|
</g>
|
|
@@ -4794,27 +5864,103 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4794
5864
|
: workingCount <= 2 ? 1
|
|
4795
5865
|
: workingCount <= 5 ? 2
|
|
4796
5866
|
: 3;
|
|
5867
|
+
// Round 404 / Loop: hub-halo cyber trough opacity 0.08 →
|
|
5868
|
+
// 0.10. Pre-R404 the breath's low-point sat at α=0.08
|
|
5869
|
+
// cyber (per R84 family tuning) — the halo nearly faded
|
|
5870
|
+
// out at trough on the dark canvas. R404 lifts cyber
|
|
5871
|
+
// trough to 0.10. Per-bucket peak amplitudes [0.16/0.20/
|
|
5872
|
+
// 0.26/0.32] stay exactly tuned.
|
|
5873
|
+
//
|
|
5874
|
+
// Round 405 / Loop: hub-halo LIGHT trough 0.32 → 0.34 —
|
|
5875
|
+
// symmetric +0.02 lift to mirror R404's cyber treatment
|
|
5876
|
+
// across both themes. Pre-R405 only cyber got the lift
|
|
5877
|
+
// (R404 docstring noted "light already at the strong
|
|
5878
|
+
// end" as deliberate); but the cyber/light delta in
|
|
5879
|
+
// R404 was an inconsistency in the family pattern.
|
|
5880
|
+
// R405 closes the symmetry — both themes get +0.02
|
|
5881
|
+
// baseline lift, so the breath low-point reads with
|
|
5882
|
+
// matching confidence regardless of theme. Light peak
|
|
5883
|
+
// array [0.52/0.58/0.65/0.72] stays tuned.
|
|
5884
|
+
//
|
|
5885
|
+
// Stale-state legibility lift family (5 anchors now):
|
|
5886
|
+
// R317 subordinate-text gray-500 → gray-400
|
|
5887
|
+
// R358 freshness floor 0.25 → 0.30
|
|
5888
|
+
// R372 minimap offline-dot opacity 0.5 → 0.6
|
|
5889
|
+
// R404 hub-halo cyber trough 0.08 → 0.10
|
|
5890
|
+
// R405 hub-halo light trough 0.32 → 0.34 (this round)
|
|
5891
|
+
//
|
|
5892
|
+
// R84 per-bucket peak/dur + R245 ease-in-out spline
|
|
5893
|
+
// keySplines all preserved. Test fixture probes the
|
|
5894
|
+
// SMIL <animate> values via data-topo-hub-halo-trough
|
|
5895
|
+
// attr (now exposes both light + cyber resolved values).
|
|
4797
5896
|
const peakLight = [0.52, 0.58, 0.65, 0.72][busy];
|
|
4798
5897
|
const peakDark = [0.16, 0.20, 0.26, 0.32][busy];
|
|
4799
|
-
const troughLight = 0.
|
|
4800
|
-
const troughDark = 0.
|
|
5898
|
+
const troughLight = 0.34;
|
|
5899
|
+
const troughDark = 0.10;
|
|
4801
5900
|
const dur = [4.0, 3.2, 2.7, 2.4][busy];
|
|
4802
5901
|
const valuesLight = `${troughLight};${peakLight};${troughLight}`;
|
|
4803
5902
|
const valuesDark = `${troughDark};${peakDark};${troughDark}`;
|
|
5903
|
+
// Round 408 / Loop: hub-halo radius 18 → 20. The
|
|
5904
|
+
// grounding halo (the breathing outer circle around
|
|
5905
|
+
// the hub center) is the canvas's signature breath
|
|
5906
|
+
// element — R84 family. R408 bumps r=18 → 20 so the
|
|
5907
|
+
// breath extends slightly further while keeping 4px
|
|
5908
|
+
// clearance before the spoke origin (still room for
|
|
5909
|
+
// spoke start anchors). Visual presence on the
|
|
5910
|
+
// canvas focal point lifts ~23% area (π·20²/π·18²
|
|
5911
|
+
// = 1.23) without changing the per-bucket opacity
|
|
5912
|
+
// envelope or breath rhythm. Visual-weight bump
|
|
5913
|
+
// family 13th anchor — pairs with R404/R405 trough
|
|
5914
|
+
// lifts so the halo now breathes both with more
|
|
5915
|
+
// visible amplitude AND more visual footprint.
|
|
5916
|
+
// R84 per-bucket peak/dur invariants + R244 calc-
|
|
5917
|
+
// Mode='spline' + R245 ease-in-out keySplines all
|
|
5918
|
+
// preserved. data-topo-hub-halo-radius attr exposes
|
|
5919
|
+
// value for tests.
|
|
5920
|
+
/* Round 451 / Loop: hub center halo radius lift on
|
|
5921
|
+
hoveredHub — r 20 → 22 (+2px, ~21% area). Adds another
|
|
5922
|
+
geometric axis to the hub-hover signature stack
|
|
5923
|
+
alongside R177 ring radius lift + R209 digit scale +
|
|
5924
|
+
R425 digit fw + R370 halo opacity + R386 highlight
|
|
5925
|
+
opacity + R441 core fill chroma. Pre-R451 the halo
|
|
5926
|
+
r stayed planted at R408's 20px while the rest of
|
|
5927
|
+
the hub structure responded to hover. R451 makes
|
|
5928
|
+
the halo breath outward on hover so the focal pulse
|
|
5929
|
+
intensifies under attention. SMIL `<animate>` on
|
|
5930
|
+
opacity continues independently (animateAttr=
|
|
5931
|
+
'opacity' vs CSS-property r — non-conflicting). R408
|
|
5932
|
+
base radius 20 preserved as rest; +2 hover delta
|
|
5933
|
+
keeps clearance from the R177 hub-hover-ring at
|
|
5934
|
+
r=17 hover (halo is BEHIND the ring, halo r=22 sits
|
|
5935
|
+
5px beyond the ring's hover-r=17, still well within
|
|
5936
|
+
the hub canvas envelope). data-topo-hub-halo-radius
|
|
5937
|
+
attr now reports the dynamic value. */
|
|
5938
|
+
const isHaloHovered = !reducedMotion && hoveredHub;
|
|
5939
|
+
const haloR = isHaloHovered ? 22 : 20;
|
|
4804
5940
|
return (
|
|
4805
5941
|
<circle
|
|
4806
|
-
cx={cx} cy={cy}
|
|
5942
|
+
cx={cx} cy={cy}
|
|
4807
5943
|
fill={isLight ? '#d1fae5' : '#10b981'}
|
|
4808
5944
|
opacity={isLight ? 0.42 : 0.12}
|
|
4809
5945
|
data-hub-busyness={busy}
|
|
5946
|
+
data-topo-hub-halo-radius={haloR}
|
|
5947
|
+
data-topo-hub-halo-hovered={isHaloHovered ? 'true' : 'false'}
|
|
5948
|
+
data-topo-hub-halo-trough={isLight ? troughLight : troughDark}
|
|
5949
|
+
data-topo-hub-halo-peak={isLight ? peakLight : peakDark}
|
|
4810
5950
|
/* Round 253 / Loop: hub grounding halo fill transition
|
|
4811
5951
|
for theme toggle. Pre-R253 the base fill (cyber
|
|
4812
5952
|
#10b981 ↔ light #d1fae5) snapped while R244's SMIL
|
|
4813
5953
|
animate on opacity continued running. CSS fill
|
|
4814
5954
|
transition is independent of the SMIL animate
|
|
4815
5955
|
(different attributes), so they compose without
|
|
4816
|
-
conflict.
|
|
4817
|
-
|
|
5956
|
+
conflict.
|
|
5957
|
+
R451: r as CSS property (R197/R198 idiom) so the
|
|
5958
|
+
hover-radius tween eases smoothly under the same
|
|
5959
|
+
200ms cadence as fill. */
|
|
5960
|
+
style={{
|
|
5961
|
+
r: `${haloR}px`,
|
|
5962
|
+
transition: 'fill 200ms ease-out, r 200ms ease-out',
|
|
5963
|
+
} as React.CSSProperties}
|
|
4818
5964
|
>
|
|
4819
5965
|
{/* Round 244 / Loop: hub grounding halo breath gets
|
|
4820
5966
|
ease-in-out keySplines, matching the active-node
|
|
@@ -4859,12 +6005,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4859
6005
|
attr added for test introspection (the parent <g> at
|
|
4860
6006
|
line 3587 has data-topo-hub but the core specifically
|
|
4861
6007
|
is the canvas anchor). */}
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
6008
|
+
{(() => {
|
|
6009
|
+
/* Round 441 / Loop: hub center core fill brighten on
|
|
6010
|
+
hoveredHub. Pre-R441 the core was static (cyber
|
|
6011
|
+
emerald-500 #10b981 / light emerald-600 #059669) and
|
|
6012
|
+
the hub-hover gesture lifted ring radius (R177) +
|
|
6013
|
+
digit scale (R209) + digit fw (R425) + halo opacity
|
|
6014
|
+
(R370) + highlight opacity (R386) but the focal core
|
|
6015
|
+
ITSELF stayed planted at rest tone. R441 shifts the
|
|
6016
|
+
fill one emerald tier brighter on hover so the canvas
|
|
6017
|
+
anchor itself responds:
|
|
6018
|
+
cyber emerald-500 → emerald-400 (#10b981 → #34d399)
|
|
6019
|
+
light emerald-600 → emerald-500 (#059669 → #10b981)
|
|
6020
|
+
Same +100 step on the emerald scale across both themes.
|
|
6021
|
+
Pure paint axis; no geometry change. R248 fill 200ms
|
|
6022
|
+
transition already in the style list eases the shift.
|
|
6023
|
+
Closes the chroma axis on the hub-hover gesture stack:
|
|
6024
|
+
R177 ring radius lift geometry
|
|
6025
|
+
R209 digit scale 1.08 geometry
|
|
6026
|
+
R425 digit fw 700 → 800 typography
|
|
6027
|
+
R370 halo opacity 0.7 → 0.8 paint
|
|
6028
|
+
R386 highlight opacity paint
|
|
6029
|
+
R441 core fill brighten chroma ← this round
|
|
6030
|
+
data-topo-hub-core-hovered + -fill attrs exposed
|
|
6031
|
+
for tests. */
|
|
6032
|
+
const isCoreHovered = !reducedMotion && hoveredHub;
|
|
6033
|
+
const coreFill = isLight
|
|
6034
|
+
? (isCoreHovered ? '#10b981' : '#059669')
|
|
6035
|
+
: (isCoreHovered ? '#34d399' : '#10b981');
|
|
6036
|
+
return (
|
|
6037
|
+
<circle
|
|
6038
|
+
cx={cx} cy={cy} r="10"
|
|
6039
|
+
fill={coreFill}
|
|
6040
|
+
data-topo-hub-core
|
|
6041
|
+
data-topo-hub-core-hovered={isCoreHovered ? 'true' : 'false'}
|
|
6042
|
+
data-topo-hub-core-fill={coreFill}
|
|
6043
|
+
style={{ transition: 'fill 200ms ease-out' }}
|
|
6044
|
+
/>
|
|
6045
|
+
);
|
|
6046
|
+
})()}
|
|
4868
6047
|
{/* R130 / Loop: when workingCount > 0, the decorative inner
|
|
4869
6048
|
highlight gets replaced with the workingCount digit. The
|
|
4870
6049
|
R84 busyness breath already encodes the same metric
|
|
@@ -4905,11 +6084,40 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4905
6084
|
textAnchor="middle"
|
|
4906
6085
|
dy="0.36em"
|
|
4907
6086
|
fill={isLight ? '#d1fae5' : '#ecfdf5'}
|
|
4908
|
-
fontSize
|
|
6087
|
+
/* Round 360 / Loop: hub working-count digit fontSize 11
|
|
6088
|
+
→ 12. The hub is the canvas's focal point — its digit
|
|
6089
|
+
is the most-read scalar on the whole topology. R130
|
|
6090
|
+
sized it at 11 (well inside the r=10 / 20-px core);
|
|
6091
|
+
R360 nudges it to 12 (~13 px wide × 12 px tall, still
|
|
6092
|
+
well inside the 20-px diameter) for ~9 % more presence.
|
|
6093
|
+
Sibling visual-weight bump family:
|
|
6094
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
6095
|
+
R295 legend swatch base radius 5.5 → 6
|
|
6096
|
+
R359 recent-row pip radius 1.6 → 1.8
|
|
6097
|
+
R360 hub digit fontSize 11 → 12 (this round)
|
|
6098
|
+
The R209 scale-1.08-on-hub-hover, R225 tabular-nums,
|
|
6099
|
+
R253 fill transition, R213 always-mount opacity gate
|
|
6100
|
+
all preserved. data-topo-hub-working-count-font-size
|
|
6101
|
+
attr exposes the value for tests. */
|
|
6102
|
+
fontSize="12"
|
|
4909
6103
|
fontFamily="monospace"
|
|
4910
|
-
fontWeight
|
|
6104
|
+
/* R425 — hub digit fontWeight 700 → 800 on hoveredHub.
|
|
6105
|
+
Closes the "data tightens under attention" pattern
|
|
6106
|
+
across three focal scopes: chip-digit (R416, chip
|
|
6107
|
+
scope) → panel-digit (R424, panel-header scope) →
|
|
6108
|
+
hub-digit (R425, hub focal scope). The hub digit is
|
|
6109
|
+
the most-read scalar on the topology; adding a weight
|
|
6110
|
+
axis on hover stacks with the R209 scale-1.08 + R177
|
|
6111
|
+
ring grow + R370 halo opacity + R386 highlight
|
|
6112
|
+
opacity hub-hover gestures, giving the focal point
|
|
6113
|
+
a typographic axis alongside its scale/structure cues.
|
|
6114
|
+
R360 fontSize=12 + R225 tabular-nums + R209 scale +
|
|
6115
|
+
R253 fill transition all preserved. Transition list
|
|
6116
|
+
extends to include font-weight 200ms ease-out. */
|
|
6117
|
+
fontWeight={hoveredHub ? '800' : '700'}
|
|
4911
6118
|
opacity={workingCount > 0 ? 1 : 0}
|
|
4912
6119
|
data-topo-hub-working-count={workingCount}
|
|
6120
|
+
data-topo-hub-working-count-font-size="12"
|
|
4913
6121
|
data-topo-hub-working-count-hovered={hoveredHub ? 'true' : 'false'}
|
|
4914
6122
|
data-topo-hub-working-count-visible={workingCount > 0 ? 'true' : 'false'}
|
|
4915
6123
|
// Round 209 / Loop: hub workingCount digit scales 1.0 →
|
|
@@ -4943,19 +6151,63 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4943
6151
|
transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',
|
|
4944
6152
|
transformBox: 'fill-box',
|
|
4945
6153
|
transformOrigin: 'center',
|
|
4946
|
-
|
|
6154
|
+
/* R425: font-weight 200ms appended so the hover fw
|
|
6155
|
+
bump 700 → 800 eases under the same cadence as
|
|
6156
|
+
R209 scale + R253 fill + R213 opacity. */
|
|
6157
|
+
transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out, font-weight 200ms ease-out',
|
|
4947
6158
|
fontVariantNumeric: 'tabular-nums',
|
|
4948
6159
|
}}
|
|
4949
6160
|
>
|
|
4950
6161
|
{workingCount}
|
|
4951
6162
|
</text>
|
|
4952
6163
|
{/* decorative highlight (visible when workingCount === 0) */}
|
|
6164
|
+
{/* Round 365 / Loop: hub-center 'lit-lamp' decorative highlight
|
|
6165
|
+
circle r 5 → 5.5. Sibling visual-weight bump family —
|
|
6166
|
+
each round lifts one canvas anchor's geometric presence
|
|
6167
|
+
without disturbing its bbox envelope:
|
|
6168
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
6169
|
+
R295 legend swatch base radius 5.5 → 6
|
|
6170
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
6171
|
+
R360 hub digit fontSize 11 → 12
|
|
6172
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
6173
|
+
R365 hub-highlight base radius 5 → 5.5 (this round)
|
|
6174
|
+
The highlight only renders when workingCount === 0
|
|
6175
|
+
(decorative 'lamp lit but idle' state per R130 + R213
|
|
6176
|
+
always-mount opacity-gate). At idle, the 0.5-px radius
|
|
6177
|
+
bump (21 % area, π*5.5² / π*5² = 1.21) lifts the lamp's
|
|
6178
|
+
presence — still well inside the r=10 hub-core (R130).
|
|
6179
|
+
opacity=0 when working preserved so the hub-digit's R130
|
|
6180
|
+
takeover stays seamless. R213 always-mount opacity-gate
|
|
6181
|
+
+ 300ms opacity transition + pointerEvents:none all
|
|
6182
|
+
preserved. data-topo-hub-highlight-radius attr exposes
|
|
6183
|
+
the value for tests. */}
|
|
6184
|
+
{/* Round 386 / Loop: hub-highlight idle opacity 0.9 → 0.95.
|
|
6185
|
+
When workingCount===0 the highlight paints as the visible
|
|
6186
|
+
idle "lamp lit but no work" core (R130 takeover gate).
|
|
6187
|
+
Pre-R386 idle opacity was 0.9 — a ~6 % fade against full
|
|
6188
|
+
paint that read as slightly-dimmed-ghost on the focal
|
|
6189
|
+
point. R386 lifts to 0.95 (idle alpha gap halved 0.10
|
|
6190
|
+
→ 0.05) so the canvas anchor reads more confidently
|
|
6191
|
+
as a present-but-idle state rather than a faded ghost.
|
|
6192
|
+
Theme-consistency / canvas-presence polish family (4th
|
|
6193
|
+
anchor):
|
|
6194
|
+
R370 hub hover-ring opacity 0.7 → 0.8 cyber
|
|
6195
|
+
R371 edge-badge rest opacity 0.82 → 0.85 cyber
|
|
6196
|
+
R372 minimap offline-dot opacity 0.5 → 0.6
|
|
6197
|
+
R386 hub-highlight idle opacity 0.9 → 0.95 (this round)
|
|
6198
|
+
opacity=0 when working preserved so the hub-digit's
|
|
6199
|
+
R130 takeover stays seamless. 300ms opacity transition
|
|
6200
|
+
+ R213 always-mount opacity-gate + pointerEvents:none
|
|
6201
|
+
+ R365 r=5.5 all preserved. data-topo-hub-highlight-
|
|
6202
|
+
opacity attr exposes the resolved value for tests. */}
|
|
4953
6203
|
<circle
|
|
4954
|
-
cx={cx} cy={cy} r="5"
|
|
6204
|
+
cx={cx} cy={cy} r="5.5"
|
|
4955
6205
|
fill="#d1fae5"
|
|
4956
|
-
opacity={workingCount > 0 ? 0 : 0.
|
|
6206
|
+
opacity={workingCount > 0 ? 0 : 0.95}
|
|
4957
6207
|
data-topo-hub-highlight
|
|
4958
6208
|
data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}
|
|
6209
|
+
data-topo-hub-highlight-radius="5.5"
|
|
6210
|
+
data-topo-hub-highlight-opacity={workingCount > 0 ? 0 : 0.95}
|
|
4959
6211
|
style={{
|
|
4960
6212
|
pointerEvents: 'none',
|
|
4961
6213
|
transition: 'opacity 300ms ease-out',
|
|
@@ -4986,15 +6238,46 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4986
6238
|
R142 group boxes, R143/R144 rows, R164 edge badges,
|
|
4987
6239
|
R177 hub ring). prefers-reduced-motion respected via
|
|
4988
6240
|
R29 globals.css blanket. */}
|
|
6241
|
+
{/* Round 385 / Loop: hub hover-ring strokeWidth 1.5 → 1.75.
|
|
6242
|
+
Sibling visual-weight bump (11th anchor) to R367 edge-
|
|
6243
|
+
badge rest stroke 1 → 1.25. The ring is only visible
|
|
6244
|
+
during hub hover (opacity=0 rest, R177 + R370 control
|
|
6245
|
+
the hover-state alpha) so the change manifests purely
|
|
6246
|
+
as a thicker hover-state ring on the canvas focal
|
|
6247
|
+
point. R177 r 14 → 17 grow + R370 opacity 0 → 0.8
|
|
6248
|
+
already lift the hover cue; R385 adds stroke weight
|
|
6249
|
+
as the third lift axis. Stays clear of R51 overlap-
|
|
6250
|
+
test sentinel value 3 (1.75 is non-sentinel); the
|
|
6251
|
+
R51 selector is gated to g[data-node] ancestors so
|
|
6252
|
+
this hub-internal circle is invisible to the probe
|
|
6253
|
+
regardless. R253 stroke transition + pointerEvents:
|
|
6254
|
+
none preserved. data-topo-hub-hover-ring-stroke-width
|
|
6255
|
+
attr exposes the value for tests. */}
|
|
4989
6256
|
<circle
|
|
4990
6257
|
cx={cx} cy={cy}
|
|
4991
6258
|
r={hoveredHub ? 17 : 14}
|
|
4992
6259
|
fill="none"
|
|
4993
6260
|
stroke={isLight ? '#059669' : '#10b981'}
|
|
4994
|
-
strokeWidth="1.
|
|
4995
|
-
|
|
6261
|
+
strokeWidth="1.75"
|
|
6262
|
+
/* Round 370 / Loop: hub hover-ring cyber opacity 0.7 →
|
|
6263
|
+
0.8. R177 designed the hub hover-ring at opacity-0 →
|
|
6264
|
+
0.85 (light) / 0 → 0.7 (cyber). The 15 % gap between
|
|
6265
|
+
the two themes meant cyber-theme operators got a
|
|
6266
|
+
noticeably softer hover cue than light-theme users
|
|
6267
|
+
against backgrounds that should equalise (dark bg
|
|
6268
|
+
needs more luminance to read as 'on'). R370 bumps
|
|
6269
|
+
cyber 0.7 → 0.8, narrowing the theme gap to 5 % —
|
|
6270
|
+
sibling theme-consistency polish to R251 edge badge
|
|
6271
|
+
fill/opacity (cyber 0.82 / light 0.95) and R246/R247
|
|
6272
|
+
panel transition families. Light theme 0.85 stays
|
|
6273
|
+
as is (already in the legibility band). data-topo-
|
|
6274
|
+
hub-hover-ring-opacity attr exposes the value for
|
|
6275
|
+
tests. */
|
|
6276
|
+
opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}
|
|
4996
6277
|
data-topo-hub-hover-ring
|
|
4997
6278
|
data-topo-hub-hover-ring-radius={hoveredHub ? 17 : 14}
|
|
6279
|
+
data-topo-hub-hover-ring-stroke-width="1.75"
|
|
6280
|
+
data-topo-hub-hover-ring-opacity={hoveredHub ? (isLight ? 0.85 : 0.8) : 0}
|
|
4998
6281
|
/* Round 253 / Loop: hub hover ring also gets stroke
|
|
4999
6282
|
transition for theme toggle (cyber #10b981 ↔ light
|
|
5000
6283
|
#059669). The opacity + r transitions stay for hover
|
|
@@ -5423,14 +6706,51 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5423
6706
|
keyTimes="0;0.5;1"
|
|
5424
6707
|
keySplines="0.42 0 0.58 1;0.42 0 0.58 1"
|
|
5425
6708
|
/>
|
|
6709
|
+
{/* Round 409 / Loop: active-node pulse peak
|
|
6710
|
+
opacity lift — cyber 0.18 → 0.20 / light
|
|
6711
|
+
0.12 → 0.14. Theme-consistency / canvas-
|
|
6712
|
+
presence family 9th anchor. R243 family
|
|
6713
|
+
rhythm preserved.
|
|
6714
|
+
Round 413 / Loop: trough lift mirrors R409
|
|
6715
|
+
peak — cyber 0.04 → 0.05 / light 0.02 →
|
|
6716
|
+
0.03. Stale-state legibility lift family
|
|
6717
|
+
8th anchor — pairs with R404 (hub-halo
|
|
6718
|
+
cyber trough 0.08 → 0.10) and R405 (light
|
|
6719
|
+
trough 0.32 → 0.34). The per-node breath's
|
|
6720
|
+
low-point now reads slightly above the
|
|
6721
|
+
"nearly gone" zone while preserving the
|
|
6722
|
+
breath amplitude (cyber Δ 0.16 vs Δ pre-
|
|
6723
|
+
R409+R413 of 0.14; light Δ 0.11 vs 0.10).
|
|
6724
|
+
Both peak (R409) AND trough (R413) lift
|
|
6725
|
+
together so the active-pulse signal stays
|
|
6726
|
+
confidently present at both ends of its
|
|
6727
|
+
2.4s cycle.
|
|
6728
|
+
Stale-state legibility lift family (8):
|
|
6729
|
+
R317 subordinate-text gray-500→400
|
|
6730
|
+
R358 freshness floor 0.25→0.30
|
|
6731
|
+
R372 minimap offline-dot 0.5→0.6
|
|
6732
|
+
R404 hub-halo cyber trough 0.08→0.10
|
|
6733
|
+
R405 hub-halo light trough 0.32→0.34
|
|
6734
|
+
R406 edge freshness floor 0.35→0.40
|
|
6735
|
+
R407 node halo offline opacity (cyber+light)
|
|
6736
|
+
R413 active-node pulse trough (this round)
|
|
6737
|
+
cyber 0.04 → 0.05
|
|
6738
|
+
light 0.02 → 0.03
|
|
6739
|
+
R243 always-mount opacity-gate + R243
|
|
6740
|
+
ease-in-out keySplines + r animation
|
|
6741
|
+
(radius+8 ↔ radius+22) preserved.
|
|
6742
|
+
data-node-pulse-peak + new -pulse-trough
|
|
6743
|
+
attrs expose resolved per-theme values. */}
|
|
5426
6744
|
<animate
|
|
5427
6745
|
attributeName="opacity"
|
|
5428
|
-
values={isLight ? '0.
|
|
6746
|
+
values={isLight ? '0.14;0.03;0.14' : '0.20;0.05;0.20'}
|
|
5429
6747
|
dur="2.4s"
|
|
5430
6748
|
repeatCount="indefinite"
|
|
5431
6749
|
calcMode="spline"
|
|
5432
6750
|
keyTimes="0;0.5;1"
|
|
5433
6751
|
keySplines="0.42 0 0.58 1;0.42 0 0.58 1"
|
|
6752
|
+
data-node-pulse-peak={isLight ? '0.14' : '0.20'}
|
|
6753
|
+
data-node-pulse-trough={isLight ? '0.03' : '0.05'}
|
|
5434
6754
|
/>
|
|
5435
6755
|
</circle>
|
|
5436
6756
|
</g>
|
|
@@ -5475,12 +6795,107 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5475
6795
|
parent stays for status flips. data-node-halo-
|
|
5476
6796
|
breath-offset surfaces the chosen offset for
|
|
5477
6797
|
test introspection. */}
|
|
6798
|
+
{/* Round 407 / Loop: offline node halo opacity lift —
|
|
6799
|
+
cyber 0.25 → 0.30 and light 0.4 → 0.45. Pre-R407
|
|
6800
|
+
offline node halos faded to α=0.25 cyber (75 %
|
|
6801
|
+
dim) / α=0.4 light. On the dark canvas the 0.25
|
|
6802
|
+
halo read as "nearly gone" — exactly the
|
|
6803
|
+
legibility floor R404/R405 just lifted on the
|
|
6804
|
+
hub-halo and R372 lifted on minimap offline dots.
|
|
6805
|
+
R407 closes the same family at the per-node halo
|
|
6806
|
+
surface: +0.05 lift on both themes so offline
|
|
6807
|
+
anchors stay legibly present without crossing into
|
|
6808
|
+
"could be online" territory (online cyber 0.65 /
|
|
6809
|
+
light 0.85 unchanged — the 0.30/0.65 cyber ratio
|
|
6810
|
+
still gives 2.17× contrast for online/offline).
|
|
6811
|
+
Stale-state legibility lift family (7 anchors now):
|
|
6812
|
+
R317 subordinate-text gray-500 → gray-400
|
|
6813
|
+
R358 freshness floor 0.25 → 0.30
|
|
6814
|
+
R372 minimap offline-dot 0.5 → 0.6
|
|
6815
|
+
R404 hub-halo cyber trough 0.08 → 0.10
|
|
6816
|
+
R405 hub-halo light trough 0.32 → 0.34
|
|
6817
|
+
R406 edge freshness floor 0.35 → 0.40
|
|
6818
|
+
R407 node halo offline opacity (this round)
|
|
6819
|
+
cyber 0.25 → 0.30
|
|
6820
|
+
light 0.4 → 0.45
|
|
6821
|
+
R278 retired-breath gate + R12 status.halo color
|
|
6822
|
+
+ R226 phase stagger code-path preserved (the
|
|
6823
|
+
breath stays disabled per Vincent's R278 ask;
|
|
6824
|
+
only the BASE opacity floor shifts here). transi-
|
|
6825
|
+
tion list ('fill,opacity' 300ms ease-out) unchanged.
|
|
6826
|
+
data-node-halo-offline-opacity attr exposes the
|
|
6827
|
+
resolved value for tests. */}
|
|
6828
|
+
{(() => {
|
|
6829
|
+
/* Round 440 / Loop: node halo opacity hover lift —
|
|
6830
|
+
lifts toward full on the matched node. Pure paint
|
|
6831
|
+
axis: rest values unchanged for un-hovered halos,
|
|
6832
|
+
hover state lifts the matched halo's alpha by
|
|
6833
|
+
+0.15 on each tier:
|
|
6834
|
+
online cyber 0.65 → 0.80
|
|
6835
|
+
online light 0.85 → 1.00 (capped)
|
|
6836
|
+
offline cyber 0.30 → 0.45
|
|
6837
|
+
offline light 0.45 → 0.60
|
|
6838
|
+
Same paint-only mental model as R430 hub-spoke
|
|
6839
|
+
opacity lift + R429 label-card body opacity lift,
|
|
6840
|
+
now at the per-node halo scope. No geometry
|
|
6841
|
+
change so R51 sentinels stay safe and the overlap-
|
|
6842
|
+
test invariant is unchanged (test runs at rest).
|
|
6843
|
+
Closes a chroma/presence axis on the per-node
|
|
6844
|
+
hover signature alongside the 12-layer cue stack
|
|
6845
|
+
(R26/R217/R142/R427/R428/R429 card + R430/R435/
|
|
6846
|
+
R436/R437/R94 link + R438 ring). R407 offline
|
|
6847
|
+
halo opacity floor (cyber 0.30 / light 0.45) is
|
|
6848
|
+
the rest branch unchanged. Existing transition-
|
|
6849
|
+
[fill,opacity] duration-300 className handles
|
|
6850
|
+
the easing. data-node-halo-hovered exposes the
|
|
6851
|
+
gate; data-node-halo-resolved-opacity exposes
|
|
6852
|
+
the four-state resolved value for tests. */
|
|
6853
|
+
const isHaloHovered = !reducedMotion && hoveredAlias === session.alias;
|
|
6854
|
+
/* Round 456 / Loop: light-theme offline node halo
|
|
6855
|
+
rest opacity 0.45 → 0.50. Stale-state legibility
|
|
6856
|
+
lift family extension (10th anchor) at the per-
|
|
6857
|
+
node halo light-theme scope:
|
|
6858
|
+
R317 subordinate-text gray-500 → gray-400
|
|
6859
|
+
R358 freshness floor 0.25 → 0.30
|
|
6860
|
+
R372 minimap offline-dot 0.5 → 0.6
|
|
6861
|
+
R404 hub-halo cyber trough 0.08 → 0.10
|
|
6862
|
+
R405 hub-halo light trough 0.32 → 0.34
|
|
6863
|
+
R406 edge freshness floor 0.35 → 0.40
|
|
6864
|
+
R407 node halo offline opacity
|
|
6865
|
+
cyber 0.25 → 0.30
|
|
6866
|
+
light 0.4 → 0.45
|
|
6867
|
+
R419 hub-spoke idle 0.45 → 0.50
|
|
6868
|
+
R452 dense alias rest 0.9 → 0.95
|
|
6869
|
+
R456 node halo offline LIGHT 0.45 → 0.50 ← this round
|
|
6870
|
+
Pre-R456 light-theme offline halo at 0.45 sat at
|
|
6871
|
+
the upper end of "near-floor" but read as soft-
|
|
6872
|
+
focus on the lighter canvas; +0.05 (~11 % opacity
|
|
6873
|
+
gain) lifts it to 0.50 — the midpoint between
|
|
6874
|
+
R407 rest 0.45 and R440 hover 0.60 — closing the
|
|
6875
|
+
gap so offline halos read more confidently as
|
|
6876
|
+
present-but-stale anchors. Cyber theme stays at
|
|
6877
|
+
R407's 0.30 (cyber backdrop is dark; the cyber
|
|
6878
|
+
offline halo against #080814 contains a stronger
|
|
6879
|
+
contrast envelope than light, so doesn't need
|
|
6880
|
+
the same lift). R440 hover 0.45→0.60 light + R12
|
|
6881
|
+
status.halo color + R407 transition list all
|
|
6882
|
+
preserved. */
|
|
6883
|
+
const haloOpacity = (() => {
|
|
6884
|
+
if (isOnline) {
|
|
6885
|
+
return isLight ? (isHaloHovered ? 1 : 0.85) : (isHaloHovered ? 0.80 : 0.65);
|
|
6886
|
+
}
|
|
6887
|
+
return isLight ? (isHaloHovered ? 0.60 : 0.50) : (isHaloHovered ? 0.45 : 0.30);
|
|
6888
|
+
})();
|
|
6889
|
+
return (
|
|
5478
6890
|
<circle
|
|
5479
6891
|
cx={pos.x}
|
|
5480
6892
|
cy={pos.y}
|
|
5481
6893
|
r={radius + 8}
|
|
5482
6894
|
fill={status.halo}
|
|
5483
|
-
opacity={
|
|
6895
|
+
opacity={haloOpacity}
|
|
6896
|
+
data-node-halo-offline-opacity={isOnline ? undefined : (isLight ? 0.45 : 0.30)}
|
|
6897
|
+
data-node-halo-hovered={isHaloHovered ? 'true' : 'false'}
|
|
6898
|
+
data-node-halo-resolved-opacity={haloOpacity}
|
|
5484
6899
|
className="transition-[fill,opacity] duration-300 ease-out"
|
|
5485
6900
|
data-node-halo-breath={!reducedMotion && session.status === 'working' ? 'on' : 'off'}
|
|
5486
6901
|
data-node-halo-breath-offset={
|
|
@@ -5539,6 +6954,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5539
6954
|
/>
|
|
5540
6955
|
)}
|
|
5541
6956
|
</circle>
|
|
6957
|
+
);
|
|
6958
|
+
})()}
|
|
5542
6959
|
{/* Round 111 / Loop: edge-endpoint emphasis ring. R49
|
|
5543
6960
|
already keeps endpoint nodes at opacity 1 while
|
|
5544
6961
|
others dim when an edge is hovered, but the
|
|
@@ -5595,11 +7012,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5595
7012
|
ring stroke-width). data-edge-endpoint-ring-
|
|
5596
7013
|
stroke-width attr surfaces the chosen value for
|
|
5597
7014
|
test introspection. */
|
|
7015
|
+
/* Round 442 / Loop: endpoint emphasis ring radius
|
|
7016
|
+
hover lift — r=radius+7 → radius+8 on isEndpoint,
|
|
7017
|
+
closing a 3-axis hover-elevation parity at endpoint
|
|
7018
|
+
ring scope (r + sw + opacity):
|
|
7019
|
+
opacity R182 0 → 0.85/0.9
|
|
7020
|
+
sw R233 1.6 → 2.4
|
|
7021
|
+
r R442 +7 → +8 ← this round
|
|
7022
|
+
Mirrors the 3-axis trios already established at
|
|
7023
|
+
hub hover-ring (R177/R370/R385) and edge badge
|
|
7024
|
+
(R164/R394/R395). Pre-R442 the endpoint ring
|
|
7025
|
+
faded in + thickened on edge-hover but its radius
|
|
7026
|
+
stayed locked at radius+7 — only the paint/weight
|
|
7027
|
+
axes lifted while the GEOMETRY stayed unchanged.
|
|
7028
|
+
+1px (~radius+7 to radius+8) gives a subtle outward
|
|
7029
|
+
pulse on hover without crowding the status ring
|
|
7030
|
+
(which sits at radius from R438 sw3.5 hover) or
|
|
7031
|
+
the halo (radius+8 from R440 opacity hover — the
|
|
7032
|
+
endpoint ring sits at the SAME radius as the halo
|
|
7033
|
+
but with stroke=cyan vs fill=status.halo so they
|
|
7034
|
+
don't visually collide). The transition list
|
|
7035
|
+
extends to include 'r 180ms ease-out' so the new
|
|
7036
|
+
axis eases under the same R233 cadence. SVG `r`
|
|
7037
|
+
on a <circle> uses CSS-property syntax for inter-
|
|
7038
|
+
polation (same idiom R197/R198 used on the
|
|
7039
|
+
legend swatch). data-edge-endpoint-ring-radius
|
|
7040
|
+
attr exposes the resolved value for tests. */
|
|
7041
|
+
const endpointR = isEndpoint ? radius + 8 : radius + 7;
|
|
5598
7042
|
return (
|
|
5599
7043
|
<circle
|
|
5600
7044
|
cx={pos.x}
|
|
5601
7045
|
cy={pos.y}
|
|
5602
|
-
r={radius + 7}
|
|
5603
7046
|
fill="none"
|
|
5604
7047
|
stroke={pal.flowEdge}
|
|
5605
7048
|
strokeWidth={isEndpoint ? 2.4 : 1.6}
|
|
@@ -5607,7 +7050,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5607
7050
|
data-edge-endpoint-ring
|
|
5608
7051
|
data-edge-endpoint-active={isEndpoint ? 'true' : 'false'}
|
|
5609
7052
|
data-edge-endpoint-ring-stroke-width={isEndpoint ? 2.4 : 1.6}
|
|
5610
|
-
|
|
7053
|
+
data-edge-endpoint-ring-radius={endpointR}
|
|
7054
|
+
style={{
|
|
7055
|
+
pointerEvents: 'none',
|
|
7056
|
+
r: `${endpointR}px`,
|
|
7057
|
+
transition: 'opacity 180ms ease-out, stroke-width 180ms ease-out, r 180ms ease-out',
|
|
7058
|
+
} as React.CSSProperties}
|
|
5611
7059
|
/>
|
|
5612
7060
|
);
|
|
5613
7061
|
})()}
|
|
@@ -5632,20 +7080,62 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5632
7080
|
stroke-width="3"/"1.5" still works against the
|
|
5633
7081
|
DOM attribute value (React-rendered, not
|
|
5634
7082
|
interpolated). */}
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
7083
|
+
{(() => {
|
|
7084
|
+
/* Round 438 / Loop: status ring strokeWidth hover lift —
|
|
7085
|
+
when hoveredAlias matches, the node's status ring
|
|
7086
|
+
thickens by +0.5: online 3 → 3.5, offline 1.5 → 2.
|
|
7087
|
+
Same absolute delta as R435 hub-spoke (idle 1→1.25
|
|
7088
|
+
used Δ +0.25 because rest base was thinner; status
|
|
7089
|
+
ring's heavier rest values 1.5/3 need a bigger
|
|
7090
|
+
+0.5 to register as visible thickening).
|
|
7091
|
+
Status-ring axis joins the node-hover cue stack
|
|
7092
|
+
(now 9 layers including link surfaces):
|
|
7093
|
+
R26 group translateY -2px per-node geometry
|
|
7094
|
+
R217 stroke tint legendAccent per-node card
|
|
7095
|
+
R142 drop-shadow boost per-node card
|
|
7096
|
+
R427 alias letter-spacing per-node text
|
|
7097
|
+
R428 sub-text letter-spacing per-node text
|
|
7098
|
+
R429 body opacity 0.94 → 1.0 per-node card
|
|
7099
|
+
R430 hub-spoke α+ link to hub (paint)
|
|
7100
|
+
R435 hub-spoke sw+ link to hub (geo)
|
|
7101
|
+
R94 edge α 1.7× inter-node link (paint)
|
|
7102
|
+
R436 edge sw 1.15× inter-node link (geo)
|
|
7103
|
+
R437 flow-rail sw 1 → 1.5 edge paint-layer
|
|
7104
|
+
R438 status-ring sw +0.5 ring geometry ← this round
|
|
7105
|
+
R51 sentinel safety: rest values 3 / 1.5 unchanged
|
|
7106
|
+
so the overlap-test selector `circle[stroke-width=
|
|
7107
|
+
"3"]` / `circle[stroke-width="1.5"]` inside
|
|
7108
|
+
g[data-node] still matches at rest. Hover values
|
|
7109
|
+
3.5 / 2 are not in the reserved {1.5, 3} set so
|
|
7110
|
+
the selector wouldn't match them anyway; but the
|
|
7111
|
+
test runs WITHOUT hover so this never matters
|
|
7112
|
+
in practice. R167 stroke-width 300ms transition
|
|
7113
|
+
already in the style list eases the lift for
|
|
7114
|
+
free. data-node-status-ring-hovered exposes the
|
|
7115
|
+
gate for tests. */
|
|
7116
|
+
const isRingHovered = !reducedMotion && hoveredAlias === session.alias;
|
|
7117
|
+
const ringStrokeWidth = isOnline
|
|
7118
|
+
? (isRingHovered ? 3.5 : 3)
|
|
7119
|
+
: (isRingHovered ? 2 : 1.5);
|
|
7120
|
+
return (
|
|
7121
|
+
<circle
|
|
7122
|
+
cx={pos.x}
|
|
7123
|
+
cy={pos.y}
|
|
7124
|
+
r={radius}
|
|
7125
|
+
fill={isOnline ? pal.nodeFill.online : pal.nodeFill.offline}
|
|
7126
|
+
stroke={status.primary}
|
|
7127
|
+
strokeWidth={ringStrokeWidth}
|
|
7128
|
+
strokeDasharray={isOnline ? 'none' : '5 5'}
|
|
7129
|
+
filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}
|
|
7130
|
+
data-node-status-ring={status.label}
|
|
7131
|
+
data-node-status-ring-hovered={isRingHovered ? 'true' : 'false'}
|
|
7132
|
+
data-node-status-ring-stroke-width={ringStrokeWidth}
|
|
7133
|
+
style={{
|
|
7134
|
+
transition: 'fill 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out',
|
|
7135
|
+
}}
|
|
7136
|
+
/>
|
|
7137
|
+
);
|
|
7138
|
+
})()}
|
|
5649
7139
|
{/* v0.10.0 Hero 1+2 / §3.F server-health node-ring tint.
|
|
5650
7140
|
When the host server this agent runs on is in the
|
|
5651
7141
|
`red` tier (CPU/Mem/Disk worst-of ≥ 85% per
|
|
@@ -5825,8 +7315,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5825
7315
|
transition: 'r 150ms ease-out, stroke-width 150ms ease-out',
|
|
5826
7316
|
} as React.CSSProperties}
|
|
5827
7317
|
/>
|
|
7318
|
+
{/* Round 443 / Loop: runtime badge inner-icon
|
|
7319
|
+
strokeWidth lift on node hover — 2.4 → 2.8 on
|
|
7320
|
+
isNodeActive. Pre-R443 the outer badge ring
|
|
7321
|
+
lifted (R208 r + sw both grow on hover) but
|
|
7322
|
+
the inner icon path stayed locked at sw=2.4.
|
|
7323
|
+
The two layers of the runtime badge were
|
|
7324
|
+
out of phase: ring thickened, icon stayed
|
|
7325
|
+
thin. R443 closes the 2-axis hover signature
|
|
7326
|
+
on the badge so both ring and icon lift
|
|
7327
|
+
together. +0.4 absolute delta matches the
|
|
7328
|
+
R208 ring's +0.5 sw delta (badge ring 1.5 →
|
|
7329
|
+
2.0 absolute), proportional to the icon's
|
|
7330
|
+
heavier base of 2.4. Pure paint axis;
|
|
7331
|
+
strokeLinecap='round' + strokeLinejoin='round'
|
|
7332
|
+
preserved. transition list extends to include
|
|
7333
|
+
'stroke-width 150ms ease-out' matching R208
|
|
7334
|
+
outer-ring cadence. data-runtime-badge-icon
|
|
7335
|
+
+ -active attrs exposed for tests. */}
|
|
5828
7336
|
<g transform={`translate(${bx - icon / 2} ${by - icon / 2}) scale(${icon / 24})`}>
|
|
5829
|
-
<path
|
|
7337
|
+
<path
|
|
7338
|
+
d={rt.iconPath}
|
|
7339
|
+
fill="none"
|
|
7340
|
+
stroke={rt.color}
|
|
7341
|
+
strokeWidth={isNodeActive ? '2.8' : '2.4'}
|
|
7342
|
+
strokeLinecap="round"
|
|
7343
|
+
strokeLinejoin="round"
|
|
7344
|
+
data-runtime-badge-icon={session.alias}
|
|
7345
|
+
data-runtime-badge-icon-active={isNodeActive ? 'true' : 'false'}
|
|
7346
|
+
data-runtime-badge-icon-stroke-width={isNodeActive ? '2.8' : '2.4'}
|
|
7347
|
+
style={{ transition: 'stroke-width 150ms ease-out' }}
|
|
7348
|
+
/>
|
|
5830
7349
|
</g>
|
|
5831
7350
|
</g>
|
|
5832
7351
|
);
|
|
@@ -5969,14 +7488,69 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5969
7488
|
220ms cadence the existing filter/stroke
|
|
5970
7489
|
pair uses — coordinated 4-property easing
|
|
5971
7490
|
across the card. */}
|
|
7491
|
+
{/* Round 411 / Loop: node label card rx 6 → 8.
|
|
7492
|
+
Pre-R411 the per-node label card painted at
|
|
7493
|
+
rx=6, sitting one tier BELOW the R332/R375/
|
|
7494
|
+
R376 compact-chrome tier (rx=8). Inside the
|
|
7495
|
+
corner-radius cascade family the cards used
|
|
7496
|
+
to be the only "smaller" tier — but the
|
|
7497
|
+
label card is a content-bearing surface
|
|
7498
|
+
(alias + sub text + ring), not a sub-
|
|
7499
|
+
element decoration. R411 lifts rx=6 → 8
|
|
7500
|
+
to align with the compact-chrome / segmented-
|
|
7501
|
+
control tier so all "compact card" surfaces
|
|
7502
|
+
read with the same corner radius.
|
|
7503
|
+
Corner-radius cascade (8 anchors now):
|
|
7504
|
+
R330 canvas rx 12 (root)
|
|
7505
|
+
R331 panels rx 10 (recent-signal, legend)
|
|
7506
|
+
R332 minimap container rx 8 (compact chrome)
|
|
7507
|
+
R375 Layout-toggle rx 8 (segmented control)
|
|
7508
|
+
R376 nodeSize/zoom rx 8 (segmented control)
|
|
7509
|
+
R390 hover-detail rx 10 (panel)
|
|
7510
|
+
R393 minimap viewport rx 2 (sub-element)
|
|
7511
|
+
R411 node label card rx 6 → 8 (compact card, this round)
|
|
7512
|
+
Pure paint — rx grows the corner curve
|
|
7513
|
+
inward without changing the card's outer
|
|
7514
|
+
cardW × cardH bbox (cardW=92/cardH=22 for
|
|
7515
|
+
standard nodes per R23 / R187 sizing). R217
|
|
7516
|
+
hover-stroke cyan tint + R142 drop-shadow
|
|
7517
|
+
+ R246 fill+opacity 220ms transition list
|
|
7518
|
+
+ R211 alias/sub text-fill ease all
|
|
7519
|
+
preserved. data-node-label-card-rx attr
|
|
7520
|
+
exposes the value for tests. */}
|
|
7521
|
+
{/* Round 429 / Loop: node label-card body opacity
|
|
7522
|
+
0.94 → 1.0 on hover (cyber theme). Sibling
|
|
7523
|
+
treatment to R348 panel rect opacity lift —
|
|
7524
|
+
0.92 → 0.97 cyber / 0.97 → 1.0 light at the
|
|
7525
|
+
panel scope. Pre-R429 the cyber theme card
|
|
7526
|
+
sat at 0.94 always; on hover R217 tinted the
|
|
7527
|
+
stroke + R142 grew the drop-shadow + R26
|
|
7528
|
+
lifted the group + R427/R428 spaced the text
|
|
7529
|
+
but the rect itself never solidified —
|
|
7530
|
+
the card glowed brighter through the
|
|
7531
|
+
shadow but the body alpha gap (6 pct) stayed
|
|
7532
|
+
fixed. R429 lifts the body to full alpha on
|
|
7533
|
+
hover so the card reads as a confidently
|
|
7534
|
+
present surface under the cursor (matching
|
|
7535
|
+
the panel-pair pattern). Light theme stays
|
|
7536
|
+
at 1.0 in both states (already maxed). R246
|
|
7537
|
+
transition list already covers opacity 220ms
|
|
7538
|
+
so the lift eases for free. R217 stroke tint
|
|
7539
|
+
+ R142 drop-shadow + R211 fill ease all
|
|
7540
|
+
preserved (additive opacity branch only). */}
|
|
5972
7541
|
<rect
|
|
5973
|
-
x={-cardW / 2} y={cardTopY} width={cardW} height={cardH} rx="
|
|
7542
|
+
x={-cardW / 2} y={cardTopY} width={cardW} height={cardH} rx="8"
|
|
5974
7543
|
fill={pal.labelBox.fill}
|
|
5975
7544
|
stroke={!reducedMotion && hoveredAlias === session.alias
|
|
5976
7545
|
? pal.legendAccent
|
|
5977
7546
|
: pal.labelBox.stroke}
|
|
5978
|
-
opacity={
|
|
7547
|
+
opacity={
|
|
7548
|
+
!reducedMotion && hoveredAlias === session.alias
|
|
7549
|
+
? 1
|
|
7550
|
+
: (isLight ? 1 : 0.94)
|
|
7551
|
+
}
|
|
5979
7552
|
data-node-label-card={session.alias}
|
|
7553
|
+
data-node-label-card-rx="8"
|
|
5980
7554
|
data-node-label-card-elevation={
|
|
5981
7555
|
!reducedMotion && hoveredAlias === session.alias ? 'hover' : 'idle'
|
|
5982
7556
|
}
|
|
@@ -6020,25 +7594,94 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6020
7594
|
transition list extends 'letter-spacing
|
|
6021
7595
|
200ms ease-out' so it eases alongside the
|
|
6022
7596
|
existing 300ms fill transition. */}
|
|
7597
|
+
{/* Round 427 / Loop: extend the node alias label
|
|
7598
|
+
letter-spacing family to a 3-tier scale —
|
|
7599
|
+
rest 0px → hover 0.3px → chat-target 0.5px.
|
|
7600
|
+
Pre-R427 the alias text shifted only when
|
|
7601
|
+
chat was actively pinned (R305); pure node-
|
|
7602
|
+
hover left the text dead-typographic while
|
|
7603
|
+
the surrounding card lifted (R26 translateY
|
|
7604
|
+
+ R242 stroke + filter cues). R427 adds the
|
|
7605
|
+
missing typographic axis to the hover gesture
|
|
7606
|
+
so the alias text rises with the card.
|
|
7607
|
+
The chat-target tier still wins (0.5 > 0.3)
|
|
7608
|
+
so the pin signature stays at the top of the
|
|
7609
|
+
scale — hover is the mid tier between rest
|
|
7610
|
+
and chat-target.
|
|
7611
|
+
Hover-letter-spacing family extension:
|
|
7612
|
+
R344 chip count digit
|
|
7613
|
+
R345 panel title (R423 sibling)
|
|
7614
|
+
R347 active-links chip
|
|
7615
|
+
R351 vendor chip
|
|
7616
|
+
R420 zoom-level chip
|
|
7617
|
+
R427 node alias text (this round)
|
|
7618
|
+
R211 fill 300ms + R305 letter-spacing 200ms
|
|
7619
|
+
transition list preserved; only the
|
|
7620
|
+
conditional gets a middle case. */}
|
|
6023
7621
|
<text
|
|
6024
7622
|
x="0" y="1" textAnchor="middle"
|
|
6025
7623
|
fill={status.text}
|
|
6026
7624
|
fontSize={aliasFs} fontFamily="monospace" fontWeight="700"
|
|
6027
7625
|
data-node-alias-text={session.alias}
|
|
6028
7626
|
data-node-alias-chat-target={chatAlias === session.alias ? 'true' : 'false'}
|
|
7627
|
+
data-node-alias-hovered={hoveredAlias === session.alias ? 'true' : 'false'}
|
|
6029
7628
|
style={{
|
|
6030
7629
|
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',
|
|
6031
|
-
letterSpacing:
|
|
7630
|
+
letterSpacing:
|
|
7631
|
+
chatAlias === session.alias ? '0.5px' :
|
|
7632
|
+
hoveredAlias === session.alias ? '0.3px' : '0px',
|
|
6032
7633
|
}}
|
|
6033
7634
|
>
|
|
6034
7635
|
{truncate(session.alias, fullMax)}
|
|
6035
7636
|
</text>
|
|
7637
|
+
{/* Round 428 / Loop: node sub-text (status label
|
|
7638
|
+
line beneath the alias) adopts hover letter-
|
|
7639
|
+
spacing tween 0 → 0.2px on hoveredAlias.
|
|
7640
|
+
Sibling treatment to R427 alias-text hover
|
|
7641
|
+
tween (0 → 0.3) — the alias is the primary
|
|
7642
|
+
identity (top-tier kerning 0.3), the sub-text
|
|
7643
|
+
is the secondary status line (one tier lower
|
|
7644
|
+
at 0.2). Now both lines of the label card
|
|
7645
|
+
telegraph hover typographically as one unit,
|
|
7646
|
+
matching the R26 card lift + R242 stroke
|
|
7647
|
+
tint + R975 filter cues. Subtler delta on
|
|
7648
|
+
the sub-text (0.2 vs alias 0.3) preserves
|
|
7649
|
+
the alias > status visual hierarchy at the
|
|
7650
|
+
hover scope. R211 fill 300ms transition
|
|
7651
|
+
preserved (additive letter-spacing branch
|
|
7652
|
+
+ appended 'letter-spacing 200ms ease-out'). */}
|
|
7653
|
+
{/* Round 448 / Loop: node sub-text fontWeight
|
|
7654
|
+
400 → 500 (font-medium). Sibling to R363
|
|
7655
|
+
(recent-row text fw 400→500) + R364 (legend-
|
|
7656
|
+
row label fw 400→500) — same "small mono
|
|
7657
|
+
text at fontSize=9-11 needs 500-tier weight
|
|
7658
|
+
for legibility" pattern, now applied to the
|
|
7659
|
+
per-node sub-text line. At fontSize=8-9
|
|
7660
|
+
monospace against the label-card chrome
|
|
7661
|
+
(pal.labelBox.fill cyber #020617 / light
|
|
7662
|
+
#ffffff), the default fw=400 sits at the
|
|
7663
|
+
legibility floor; fw=500 (font-medium) lifts
|
|
7664
|
+
it into a clearly readable band without
|
|
7665
|
+
changing geometry. R211 fill 300ms +
|
|
7666
|
+
R428 letter-spacing 0→0.2 hover + R427
|
|
7667
|
+
alias-text + R429 body opacity all preserved.
|
|
7668
|
+
Pure typography lift; no layout shift; the
|
|
7669
|
+
alias-text fw=700 (R427) still wins so the
|
|
7670
|
+
alias > status hierarchy holds at the type
|
|
7671
|
+
level. data-node-sub-text-font-weight attr
|
|
7672
|
+
exposes the value for tests. */}
|
|
6036
7673
|
<text
|
|
6037
7674
|
x="0" y={subY} textAnchor="middle"
|
|
6038
7675
|
fill={status.primary}
|
|
6039
7676
|
fontSize={subFs} fontFamily="monospace"
|
|
7677
|
+
fontWeight="500"
|
|
6040
7678
|
data-node-sub-text={session.alias}
|
|
6041
|
-
|
|
7679
|
+
data-node-sub-text-hovered={hoveredAlias === session.alias ? 'true' : 'false'}
|
|
7680
|
+
data-node-sub-text-font-weight="500"
|
|
7681
|
+
style={{
|
|
7682
|
+
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',
|
|
7683
|
+
letterSpacing: hoveredAlias === session.alias ? '0.2px' : '0px',
|
|
7684
|
+
}}
|
|
6042
7685
|
>
|
|
6043
7686
|
{status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}
|
|
6044
7687
|
</text>
|
|
@@ -6061,6 +7704,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6061
7704
|
// CSS pseudo-class; only the transition-property
|
|
6062
7705
|
// moves to inline. Big fleets benefit most — this is
|
|
6063
7706
|
// the path users see when their dashboard is busiest.
|
|
7707
|
+
/* Round 452 / Loop: dense plain-text alias rest
|
|
7708
|
+
opacity 0.9 → 0.95. Closes the alpha gap on the
|
|
7709
|
+
dense fleet's per-node label, sibling to R449
|
|
7710
|
+
legend-count-active 0.95→1.0 and R450 minimap
|
|
7711
|
+
viewport rest 0.9→0.95 — same "close the
|
|
7712
|
+
active-presence alpha gap" idiom applied here
|
|
7713
|
+
to the dense-mode alias text at fontSize=9-10
|
|
7714
|
+
monospace. Pre-R452 dense aliases at α=0.9 sat
|
|
7715
|
+
just below full alpha; for un-hovered nodes in
|
|
7716
|
+
a busy >16-node fleet this is the only label
|
|
7717
|
+
readable, so the 10% alpha gap added a subtle
|
|
7718
|
+
"soft-focused chrome" feel where the labels
|
|
7719
|
+
should read as definitive. +0.05 lift makes
|
|
7720
|
+
them confidently present without erasing the
|
|
7721
|
+
status.text + R110 stroke halo + paintOrder
|
|
7722
|
+
layering. R26 group-hover translate + R212
|
|
7723
|
+
fill 300ms transition + R110 stroke=container-
|
|
7724
|
+
Bg halo all preserved. data-node-dense-alias-
|
|
7725
|
+
text-opacity attr exposes the resolved value
|
|
7726
|
+
for tests. */
|
|
6064
7727
|
<text
|
|
6065
7728
|
x={pos.x}
|
|
6066
7729
|
y={pos.y + radius + denseDrop}
|
|
@@ -6069,9 +7732,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6069
7732
|
fontSize={denseFs}
|
|
6070
7733
|
fontFamily="monospace"
|
|
6071
7734
|
fontWeight="700"
|
|
6072
|
-
opacity={0.
|
|
7735
|
+
opacity={0.95}
|
|
6073
7736
|
className="group-hover:-translate-y-[1.5px]"
|
|
6074
7737
|
data-node-dense-alias-text={session.alias}
|
|
7738
|
+
data-node-dense-alias-text-opacity="0.95"
|
|
6075
7739
|
style={{
|
|
6076
7740
|
pointerEvents: 'none',
|
|
6077
7741
|
paintOrder: 'stroke',
|
|
@@ -6111,26 +7775,122 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6111
7775
|
const detailY = pos.y - detailH / 2;
|
|
6112
7776
|
return (
|
|
6113
7777
|
<g transform={`translate(${detailX}, ${detailY})`} data-topo-hover-detail={session.alias} style={{ pointerEvents: 'none' }}>
|
|
7778
|
+
{/* Round 387 / Loop: hover-detail panel cyber backdrop
|
|
7779
|
+
opacity 0.94 → 0.97. The hover-detail card is
|
|
7780
|
+
ALWAYS rendered in active-hover context (it IS
|
|
7781
|
+
the hover product), so it should carry the
|
|
7782
|
+
same backdrop weight as the R348 recent-signal /
|
|
7783
|
+
legend panel HOVER state (which lifts 0.92 →
|
|
7784
|
+
0.97 cyber). Pre-R387 the card sat at 0.94
|
|
7785
|
+
cyber, leaving a 0.03 alpha gap against the
|
|
7786
|
+
R348 panel-hover state — small but visible
|
|
7787
|
+
when the hover-detail floats next to a hovered
|
|
7788
|
+
recent-signal panel. R387 unifies them at 0.97
|
|
7789
|
+
so all active-hover panels paint with the same
|
|
7790
|
+
confident backdrop opacity in cyber. Light
|
|
7791
|
+
stays at 0.98 (already at the strong end —
|
|
7792
|
+
R348 light also stays at 0.97/0.98 max).
|
|
7793
|
+
Theme-consistency / canvas-presence polish
|
|
7794
|
+
family (5th anchor):
|
|
7795
|
+
R370 hub hover-ring opacity 0.7 → 0.8 cyber
|
|
7796
|
+
R371 edge-badge rest opacity 0.82 → 0.85 cyber
|
|
7797
|
+
R372 minimap offline-dot opacity 0.5 → 0.6
|
|
7798
|
+
R386 hub-highlight idle opacity 0.9 → 0.95
|
|
7799
|
+
R387 hover-detail panel opacity 0.94 → 0.97 cyber (this round)
|
|
7800
|
+
data-topo-hover-detail-opacity attr exposes
|
|
7801
|
+
the resolved value for tests. R348 drop-shadow
|
|
7802
|
+
+ rx=8 + stroke=pal.legendAccent + fill=pal.
|
|
7803
|
+
labelBox.fill all preserved. */}
|
|
7804
|
+
{/* Round 390 / Loop: hover-detail card rx 8 → 10.
|
|
7805
|
+
Corner-radius cascade family — the hover-detail
|
|
7806
|
+
card is a panel-tier surface (192×88 floating
|
|
7807
|
+
info card with drop-shadow + stroke), so its
|
|
7808
|
+
corner radius should match the R331 panel tier
|
|
7809
|
+
(rx=10) used by the recent-signal and legend
|
|
7810
|
+
panels. Pre-R390 it shared rx=8 with the R332
|
|
7811
|
+
minimap and R375/R376 segmented-control tier
|
|
7812
|
+
(Layout-toggle, nodeSize, zoom wrappers),
|
|
7813
|
+
which is the "compact chrome control" tier —
|
|
7814
|
+
a tier mismatch for a content-bearing panel.
|
|
7815
|
+
Corner-radius cascade (6 anchors now):
|
|
7816
|
+
R330 canvas rx 12 (root)
|
|
7817
|
+
R331 panels rx 10 (recent-signal, legend)
|
|
7818
|
+
R332 minimap rx 8 (compact chrome)
|
|
7819
|
+
R375 Layout-toggle rx 8 (segmented control)
|
|
7820
|
+
R376 nodeSize/zoom rx 8 (segmented control)
|
|
7821
|
+
R390 hover-detail rx 10 (panel — this round)
|
|
7822
|
+
Pure paint change; no layout shift (rx grows
|
|
7823
|
+
the corner curve INWARD without changing the
|
|
7824
|
+
card's outer bbox). data-topo-hover-detail-
|
|
7825
|
+
rx attr exposes the resolved value for tests.
|
|
7826
|
+
R348 drop-shadow + stroke + R387 opacity all
|
|
7827
|
+
preserved. */}
|
|
6114
7828
|
<rect
|
|
6115
|
-
x="0" y="0" width={detailW} height={detailH} rx="
|
|
7829
|
+
x="0" y="0" width={detailW} height={detailH} rx="10"
|
|
6116
7830
|
fill={pal.labelBox.fill}
|
|
6117
7831
|
stroke={pal.legendAccent}
|
|
6118
|
-
opacity={isLight ? 0.98 : 0.
|
|
7832
|
+
opacity={isLight ? 0.98 : 0.97}
|
|
7833
|
+
data-topo-hover-detail-opacity={isLight ? 0.98 : 0.97}
|
|
7834
|
+
data-topo-hover-detail-rx="10"
|
|
6119
7835
|
style={{ filter: isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.16))' : 'drop-shadow(0 4px 12px rgba(0,0,0,0.6))' }}
|
|
6120
7836
|
/>
|
|
6121
7837
|
<text x="10" y="16" fontSize="9" fontFamily="monospace" fill={pal.legendAccent} fontWeight="700">
|
|
6122
7838
|
{v.id !== 'unknown' ? v.label : '—'}
|
|
6123
7839
|
</text>
|
|
6124
|
-
|
|
7840
|
+
{/* Round 389 / Loop: hover-detail model line (y=32)
|
|
7841
|
+
fontWeight 400 → 600. R388 lifted body lines
|
|
7842
|
+
(runtime/host/task at fontSize=9) to fw=500;
|
|
7843
|
+
R389 closes the typography hierarchy by giving
|
|
7844
|
+
the model name (the dominant subhead text in
|
|
7845
|
+
the card) its own weight tier. Three-tier
|
|
7846
|
+
ladder now reads cleanly:
|
|
7847
|
+
vendor fontSize=9 fw=700 (label badge)
|
|
7848
|
+
model fontSize=10 fw=600 (subhead — this round)
|
|
7849
|
+
body 3× fontSize=9 fw=500 (R388)
|
|
7850
|
+
One tier step per dimension (size + weight)
|
|
7851
|
+
between adjacent levels — classic editorial
|
|
7852
|
+
hierarchy idiom adapted to a 192×88 SVG card.
|
|
7853
|
+
Sibling to the chip-internal-hierarchy arc
|
|
7854
|
+
(R333-R341/R362/R369) which uses fw=600/500
|
|
7855
|
+
for digit/unit pairs; R389 applies the same
|
|
7856
|
+
fw=600 to a content-bearing identity line.
|
|
7857
|
+
data-topo-hover-detail-model-fw attr exposes
|
|
7858
|
+
the resolved value for tests. pal.legendHeadline
|
|
7859
|
+
fill preserved (R389 doesn't touch color). */}
|
|
7860
|
+
<text x="10" y="32" fontSize="10" fontFamily="monospace" fontWeight="600" fill={pal.legendHeadline} data-topo-hover-detail-model-fw="600">
|
|
6125
7861
|
{session.model || 'model · pending'}
|
|
6126
7862
|
</text>
|
|
6127
|
-
|
|
7863
|
+
{/* Round 388 / Loop: hover-detail body lines (the
|
|
7864
|
+
three fontSize=9 lines: runtime, host, task)
|
|
7865
|
+
gain fontWeight=500. Small-text fw lift family
|
|
7866
|
+
(6th anchor) — fontSize 9-10 px text reads
|
|
7867
|
+
consistently bolder at fw=500 than at the
|
|
7868
|
+
default 400 weight at small sizes, especially
|
|
7869
|
+
on the cyber-theme backdrop where stroke-
|
|
7870
|
+
rendering is the limiting factor.
|
|
7871
|
+
Sibling lifts in this family:
|
|
7872
|
+
R363 recent-row alias text 400 → 500
|
|
7873
|
+
R364 legend-row label 400 → 500
|
|
7874
|
+
R366 group-label count tspan 400 → 500
|
|
7875
|
+
R368 +N more flows footer 400 → 500
|
|
7876
|
+
R373 pressure-bar kicker (font-medium)
|
|
7877
|
+
R388 hover-detail body lines 400 → 500 (this round)
|
|
7878
|
+
Tier structure preserved:
|
|
7879
|
+
y=16 vendor (fw=700, headline)
|
|
7880
|
+
y=32 model (fontSize=10, subhead by size)
|
|
7881
|
+
y=48 runtime / y=64 host / y=80 task (body, now fw=500)
|
|
7882
|
+
The y=80 task line keeps opacity=0.7 so its
|
|
7883
|
+
caption-tier identity stays distinct from the
|
|
7884
|
+
y=48 / y=64 body lines despite shared fw.
|
|
7885
|
+
data-topo-hover-detail-body-fw attr exposes
|
|
7886
|
+
the resolved value for tests. */}
|
|
7887
|
+
<text x="10" y="48" fontSize="9" fontFamily="monospace" fontWeight="500" fill={pal.legendText} data-topo-hover-detail-body-fw="500">
|
|
6128
7888
|
{rt ? rt.label : 'runtime · pending'}
|
|
6129
7889
|
</text>
|
|
6130
|
-
<text x="10" y="64" fontSize="9" fontFamily="monospace" fill={pal.legendText}>
|
|
7890
|
+
<text x="10" y="64" fontSize="9" fontFamily="monospace" fontWeight="500" fill={pal.legendText} data-topo-hover-detail-body-fw="500">
|
|
6131
7891
|
host · {session.server || 'unknown'}
|
|
6132
7892
|
</text>
|
|
6133
|
-
<text x="10" y="80" fontSize="9" fontFamily="monospace" fill={pal.legendText} opacity="0.7">
|
|
7893
|
+
<text x="10" y="80" fontSize="9" fontFamily="monospace" fontWeight="500" fill={pal.legendText} opacity="0.7" data-topo-hover-detail-body-fw="500">
|
|
6134
7894
|
{session.task ? truncate(session.task, 28) : 'no recent task'}
|
|
6135
7895
|
</text>
|
|
6136
7896
|
</g>
|
|
@@ -6194,14 +7954,41 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6194
7954
|
keySplines="0.25 0.1 0.25 1"
|
|
6195
7955
|
fill="freeze"
|
|
6196
7956
|
/>
|
|
7957
|
+
{/* Round 403 / Loop: click-ripple SMIL initial opacity
|
|
7958
|
+
0.7 → 0.8. Pre-R403 the ripple's opacity animation
|
|
7959
|
+
faded from 0.7 to 0 over 500ms, providing a clean
|
|
7960
|
+
click-feedback pulse. Theme-consistency / canvas-
|
|
7961
|
+
presence polish family (R370 hub hover-ring +
|
|
7962
|
+
R391 hub-spoke active) already lifted paired
|
|
7963
|
+
hover-state alphas from 0.7 → 0.8. R403 brings
|
|
7964
|
+
click-feedback into that same alpha — three canvas
|
|
7965
|
+
state-feedback indicators (hover-ring, active spoke,
|
|
7966
|
+
click ripple) now share a uniform 0.8 start alpha
|
|
7967
|
+
so the visual "I responded" signal carries the
|
|
7968
|
+
same weight regardless of which state fired it.
|
|
7969
|
+
Pre-R403 invariants preserved: 500ms duration,
|
|
7970
|
+
R227 calcMode='spline' + ease-out keySplines
|
|
7971
|
+
(0.25 0.1 0.25 1), fill='freeze', concurrent r
|
|
7972
|
+
animation. Theme-consistency family (8 anchors):
|
|
7973
|
+
R370 hub hover-ring 0.7 → 0.8
|
|
7974
|
+
R371 edge-badge rest 0.82 → 0.85 cyber
|
|
7975
|
+
R372 minimap offline-dot 0.5 → 0.6
|
|
7976
|
+
R386 hub-highlight idle 0.9 → 0.95
|
|
7977
|
+
R387 hover-detail panel 0.94 → 0.97 cyber
|
|
7978
|
+
R391 hub-spoke active 0.7 → 0.8
|
|
7979
|
+
R392 minimap online-dot 0.9 → 0.95
|
|
7980
|
+
R403 click-ripple start 0.7 → 0.8 (this round)
|
|
7981
|
+
data-click-ripple-start-opacity attr exposes the
|
|
7982
|
+
resolved value for tests. */}
|
|
6197
7983
|
<animate
|
|
6198
7984
|
attributeName="opacity"
|
|
6199
|
-
values="0.
|
|
7985
|
+
values="0.8;0"
|
|
6200
7986
|
dur="0.5s"
|
|
6201
7987
|
calcMode="spline"
|
|
6202
7988
|
keyTimes="0;1"
|
|
6203
7989
|
keySplines="0.25 0.1 0.25 1"
|
|
6204
7990
|
fill="freeze"
|
|
7991
|
+
data-click-ripple-start-opacity="0.8"
|
|
6205
7992
|
/>
|
|
6206
7993
|
</circle>
|
|
6207
7994
|
)}
|
|
@@ -6275,17 +8062,21 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6275
8062
|
<rect
|
|
6276
8063
|
x="0" y="0" width="230" height="88" rx="10"
|
|
6277
8064
|
fill={pal.legendBox.fill}
|
|
6278
|
-
stroke
|
|
6279
|
-
//
|
|
6280
|
-
//
|
|
6281
|
-
//
|
|
6282
|
-
//
|
|
6283
|
-
//
|
|
6284
|
-
//
|
|
6285
|
-
//
|
|
6286
|
-
//
|
|
6287
|
-
//
|
|
6288
|
-
//
|
|
8065
|
+
// Round 423 / Loop: panel rect stroke tints to legendAccent
|
|
8066
|
+
// (cyan) on hover — sibling to R217 label-card stroke
|
|
8067
|
+
// hover-tint at the panel scope. Pre-R423 the panel rect
|
|
8068
|
+
// stroke painted pal.legendBox.stroke (neutral) regardless
|
|
8069
|
+
// of hover state, while every other panel hover cue stacked:
|
|
8070
|
+
// R135 drop-shadow boost
|
|
8071
|
+
// R348 rect opacity 0.92 → 0.97 cyber
|
|
8072
|
+
// R345 title letter-spacing 0.3 → 0.4
|
|
8073
|
+
// R423 rect stroke → legendAccent (this round)
|
|
8074
|
+
// Four hover layers now telegraph "you're entering this
|
|
8075
|
+
// panel" through structural, paint, and typographic axes
|
|
8076
|
+
// simultaneously. R247 transition list already covers
|
|
8077
|
+
// stroke 200ms ease-out so the tint eases naturally.
|
|
8078
|
+
// Sibling change at the legend panel rect below.
|
|
8079
|
+
stroke={hoveredPanel === 'recent' ? pal.legendAccent : pal.legendBox.stroke}
|
|
6289
8080
|
opacity={hoveredPanel === 'recent' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}
|
|
6290
8081
|
style={{
|
|
6291
8082
|
/* R135: drop-shadow intensifies on panel hover. Base
|
|
@@ -6409,8 +8200,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6409
8200
|
const alpha = ageSec <= 30
|
|
6410
8201
|
? 1
|
|
6411
8202
|
: ageSec <= 300
|
|
6412
|
-
? 1 - ((ageSec - 30) / 270) * 0.
|
|
6413
|
-
: 0.25
|
|
8203
|
+
? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
|
|
8204
|
+
: 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
|
|
6414
8205
|
// Dark cyan-400 / light teal-600 with alpha — same
|
|
6415
8206
|
// palette as R161's chip bullet so the two scopes
|
|
6416
8207
|
// visually align even side-by-side.
|
|
@@ -6478,13 +8269,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6478
8269
|
tests + count value reads still resolve via
|
|
6479
8270
|
.textContent. data-recent-panel-count-unit on
|
|
6480
8271
|
the inner unit tspan for R336 introspection. */}
|
|
8272
|
+
{/* R424 — recent-signal panel count digit fontWeight
|
|
8273
|
+
600 → 700 on panel hover. Closes the 5-layer panel
|
|
8274
|
+
hover cue stack with a typographic-weight axis at
|
|
8275
|
+
the panel-header data scope: depth (R135 drop-
|
|
8276
|
+
shadow) + solidity (R348 fill opacity) + spacing
|
|
8277
|
+
(R345 title letter-spacing) + edge color (R423
|
|
8278
|
+
stroke tint) + weight (THIS, digit fw). Sibling
|
|
8279
|
+
pattern to R416 chip-digit-hover-bold at chip
|
|
8280
|
+
scope — same "data tightens under attention"
|
|
8281
|
+
idiom now at the panel-header data scope. R311
|
|
8282
|
+
base fw=600 + R225 tabular-nums + R162 fill
|
|
8283
|
+
transition + R336 unit-tspan opacity-0.7 all
|
|
8284
|
+
preserved; only the weight axis tweens via R247's
|
|
8285
|
+
transition shape (added font-weight to the list). */}
|
|
6481
8286
|
<tspan
|
|
6482
8287
|
fill={freshFill}
|
|
6483
|
-
fontWeight=
|
|
8288
|
+
fontWeight={hoveredPanel === 'recent' ? '700' : '600'}
|
|
6484
8289
|
data-recent-panel-count
|
|
6485
8290
|
data-recent-panel-count-freshness-alpha={alpha.toFixed(2)}
|
|
6486
8291
|
style={{
|
|
6487
|
-
transition: 'fill 200ms ease-out',
|
|
8292
|
+
transition: 'fill 200ms ease-out, font-weight 200ms ease-out',
|
|
6488
8293
|
fontVariantNumeric: 'tabular-nums',
|
|
6489
8294
|
}}
|
|
6490
8295
|
>{flowLinks.length}<tspan opacity="0.7" data-recent-panel-count-unit> flows</tspan></tspan>
|
|
@@ -6906,18 +8711,83 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6906
8711
|
const alpha = ageSec <= 30
|
|
6907
8712
|
? 1
|
|
6908
8713
|
: ageSec <= 300
|
|
6909
|
-
? 1 - ((ageSec - 30) / 270) * 0.
|
|
6910
|
-
: 0.25
|
|
8714
|
+
? 1 - ((ageSec - 30) / 270) * 0.70 /* R358: floor 0.25 → 0.30 lift across 3 freshness scopes */
|
|
8715
|
+
: 0.30; /* R358: stale floor lifted 0.25 → 0.30 — 20% legibility bump while preserving fresh/stale ratio */
|
|
6911
8716
|
return (
|
|
6912
8717
|
<circle
|
|
6913
8718
|
cx={10}
|
|
6914
8719
|
cy={38 + index * 16 - 3}
|
|
6915
|
-
|
|
8720
|
+
/* Round 359 / Loop: recency pip base radius
|
|
8721
|
+
1.6 → 1.8. Sibling lift to R358's freshness-
|
|
8722
|
+
floor bump (alpha 0.25 → 0.30) — pre-R358/
|
|
8723
|
+
R359 the stale pip painted at r=1.6 + α=0.25
|
|
8724
|
+
which read as near-invisible chrome. R358
|
|
8725
|
+
gave it more alpha; R359 gives it more area
|
|
8726
|
+
(1.8² / 1.6² ≈ 1.27, so ~27 % more glyph)
|
|
8727
|
+
so the pip stays distinguishable across the
|
|
8728
|
+
freshness ramp. Geometry: 1.8-radius dot
|
|
8729
|
+
centred at (10, row_y - 3) is bbox 3.6×3.6,
|
|
8730
|
+
still well inside the 7-px left margin
|
|
8731
|
+
(x=6 rect-start → x=13 text-start) the R160
|
|
8732
|
+
pip was placed in. Overlap-test reads the
|
|
8733
|
+
parent row rect's bbox, not this pip's, so
|
|
8734
|
+
grid+ring invariants hold. Matches the same
|
|
8735
|
+
1.6 → 1.8 visual-weight bump R295 applied
|
|
8736
|
+
to the legend swatch (5.5 → 6 base radius)
|
|
8737
|
+
and R287 to the minimap viewport stroke
|
|
8738
|
+
(1 → 1.5). data-recent-row-freshness-radius
|
|
8739
|
+
attr exposes the value for tests. */
|
|
8740
|
+
/* Round 383 / Loop: recency pip base radius
|
|
8741
|
+
1.8 → 2.0. Continues the R359 lift
|
|
8742
|
+
trajectory — pip area grows ~23 % (π·2²/
|
|
8743
|
+
π·1.8² ≈ 1.23) for a clearer at-a-glance
|
|
8744
|
+
freshness anchor in each row. Bbox 4.0×4.0
|
|
8745
|
+
still inside the 7-px R160 left margin
|
|
8746
|
+
(3-px remaining clearance vs 3.4 at r=1.8
|
|
8747
|
+
— geometry-safe margin holds). Sibling
|
|
8748
|
+
visual-weight bump family (9th anchor now):
|
|
8749
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
8750
|
+
R295 legend swatch base radius 5.5 → 6
|
|
8751
|
+
R359 recent-row pip base radius 1.6 → 1.8
|
|
8752
|
+
R360 hub digit fontSize 11 → 12
|
|
8753
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
8754
|
+
R365 hub-highlight base radius 5 → 5.5
|
|
8755
|
+
R367 edge-badge rest stroke 1 → 1.25
|
|
8756
|
+
R374 pressure-bar height 1.5 → 2
|
|
8757
|
+
R383 recent-row pip radius 1.8 → 2.0 (this round)
|
|
8758
|
+
data-recent-row-freshness-radius attr
|
|
8759
|
+
bumps to '2.0' for tests. */
|
|
8760
|
+
/* Round 447 / Loop: recent-row freshness pip
|
|
8761
|
+
radius lift on (isRowHovered || isRowPinned)
|
|
8762
|
+
— r 2.0 → 2.5 (+0.5px, sibling to R442
|
|
8763
|
+
endpoint-ring r lift). Adds a geometric
|
|
8764
|
+
axis to the recent-row hover/pin gesture
|
|
8765
|
+
alongside R143 translateY + R104 row bg-
|
|
8766
|
+
tint + R434 letter-spacing + R445 count
|
|
8767
|
+
fw. Pre-R447 the pip stayed at r=2.0 always
|
|
8768
|
+
— the freshness alpha (R162) tracked
|
|
8769
|
+
recency but didn't telegraph "this row is
|
|
8770
|
+
in focus" geometrically. R447 lifts the
|
|
8771
|
+
pip outward by 25% area (π·2.5² / π·2.0²
|
|
8772
|
+
= 1.56) on attention, closing a 5-axis
|
|
8773
|
+
row-attention signature (geometry + paint
|
|
8774
|
+
+ typography + spacing + position).
|
|
8775
|
+
SVG `r` as CSS property for interpolation
|
|
8776
|
+
(R197/R198 idiom). transition list extends
|
|
8777
|
+
to include 'r 200ms ease-out' matching the
|
|
8778
|
+
opacity cadence. data-recent-row-freshness-
|
|
8779
|
+
lifted attr exposes the gate for tests. */
|
|
6916
8780
|
fill={pal.legendAccent}
|
|
6917
8781
|
opacity={alpha}
|
|
6918
8782
|
data-recent-row-freshness={link.key}
|
|
6919
8783
|
data-recent-row-freshness-alpha={alpha.toFixed(2)}
|
|
6920
|
-
|
|
8784
|
+
data-recent-row-freshness-radius={(isRowHovered || isRowPinned) ? 2.5 : 2.0}
|
|
8785
|
+
data-recent-row-freshness-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}
|
|
8786
|
+
style={{
|
|
8787
|
+
pointerEvents: 'none',
|
|
8788
|
+
r: `${(isRowHovered || isRowPinned) ? 2.5 : 2.0}px`,
|
|
8789
|
+
transition: 'opacity 200ms ease-out, r 200ms ease-out',
|
|
8790
|
+
} as React.CSSProperties}
|
|
6921
8791
|
/>
|
|
6922
8792
|
);
|
|
6923
8793
|
})()}
|
|
@@ -6946,11 +8816,65 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6946
8816
|
fill={isRowActive ? pal.legendHeadline : pal.legendText}
|
|
6947
8817
|
fontSize="9"
|
|
6948
8818
|
fontFamily="monospace"
|
|
8819
|
+
/* Round 363 / Loop: recent-row text fontWeight 400
|
|
8820
|
+
→ 500 (font-medium tier). At fontSize=9 the
|
|
8821
|
+
default-weight 400 glyphs read thin against the
|
|
8822
|
+
panel chrome (pal.legendBox.fill with 0.92/0.97
|
|
8823
|
+
opacity); the 100-weight bump lifts the alias→
|
|
8824
|
+
alias text into the legibility band without
|
|
8825
|
+
changing geometry. The R320 count tspan fw=600
|
|
8826
|
+
(cold) / fw=700 (hot) override still wins
|
|
8827
|
+
locally via inline fontWeight on the inner
|
|
8828
|
+
tspan, so the count-vs-alias hierarchy stays
|
|
8829
|
+
intact:
|
|
8830
|
+
alias fw 500 (R363, this round)
|
|
8831
|
+
count fw 600/700 (R320)
|
|
8832
|
+
Sibling typography lift to R362 chip-row digit
|
|
8833
|
+
500 → 600 — both nudge a within-element data
|
|
8834
|
+
tier without disturbing the surrounding family
|
|
8835
|
+
baseline. data-recent-row-text-font-weight attr
|
|
8836
|
+
exposes the value for tests. */
|
|
8837
|
+
fontWeight="500"
|
|
6949
8838
|
data-recent-row-text={link.key}
|
|
6950
8839
|
data-recent-row-text-pinned={isRowPinned ? 'true' : 'false'}
|
|
8840
|
+
data-recent-row-text-hovered={!isRowPinned && isRowHovered ? 'true' : 'false'}
|
|
8841
|
+
data-recent-row-text-font-weight="500"
|
|
8842
|
+
/* Round 434 / Loop: recent-signal row text extends
|
|
8843
|
+
from R220's pin-only letter-spacing (0 → 0.5 on
|
|
8844
|
+
isRowPinned) to a 3-tier scale matching R433
|
|
8845
|
+
legend-row at the sibling panel-row scope:
|
|
8846
|
+
rest → 0px
|
|
8847
|
+
isRowHovered → 0.25px ← this round
|
|
8848
|
+
isRowPinned → 0.5px (R220 preserved)
|
|
8849
|
+
Pre-R434 R220 noted: "Hover stays at default
|
|
8850
|
+
tracking — the spread is pin-exclusive so
|
|
8851
|
+
users can read pinned vs hovered at the text
|
|
8852
|
+
alone." R427-R433 established a 3-tier pattern
|
|
8853
|
+
across 4 surfaces (node-alias, edge-badge,
|
|
8854
|
+
group-label, legend-row) where hover gets a
|
|
8855
|
+
subtler intermediate kerning step distinct
|
|
8856
|
+
from the pin tier's stronger spread — the
|
|
8857
|
+
locked vs preview discrimination R220 wanted
|
|
8858
|
+
is preserved (0.5 > 0.25) AND hover gets a
|
|
8859
|
+
typographic axis of its own. R434 completes
|
|
8860
|
+
the 5-surface arc.
|
|
8861
|
+
5-surface 3-tier letter-spacing pattern now
|
|
8862
|
+
spans every interactive label on TopoGraph
|
|
8863
|
+
that distinguishes hover from pin:
|
|
8864
|
+
node-alias (R427) 0 / 0.3 / 0.5
|
|
8865
|
+
edge-badge (R431) 0 / 0.2 / 0.4
|
|
8866
|
+
group-label (R432) 0 / 0.25 / 0.5
|
|
8867
|
+
legend-row (R433) 0 / 0.25 / 0.5
|
|
8868
|
+
recent-row (R434) 0 / 0.25 / 0.5 ← this round
|
|
8869
|
+
Hover-letter-spacing family extension
|
|
8870
|
+
(10 anchors now): R344/R345/R347/R351/R420/
|
|
8871
|
+
R427/R431/R432/R433/R434. R55 fill 150ms +
|
|
8872
|
+
R220 letter-spacing 150ms transition kept
|
|
8873
|
+
(additive conditional case, no new property). */
|
|
6951
8874
|
style={{
|
|
6952
8875
|
transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',
|
|
6953
|
-
letterSpacing: isRowPinned ? '0.5px' :
|
|
8876
|
+
letterSpacing: isRowPinned ? '0.5px' :
|
|
8877
|
+
isRowHovered ? '0.25px' : '0px',
|
|
6954
8878
|
}}
|
|
6955
8879
|
>
|
|
6956
8880
|
{/* R138 / Loop: typography unification with the rest
|
|
@@ -7010,19 +8934,60 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7010
8934
|
distinct, plus the fill flip from legendText
|
|
7011
8935
|
→ amber carries the dramatic part of the cue).
|
|
7012
8936
|
Sibling treatment in the data-weight tier. */}
|
|
8937
|
+
{/* Round 445 / Loop: extend the R320 cold/hot fw
|
|
8938
|
+
binary (600/700) to ALSO fire on isRowPinned —
|
|
8939
|
+
pinned-cold now lifts to 700 alongside the
|
|
8940
|
+
existing hot-triggered lift. Sibling to R444
|
|
8941
|
+
group-label-count-pin (500→600) at the
|
|
8942
|
+
recent-row scope. Both panel-row counts now
|
|
8943
|
+
respond to pin with a typographic weight lift,
|
|
8944
|
+
part of the "data tightens under attention"
|
|
8945
|
+
family (R416/R424/R425/R426/R444/R445).
|
|
8946
|
+
Effective tiers:
|
|
8947
|
+
cold + un-pinned → fw 600
|
|
8948
|
+
cold + pinned → fw 700 ← this round
|
|
8949
|
+
hot (any pin state) → fw 700 (R320 preserved)
|
|
8950
|
+
hot is still amber-filled (R127); cold pin
|
|
8951
|
+
stays at the parent fill, so the two routes
|
|
8952
|
+
to fw=700 are visually distinct (color vs
|
|
8953
|
+
no color). transition list adds 'font-
|
|
8954
|
+
weight 200ms ease-out' so the lift eases
|
|
8955
|
+
under the same R320 fill cadence. data-
|
|
8956
|
+
recent-row-count-pinned attr exposes the
|
|
8957
|
+
pin gate for tests. */}
|
|
7013
8958
|
<tspan
|
|
7014
8959
|
fill={isHot ? hotStroke : undefined}
|
|
7015
|
-
fontWeight={isHot ? '700' : '600'}
|
|
8960
|
+
fontWeight={(isHot || isRowPinned) ? '700' : '600'}
|
|
7016
8961
|
data-recent-row-count
|
|
8962
|
+
data-recent-row-count-pinned={isRowPinned ? 'true' : 'false'}
|
|
8963
|
+
data-recent-row-count-font-weight={(isHot || isRowPinned) ? '700' : '600'}
|
|
7017
8964
|
{...(isHot ? { 'data-recent-row-count-hot': 'true' } : {})}
|
|
7018
8965
|
style={{
|
|
7019
|
-
transition: 'fill 300ms ease-out',
|
|
8966
|
+
transition: 'fill 300ms ease-out, font-weight 200ms ease-out',
|
|
7020
8967
|
fontVariantNumeric: 'tabular-nums',
|
|
7021
8968
|
}}
|
|
7022
8969
|
>
|
|
7023
8970
|
{link.count}
|
|
7024
8971
|
</tspan>
|
|
7025
|
-
{
|
|
8972
|
+
{/* Round 418 / Loop: recent-row content preview
|
|
8973
|
+
gains opacity=0.7 wrapper — subordinate-text
|
|
8974
|
+
tier at the SVG-text scope. Pre-R418 the
|
|
8975
|
+
truncated content preview (e.g. " · hi there")
|
|
8976
|
+
inherited the row's full opacity, reading at
|
|
8977
|
+
the same emphasis as the alias text and
|
|
8978
|
+
count digit. R418 wraps it in a <tspan> at
|
|
8979
|
+
opacity=0.7 so the preview reads as
|
|
8980
|
+
subordinate metadata — sibling to R333-R341/
|
|
8981
|
+
R362/R369/R389/R410/R412 chip-internal-
|
|
8982
|
+
hierarchy "label tier" (opacity-70) at the
|
|
8983
|
+
HTML scope, and R317 subordinate-text-lift
|
|
8984
|
+
gray-500 → gray-400 family. The leading
|
|
8985
|
+
" · " separator stays at full opacity so
|
|
8986
|
+
the row punctuation rhythm holds. data-
|
|
8987
|
+
recent-row-content-tspan attr surfaces the
|
|
8988
|
+
subordinate wrapper for tests. */}
|
|
8989
|
+
{' · '}
|
|
8990
|
+
<tspan opacity="0.7" data-recent-row-content-tspan>{truncate(link.content, 8)}</tspan>
|
|
7026
8991
|
</text>
|
|
7027
8992
|
{lastAt ? (
|
|
7028
8993
|
/* Round 321 / Loop: lastAt freshness timestamp picks
|
|
@@ -7209,6 +9174,23 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7209
9174
|
surface. transition list extends letter-spacing
|
|
7210
9175
|
200ms ease-out alongside the existing opacity/
|
|
7211
9176
|
fill easings. */}
|
|
9177
|
+
{/* Round 368 / Loop: `+N more flows` footer text gains
|
|
9178
|
+
fontWeight=500 (font-medium tier). Sibling small-
|
|
9179
|
+
text fw lift family with R363 recent-row alias
|
|
9180
|
+
+ R364 legend-row label + R366 group-label count
|
|
9181
|
+
— all four lifts share the same theory: at small
|
|
9182
|
+
fontSize (9-11 px) against panel chrome, SVG-
|
|
9183
|
+
default fw 400 sits at the legibility floor;
|
|
9184
|
+
fw 500 brings the glyph into the deliberate-data
|
|
9185
|
+
band. fontStyle=italic + opacity 0.55 rest + R325
|
|
9186
|
+
letterSpacing 0.2 baseline + R344 hover-spread
|
|
9187
|
+
0.2 → 0.3 + R195 cyan fill on hover all preserved
|
|
9188
|
+
— the fw bump just thickens the italic stroke.
|
|
9189
|
+
Hover-state punch (R195 fill + R325 opacity 0.55
|
|
9190
|
+
→ 0.85 + R344 letter-spacing + R133 underline)
|
|
9191
|
+
stays as is, so the rest-vs-hover delta still
|
|
9192
|
+
reads clearly. data-recent-panel-more-font-weight
|
|
9193
|
+
attr exposes the value for tests. */}
|
|
7212
9194
|
<text
|
|
7213
9195
|
x="115" y="82"
|
|
7214
9196
|
textAnchor="middle"
|
|
@@ -7216,11 +9198,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7216
9198
|
fontSize="9"
|
|
7217
9199
|
fontFamily="monospace"
|
|
7218
9200
|
fontStyle="italic"
|
|
9201
|
+
fontWeight="500"
|
|
7219
9202
|
letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}
|
|
7220
9203
|
opacity={hoveredRecentMore ? 0.85 : 0.55}
|
|
7221
9204
|
textDecoration={hoveredRecentMore ? 'underline' : 'none'}
|
|
7222
9205
|
data-recent-panel-more={moreCount}
|
|
7223
9206
|
data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}
|
|
9207
|
+
data-recent-panel-more-font-weight="500"
|
|
7224
9208
|
style={{ transition: 'opacity 150ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out' }}
|
|
7225
9209
|
>
|
|
7226
9210
|
{`+ ${moreCount}`}
|
|
@@ -7272,7 +9256,11 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7272
9256
|
<rect
|
|
7273
9257
|
x="0" y="0" width="224" height="88" rx="10"
|
|
7274
9258
|
fill={pal.legendBox.fill}
|
|
7275
|
-
stroke
|
|
9259
|
+
// R423 sibling — legend panel rect stroke tints to
|
|
9260
|
+
// legendAccent on hover (mirrors recent-signal panel
|
|
9261
|
+
// above). 4-layer hover cue stack now symmetric across
|
|
9262
|
+
// both side panels.
|
|
9263
|
+
stroke={hoveredPanel === 'legend' ? pal.legendAccent : pal.legendBox.stroke}
|
|
7276
9264
|
// R348 sibling — legend panel rect opacity hover-state
|
|
7277
9265
|
// bump 0.92 → 0.97 (cyber) / 0.97 → 1 (light) on
|
|
7278
9266
|
// hoveredPanel === 'legend'. Pairs with the recent-signal
|
|
@@ -7364,9 +9352,17 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7364
9352
|
R336 introspection; the parent .textContent still
|
|
7365
9353
|
reads "{N} node(s)" so existing R310 count tests via
|
|
7366
9354
|
textContent unchanged. */}
|
|
9355
|
+
{/* R424 sibling — legend panel count digit fontWeight 600
|
|
9356
|
+
→ 700 on panel hover. Closes 5-layer panel hover cue
|
|
9357
|
+
stack symmetric across both side panels (recent-signal
|
|
9358
|
+
+ legend): depth (R135) + solidity (R348) + spacing
|
|
9359
|
+
(R345) + edge color (R423) + weight (R424). R310 base
|
|
9360
|
+
fw=600 + R292 tabular-nums + R266 fill transition + R336
|
|
9361
|
+
unit-tspan opacity-0.7 all preserved. Same "data tightens
|
|
9362
|
+
under attention" idiom R416 established at chip scope. */}
|
|
7367
9363
|
<text
|
|
7368
9364
|
x="211" y="21" textAnchor="end"
|
|
7369
|
-
fill={pal.legendAccent} fontSize="10" fontFamily="monospace" fontWeight=
|
|
9365
|
+
fill={pal.legendAccent} fontSize="10" fontFamily="monospace" fontWeight={hoveredPanel === 'legend' ? '700' : '600'}
|
|
7370
9366
|
// R349 sibling — legend panel header count picks up
|
|
7371
9367
|
// letterSpacing="0.2", one tier below the R301 panel
|
|
7372
9368
|
// title 0.3. Pairs with the recent-signal panel count
|
|
@@ -7376,7 +9372,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7376
9372
|
data-legend-panel-count
|
|
7377
9373
|
data-legend-panel-count-letter-spacing="0.2"
|
|
7378
9374
|
style={{
|
|
7379
|
-
transition: 'fill 200ms ease-out',
|
|
9375
|
+
transition: 'fill 200ms ease-out, font-weight 200ms ease-out',
|
|
7380
9376
|
fontVariantNumeric: 'tabular-nums',
|
|
7381
9377
|
}}
|
|
7382
9378
|
>{sessions.length}<tspan opacity="0.7" data-legend-panel-count-unit> node{sessions.length === 1 ? '' : 's'}</tspan></text>
|
|
@@ -7595,14 +9591,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7595
9591
|
so this legend-internal circle is invisible to
|
|
7596
9592
|
that probe. pointerEvents:none so the ring can't
|
|
7597
9593
|
intercept the row click that produced it. */}
|
|
9594
|
+
{/* Round 402 / Loop: legend pin-ring strokeWidth 1.5
|
|
9595
|
+
→ 1.75. Sibling visual-weight bump (12th anchor)
|
|
9596
|
+
to R385 hub hover-ring strokeWidth 1.5 → 1.75 —
|
|
9597
|
+
both are pin/hover state indicators painted as
|
|
9598
|
+
stroke-only circles outside their target swatch
|
|
9599
|
+
with the R51 sentinel value 1.5. R402 lifts to
|
|
9600
|
+
1.75 (matching R385's choice) so the pin signal
|
|
9601
|
+
reads slightly heavier without crossing the
|
|
9602
|
+
R51 sentinel band (3 reserved for offline node).
|
|
9603
|
+
The R51 selector is gated to g[data-node]
|
|
9604
|
+
ancestors so this legend-internal circle (lives
|
|
9605
|
+
under a <g data-legend-status>) is invisible
|
|
9606
|
+
to the probe — same lesson R177/R385 documented.
|
|
9607
|
+
Visual-weight bump family (12 anchors now):
|
|
9608
|
+
R287 minimap viewport stroke 1 → 1.5
|
|
9609
|
+
R295 legend swatch radius 5.5 → 6
|
|
9610
|
+
R359 recent-row pip radius 1.6 → 1.8
|
|
9611
|
+
R360 hub digit fontSize 11 → 12
|
|
9612
|
+
R361 edge-badge digit fontSize 10 → 11
|
|
9613
|
+
R365 hub-highlight radius 5 → 5.5
|
|
9614
|
+
R367 edge-badge rest stroke 1 → 1.25
|
|
9615
|
+
R374 pressure-bar height 1.5 → 2
|
|
9616
|
+
R383 recent-row pip radius 1.8 → 2.0
|
|
9617
|
+
R384 minimap online dot 1.7 → 1.9
|
|
9618
|
+
R385 hub hover-ring stroke 1.5 → 1.75
|
|
9619
|
+
R402 legend pin-ring stroke 1.5 → 1.75 (this round)
|
|
9620
|
+
R181 always-mount opacity gate + 150ms transition
|
|
9621
|
+
+ pointerEvents:none all preserved. data-legend-
|
|
9622
|
+
pin-ring-stroke-width attr exposes the value for
|
|
9623
|
+
tests. */}
|
|
7598
9624
|
<circle
|
|
7599
9625
|
cx="16" cy={row.y0} r="8"
|
|
7600
9626
|
fill="none"
|
|
7601
9627
|
stroke={row.fill}
|
|
7602
|
-
strokeWidth="1.
|
|
9628
|
+
strokeWidth="1.75"
|
|
7603
9629
|
opacity={isPinned ? 1 : 0}
|
|
7604
9630
|
data-legend-pin-ring={row.key}
|
|
7605
9631
|
data-legend-pin-ring-pinned={isPinned ? 'true' : 'false'}
|
|
9632
|
+
data-legend-pin-ring-stroke-width="1.75"
|
|
7606
9633
|
style={{
|
|
7607
9634
|
pointerEvents: 'none',
|
|
7608
9635
|
transition: 'opacity 150ms ease-out',
|
|
@@ -7629,11 +9656,61 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7629
9656
|
fill={hoveredStatus === row.key || isPinned ? pal.legendHeadline : pal.legendText}
|
|
7630
9657
|
fontSize="11"
|
|
7631
9658
|
fontFamily="monospace"
|
|
9659
|
+
/* Round 364 / Loop: legend-row label fontWeight 400
|
|
9660
|
+
→ 500. Sibling typography lift to R363 recent-row
|
|
9661
|
+
text fw 400 → 500. Both surfaces render small
|
|
9662
|
+
monospace text against panel chrome at fontSize
|
|
9663
|
+
9-11 where SVG-default fw 400 sits at the
|
|
9664
|
+
legibility floor. font-medium tier (500) gives
|
|
9665
|
+
the label a more deliberate-data register.
|
|
9666
|
+
The R309 per-row count text (separate element
|
|
9667
|
+
below at x=215 textAnchor=end) keeps its own
|
|
9668
|
+
fontWeight 600 inline override, so the count >
|
|
9669
|
+
label hierarchy stays intact at the legend
|
|
9670
|
+
scope same as R363 holds it at the recent-row
|
|
9671
|
+
scope:
|
|
9672
|
+
legend label fw 500 (R364, this round)
|
|
9673
|
+
legend count fw 600 (R309)
|
|
9674
|
+
recent alias fw 500 (R363)
|
|
9675
|
+
recent count fw 600/700 (R320)
|
|
9676
|
+
data-legend-row-label-font-weight attr exposes
|
|
9677
|
+
the value for tests. R219 letter-spacing pin
|
|
9678
|
+
tween + R55 fill transition + R181 always-mount
|
|
9679
|
+
pin ring all preserved. */
|
|
9680
|
+
fontWeight="500"
|
|
7632
9681
|
data-legend-row-label={row.key}
|
|
7633
9682
|
data-legend-row-label-pinned={isPinned ? 'true' : 'false'}
|
|
9683
|
+
data-legend-row-label-hovered={!isPinned && hoveredStatus === row.key ? 'true' : 'false'}
|
|
9684
|
+
data-legend-row-label-font-weight="500"
|
|
9685
|
+
/* Round 433 / Loop: legend-row text extends from
|
|
9686
|
+
R219's pin-only letter-spacing (0px → 0.5px on
|
|
9687
|
+
isPinned) to a 3-tier scale matching the R432
|
|
9688
|
+
group-label pattern:
|
|
9689
|
+
rest → 0px
|
|
9690
|
+
hoveredStatus → 0.25px ← this round
|
|
9691
|
+
isPinned → 0.5px (R219 preserved)
|
|
9692
|
+
Pre-R433 hover already brightened the fill
|
|
9693
|
+
(hoveredStatus===row.key || isPinned matches the
|
|
9694
|
+
legendHeadline branch) but the letter-form
|
|
9695
|
+
stayed dead-typographic on transient hover —
|
|
9696
|
+
only the pin tier carried a kerning signature.
|
|
9697
|
+
R433 adds the missing mid tier so hover
|
|
9698
|
+
telegraphs through BOTH fill brighten AND a
|
|
9699
|
+
subtle 0.25-px kerning spread, mirroring
|
|
9700
|
+
R427/R431/R432 at legend-row scope. Pin tier
|
|
9701
|
+
still wins so the locked vs preview distinction
|
|
9702
|
+
at the type level stays intact.
|
|
9703
|
+
Hover-letter-spacing family extension (9 anchors
|
|
9704
|
+
now): R344/R345/R347/R351/R420/R427/R431/R432/
|
|
9705
|
+
R433. 3-tier letter-spacing pattern now spans 4
|
|
9706
|
+
surfaces (node-alias R427, edge-badge R431,
|
|
9707
|
+
group-label R432, legend-row R433). R55 fill
|
|
9708
|
+
150ms + R219 letter-spacing 150ms transition
|
|
9709
|
+
untouched — additive conditional case. */
|
|
7634
9710
|
style={{
|
|
7635
9711
|
transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',
|
|
7636
|
-
letterSpacing: isPinned ? '0.5px' :
|
|
9712
|
+
letterSpacing: isPinned ? '0.5px' :
|
|
9713
|
+
hoveredStatus === row.key ? '0.25px' : '0px',
|
|
7637
9714
|
}}
|
|
7638
9715
|
>{row.label}</text>
|
|
7639
9716
|
{/* R95: live count anchored to the right edge of the
|
|
@@ -7712,20 +9789,60 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7712
9789
|
labels to single status words, the count
|
|
7713
9790
|
becomes proportionally more important; this
|
|
7714
9791
|
round emphasizes that role typographically. */}
|
|
9792
|
+
{/* Round 446 / Loop: legend per-row count fontWeight
|
|
9793
|
+
lift 600 → 700 on isPinned. Mirror of R444 group-
|
|
9794
|
+
label-count + R445 recent-row-count at the
|
|
9795
|
+
legend-row scope. Closes the 3-panel-row family
|
|
9796
|
+
for the "data tightens under attention" pattern —
|
|
9797
|
+
every panel-row count now responds to pin with a
|
|
9798
|
+
typographic-weight bump:
|
|
9799
|
+
R444 group-label-count 500 → 600
|
|
9800
|
+
R445 recent-row-count 600 → 700 (cold-pin route)
|
|
9801
|
+
R446 legend-row-count 600 → 700 ← this round
|
|
9802
|
+
Hover gate (hoveredStatus===row.key) keeps rest
|
|
9803
|
+
fw=600 so the locked-vs-preview distinction at
|
|
9804
|
+
the type level stays intact — same gate R433 used
|
|
9805
|
+
on the parent <text> letter-spacing tween. R309
|
|
9806
|
+
fw=600 baseline + R204 empty-row opacity dim +
|
|
9807
|
+
R225 tabular-nums all preserved. transition list
|
|
9808
|
+
extends to include 'font-weight 150ms ease-out'
|
|
9809
|
+
matching R433 fill/letter-spacing cadence.
|
|
9810
|
+
data-legend-count-pinned + -font-weight attrs
|
|
9811
|
+
exposed for tests. */}
|
|
7715
9812
|
<text
|
|
7716
9813
|
x="215" y={row.y1}
|
|
7717
9814
|
textAnchor="end"
|
|
7718
9815
|
fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? row.fill : pal.legendText}
|
|
7719
9816
|
fontSize="11"
|
|
7720
9817
|
fontFamily="monospace"
|
|
7721
|
-
fontWeight=
|
|
9818
|
+
fontWeight={isPinned ? '700' : '600'}
|
|
9819
|
+
/* Round 449 / Loop: legend-row count active-state
|
|
9820
|
+
opacity 0.95 → 1.0 on (hoveredStatus===row.key
|
|
9821
|
+
|| isPinned). Pre-R449 R204 lifted populated-row
|
|
9822
|
+
active opacity from rest 0.65 to 0.95 — visibly
|
|
9823
|
+
brighter but kept a 5 pct alpha gap (1 - 0.95).
|
|
9824
|
+
R449 closes the gap to 1.0 so the active count
|
|
9825
|
+
reads as confidently present alongside the R446
|
|
9826
|
+
fw=600→700 + R433 letter-spacing tween. Theme-
|
|
9827
|
+
consistency / canvas-presence family extension
|
|
9828
|
+
(7th anchor on the active-presence lift sub-
|
|
9829
|
+
family): R370 hub hover-ring 0.7→0.8, R371 edge-
|
|
9830
|
+
badge rest 0.82→0.85, R372 minimap offline-dot
|
|
9831
|
+
0.5→0.6, R386 hub-highlight idle 0.9→0.95, R387
|
|
9832
|
+
hover-detail panel 0.94→0.97, R429 label-card
|
|
9833
|
+
body 0.94→1.0, R449 legend-count active 0.95→1.0
|
|
9834
|
+
← this round. Empty-row opacity (R204: 0.28
|
|
9835
|
+
light / 0.30 cyber) and idle 0.65 rest both
|
|
9836
|
+
preserved. */
|
|
7722
9837
|
opacity={row.count === 0
|
|
7723
9838
|
? (isLight ? 0.28 : 0.30)
|
|
7724
|
-
: (hoveredStatus === row.key || isPinned ?
|
|
9839
|
+
: (hoveredStatus === row.key || isPinned ? 1 : 0.65)}
|
|
7725
9840
|
data-legend-count={row.key}
|
|
7726
9841
|
data-legend-count-empty={row.count === 0 ? 'true' : 'false'}
|
|
9842
|
+
data-legend-count-pinned={isPinned ? 'true' : 'false'}
|
|
9843
|
+
data-legend-count-font-weight={isPinned ? '700' : '600'}
|
|
7727
9844
|
data-legend-count-fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? 'tier' : 'neutral'}
|
|
7728
|
-
style={{ pointerEvents: 'none', transition: 'opacity 150ms ease-out, fill 150ms ease-out', fontVariantNumeric: 'tabular-nums' }}
|
|
9845
|
+
style={{ pointerEvents: 'none', transition: 'opacity 150ms ease-out, fill 150ms ease-out, font-weight 150ms ease-out', fontVariantNumeric: 'tabular-nums' }}
|
|
7729
9846
|
>{row.count}</text>
|
|
7730
9847
|
</g>
|
|
7731
9848
|
);
|
|
@@ -7949,14 +10066,75 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7949
10066
|
const isOn = s.status !== 'offline' || !!sseN;
|
|
7950
10067
|
const st = nodeStatus(s, isOn, isLight);
|
|
7951
10068
|
return (
|
|
10069
|
+
/* Round 372 / Loop: minimap offline-dot opacity
|
|
10070
|
+
0.5 → 0.6. Sibling stale-state legibility lift
|
|
10071
|
+
to R358 freshness ramp floor 0.25 → 0.30 + R317
|
|
10072
|
+
subordinate-text-lift family. Pre-R372 R198
|
|
10073
|
+
drew offline dots at α=0.5 (44 % below online
|
|
10074
|
+
0.9). The minimap is a small overlay against
|
|
10075
|
+
the canvas backdrop — at α=0.5 offline dots
|
|
10076
|
+
sat at the legibility floor when the minimap
|
|
10077
|
+
mounted (only on non-default view). R372 lifts
|
|
10078
|
+
offline 0.5 → 0.6 for +20 % relative presence;
|
|
10079
|
+
online stays at 0.9 so the offline/online
|
|
10080
|
+
contrast ratio is now 0.6/0.9 ≈ 0.67 (vs prior
|
|
10081
|
+
0.5/0.9 ≈ 0.56) — still a clear two-tier
|
|
10082
|
+
distinction. R198 opacity + fill + r transition
|
|
10083
|
+
list preserved so status flips still ease
|
|
10084
|
+
smoothly. data-topo-minimap-dot-opacity attr
|
|
10085
|
+
exposes the resolved value for tests. */
|
|
7952
10086
|
<circle
|
|
7953
10087
|
key={s.alias}
|
|
7954
10088
|
cx={p.x * sx} cy={p.y * sy}
|
|
7955
|
-
|
|
10089
|
+
/* Round 384 / Loop: minimap online dot radius 1.7
|
|
10090
|
+
→ 1.9. Sibling visual-weight bump (10th anchor)
|
|
10091
|
+
to R383 recent-row pip 1.8 → 2.0. R198 designed
|
|
10092
|
+
the dots at 1.7 (online) / 1.2 (offline) — at
|
|
10093
|
+
the minimap's 120 × 82 scale these read clearly
|
|
10094
|
+
but the online ↔ offline contrast was modest
|
|
10095
|
+
(1.7/1.2 = 1.42×). R384 bumps online to 1.9 so
|
|
10096
|
+
the tier delta widens to 1.58× (1.9/1.2). Pair
|
|
10097
|
+
completes minimap-dot legibility polish:
|
|
10098
|
+
R358 (era R372) offline opacity 0.5 → 0.6
|
|
10099
|
+
R384 online radius 1.7 → 1.9 (this round)
|
|
10100
|
+
R198 transition list (opacity + fill + r 200ms)
|
|
10101
|
+
preserved so status flips still ease smoothly.
|
|
10102
|
+
data-topo-minimap-dot-radius attr exposes the
|
|
10103
|
+
resolved value for tests. */
|
|
10104
|
+
/* Round 392 / Loop: minimap online dot opacity
|
|
10105
|
+
0.9 → 0.95. Theme-consistency / canvas-presence
|
|
10106
|
+
polish family (7th anchor) — mirrors R386's
|
|
10107
|
+
hub-highlight idle 0.9 → 0.95 lift on the
|
|
10108
|
+
minimap surface: the online-dot's idle alpha
|
|
10109
|
+
gap (0.10 against full presence) halves to
|
|
10110
|
+
0.05, so the live-fleet anchors on the minimap
|
|
10111
|
+
read more confidently. Offline dot stays at
|
|
10112
|
+
R372 0.6 — the binary online/offline contrast
|
|
10113
|
+
ratio shifts from 0.6/0.9 ≈ 0.67 to 0.6/0.95
|
|
10114
|
+
≈ 0.63, preserved as a clear two-tier
|
|
10115
|
+
distinction. R198 opacity + fill + r transition
|
|
10116
|
+
list + R384 r=1.9 + R372 offline 0.6 all
|
|
10117
|
+
preserved. data-topo-minimap-dot-opacity attr
|
|
10118
|
+
bumps to '0.95' for tests. */
|
|
10119
|
+
/* Round 421 / Loop: online dot opacity 0.95 → 1.0
|
|
10120
|
+
on minimap container hover. Sibling to R346
|
|
10121
|
+
viewport rect strokeWidth/opacity hover tween.
|
|
10122
|
+
When the user hovers the minimap container,
|
|
10123
|
+
the live-fleet anchors brighten from R392
|
|
10124
|
+
baseline (0.95) to full opacity in concert
|
|
10125
|
+
with the R346 viewport rect lift. Offline
|
|
10126
|
+
stays at R372 0.6 — hover state focuses
|
|
10127
|
+
attention on the ACTIVE anchors, not the
|
|
10128
|
+
stale ones. data-topo-minimap-dot-opacity
|
|
10129
|
+
attr (R392) reflects the resolved hover-
|
|
10130
|
+
state value for tests. */
|
|
10131
|
+
r={isOn ? 1.9 : 1.2}
|
|
7956
10132
|
fill={st.primary}
|
|
7957
|
-
opacity={isOn ? 0.
|
|
10133
|
+
opacity={isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6}
|
|
7958
10134
|
data-topo-minimap-dot={s.alias}
|
|
7959
10135
|
data-topo-minimap-dot-online={isOn ? 'true' : 'false'}
|
|
10136
|
+
data-topo-minimap-dot-opacity={isOn ? (hoveredMinimap ? 1 : 0.95) : 0.6}
|
|
10137
|
+
data-topo-minimap-dot-radius={isOn ? 1.9 : 1.2}
|
|
7960
10138
|
style={{
|
|
7961
10139
|
transition: 'opacity 200ms ease-out, fill 200ms ease-out, r 200ms ease-out',
|
|
7962
10140
|
} as React.CSSProperties}
|
|
@@ -7997,17 +10175,88 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7997
10175
|
information element to lift it above ambient
|
|
7998
10176
|
chrome. opacity 0.9 stays — strokeWidth alone
|
|
7999
10177
|
does the lifting. */}
|
|
10178
|
+
{/* Round 379 / Loop: minimap viewport rect picks up
|
|
10179
|
+
strokeLinejoin='round'. Pre-R379 the rect's 4
|
|
10180
|
+
corners painted with default 'miter' joins —
|
|
10181
|
+
sharp 90° corners with a small miter overshoot
|
|
10182
|
+
(≈ strokeWidth × 1.4 = 2.1 px at sw=1.5). R379
|
|
10183
|
+
rounds the joins so corners arc smoothly through
|
|
10184
|
+
a quarter-circle of radius ≈ strokeWidth/2. At
|
|
10185
|
+
sw=1.5 that's a 0.75-px radius — subtle but
|
|
10186
|
+
matches the same stroke-softening vocabulary R288
|
|
10187
|
+
chrome icons (zoom/reset/fullscreen) and R378
|
|
10188
|
+
flow-rail already speak. Geometry-safe: stroke-
|
|
10189
|
+
linejoin only affects the corner overshoot, the
|
|
10190
|
+
rect's bbox is unchanged. R287 strokeWidth=1.5 +
|
|
10191
|
+
R346 hover-state strokeWidth/opacity bump + R199
|
|
10192
|
+
smoothView x/y/w/h transition all preserved.
|
|
10193
|
+
data-topo-minimap-viewport-linejoin attr exposes
|
|
10194
|
+
the value for tests. */}
|
|
10195
|
+
{/* Round 393 / Loop: minimap viewport rect rx 0 → 2.
|
|
10196
|
+
Pre-R393 the cyan-stroked viewport rect (the frame
|
|
10197
|
+
showing what's currently visible on the canvas)
|
|
10198
|
+
drew with sharp corners inside the R332 rounded
|
|
10199
|
+
minimap container (rx=8). A small frame with sharp
|
|
10200
|
+
corners sitting inside a rounded container reads
|
|
10201
|
+
as visually loud — the 90° corners catch the eye
|
|
10202
|
+
against the soft container edge. R393 adds rx=2
|
|
10203
|
+
so the viewport corners get a subtle radius that
|
|
10204
|
+
matches the family's softening idiom on a sub-
|
|
10205
|
+
element scale. The R379 strokeLinejoin='round'
|
|
10206
|
+
already softens stroke joins; R393 adds a complete
|
|
10207
|
+
geometric soften via rx.
|
|
10208
|
+
Corner-radius cascade (7 anchors now):
|
|
10209
|
+
R330 canvas rx 12
|
|
10210
|
+
R331 panels rx 10
|
|
10211
|
+
R332 minimap container rx 8
|
|
10212
|
+
R375 Layout-toggle rx 8
|
|
10213
|
+
R376 nodeSize/zoom rx 8
|
|
10214
|
+
R390 hover-detail rx 10
|
|
10215
|
+
R393 minimap viewport rx 2 (this round, sub-element)
|
|
10216
|
+
The 2-px radius is intentionally small — the
|
|
10217
|
+
viewport rect is typically only 30-50px wide,
|
|
10218
|
+
where rx=2 reads as "rounded enough to not snap"
|
|
10219
|
+
without feeling pillowy. data-topo-minimap-
|
|
10220
|
+
viewport-rx attr exposes the resolved value
|
|
10221
|
+
for tests. R346 hover-state tweens (strokeWidth
|
|
10222
|
+
+ opacity) preserved verbatim. */}
|
|
8000
10223
|
<rect
|
|
8001
10224
|
x={Math.max(0, rectX)} y={Math.max(0, rectY)}
|
|
8002
10225
|
width={Math.max(0, Math.min(MW - Math.max(0, rectX), rectW))}
|
|
8003
10226
|
height={Math.max(0, Math.min(MH - Math.max(0, rectY), rectH))}
|
|
10227
|
+
rx="2"
|
|
8004
10228
|
fill="none" stroke={pal.legendAccent}
|
|
8005
10229
|
// R346: strokeWidth + opacity tween on container hover.
|
|
8006
10230
|
strokeWidth={hoveredMinimap ? '1.75' : '1.5'}
|
|
8007
|
-
|
|
10231
|
+
strokeLinejoin="round"
|
|
10232
|
+
/* Round 450 / Loop · milestone: minimap viewport rest
|
|
10233
|
+
opacity 0.9 → 0.95. Closes half the alpha gap on
|
|
10234
|
+
the wayfinding indicator while preserving the
|
|
10235
|
+
R346 hover delta to 1.0. Pre-R450 the rest viewport
|
|
10236
|
+
sat at 0.9 (10 pct alpha gap) — adequate but
|
|
10237
|
+
under-confident for the user's primary "you are
|
|
10238
|
+
here" indicator on the minimap. R450 lifts to 0.95
|
|
10239
|
+
so the rest read is more present without erasing
|
|
10240
|
+
the hover lift cue (the +0.05 rest-to-hover delta
|
|
10241
|
+
is small but pairs with R346 sw 1.5→1.75 to keep
|
|
10242
|
+
hover clearly distinguishable). Sibling to R449
|
|
10243
|
+
legend-count active opacity 0.95→1.0 — same
|
|
10244
|
+
"close the active-presence alpha gap" idiom now
|
|
10245
|
+
applied to the REST tier of the wayfinding rect
|
|
10246
|
+
(the minimap viewport stays at canvas-presence
|
|
10247
|
+
register even when un-hovered since it's the
|
|
10248
|
+
spatial referent). Theme-consistency / canvas-
|
|
10249
|
+
presence family (8th anchor on the active-
|
|
10250
|
+
presence lift sub-arc).
|
|
10251
|
+
R287 strokeWidth=1.5 + R379 strokeLinejoin='round'
|
|
10252
|
+
+ R346 hover-state tweens + R393 rx=2 + R199
|
|
10253
|
+
smoothView x/y/w/h transition all preserved. */
|
|
10254
|
+
opacity={hoveredMinimap ? '1' : '0.95'}
|
|
8008
10255
|
data-topo-minimap-viewport
|
|
10256
|
+
data-topo-minimap-viewport-rx="2"
|
|
8009
10257
|
data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}
|
|
8010
10258
|
data-topo-minimap-viewport-hover={hoveredMinimap ? 'true' : 'false'}
|
|
10259
|
+
data-topo-minimap-viewport-linejoin="round"
|
|
8011
10260
|
style={{
|
|
8012
10261
|
transition: smoothView
|
|
8013
10262
|
? 'x 280ms ease-out, y 280ms ease-out, width 280ms ease-out, height 280ms ease-out, stroke-width 200ms ease-out, opacity 200ms ease-out'
|
|
@@ -8066,8 +10315,22 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8066
10315
|
own transition-colors. Same R254 holdover pattern that
|
|
8067
10316
|
R263 just closed at the canvas wrapper scope, now at the
|
|
8068
10317
|
chrome strip's nodeSize sub-wrapper scope. */}
|
|
10318
|
+
{/* Round 376 / Loop: nodeSize wrapper rounded-md → rounded-lg.
|
|
10319
|
+
Sibling polish to R375 Layout-toggle wrapper. Three
|
|
10320
|
+
chrome-strip segmented controls now all share rounded-lg
|
|
10321
|
+
at the wrapper tier:
|
|
10322
|
+
R375 Layout-toggle wrapper rounded-lg 8 px
|
|
10323
|
+
R376 nodeSize wrapper rounded-lg 8 px (this round)
|
|
10324
|
+
R376 zoom wrapper rounded-lg 8 px (this round)
|
|
10325
|
+
Individual atomic chrome buttons (reset, fullscreen) keep
|
|
10326
|
+
rounded-md (6 px) as their own atomic-button tier — the
|
|
10327
|
+
chrome strip's typography now expresses a clear two-tier
|
|
10328
|
+
hierarchy: 'segmented control container' (rounded-lg)
|
|
10329
|
+
vs 'standalone button' (rounded-md). Pure paint change,
|
|
10330
|
+
no layout shift. */}
|
|
8069
10331
|
<div
|
|
8070
|
-
className="flex items-center rounded-
|
|
10332
|
+
className="flex items-center rounded-lg border overflow-hidden"
|
|
10333
|
+
data-topo-chrome-nodesize-radius="rounded-lg"
|
|
8071
10334
|
style={{
|
|
8072
10335
|
background: pal.legendBox.fill,
|
|
8073
10336
|
borderColor: pal.containerBorder,
|
|
@@ -8147,8 +10410,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8147
10410
|
data-topo-chrome-view-group-leader marks the boundary surface
|
|
8148
10411
|
for the test probe; data-topo-chrome-fleet-group-trailer marks
|
|
8149
10412
|
the nodeSize wrapper's right edge for the gap measurement. */}
|
|
10413
|
+
{/* R376 sibling — zoom wrapper rounded-md → rounded-lg.
|
|
10414
|
+
Closes the chrome-strip segmented-control corner radius
|
|
10415
|
+
cascade (Layout R375 + nodeSize R376 + zoom R376). */}
|
|
8150
10416
|
<div
|
|
8151
|
-
className="ml-1.5 flex items-center rounded-
|
|
10417
|
+
className="ml-1.5 flex items-center rounded-lg border overflow-hidden"
|
|
10418
|
+
data-topo-chrome-zoom-wrapper-radius="rounded-lg"
|
|
8152
10419
|
style={{
|
|
8153
10420
|
background: pal.legendBox.fill,
|
|
8154
10421
|
borderColor: pal.containerBorder,
|
|
@@ -8187,11 +10454,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8187
10454
|
its own compositor layer for crisper edges during
|
|
8188
10455
|
the scale tween. Sibling change on zoom-in icon
|
|
8189
10456
|
below. */}
|
|
10457
|
+
{/* Round 454 / Loop: extend R453 chrome reset icon hover
|
|
10458
|
+
sw lift to zoom +/− icons via Tailwind arbitrary class
|
|
10459
|
+
group-hover:[stroke-width:2.8]. Chrome icon hover sw
|
|
10460
|
+
lift family now 5 anchors:
|
|
10461
|
+
R208 runtime badge outer ring 1.5 → 2
|
|
10462
|
+
R443 runtime badge inner icon 2.4 → 2.8
|
|
10463
|
+
R453 chrome reset icon 2.5 → 2.8
|
|
10464
|
+
R454 chrome zoom-out icon 2.5 → 2.8 ← this round
|
|
10465
|
+
R454 chrome zoom-in icon 2.5 → 2.8 ← this round
|
|
10466
|
+
Tailwind v4 arbitrary-value group-hover variant
|
|
10467
|
+
resolves [stroke-width:2.8] as a CSS property which
|
|
10468
|
+
overrides the static strokeWidth='2.5' attribute on
|
|
10469
|
+
hover. transition-[stroke-width] appended to the
|
|
10470
|
+
existing transition-transform list so the sw tween
|
|
10471
|
+
eases under the same 200ms cadence as R352 group-
|
|
10472
|
+
hover:scale-110. R186 anet-chrome-pop keyframe still
|
|
10473
|
+
owns transform during click via CSS-animation
|
|
10474
|
+
precedence over transition-transform. Sibling change
|
|
10475
|
+
on zoom-in icon below. */}
|
|
8190
10476
|
<svg
|
|
8191
10477
|
width="12" height="12" viewBox="0 0 24 24"
|
|
8192
10478
|
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
8193
10479
|
aria-hidden
|
|
8194
|
-
className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-out' ? ' anet-chrome-pop' : ''}`}
|
|
10480
|
+
className={`transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:[stroke-width:2.8] transform-gpu${chromePopping === 'zoom-out' ? ' anet-chrome-pop' : ''}`}
|
|
8195
10481
|
data-topo-chrome-zoom-out-icon
|
|
8196
10482
|
><path d="M5 12h14" /></svg>
|
|
8197
10483
|
</button>
|
|
@@ -8248,6 +10534,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8248
10534
|
// R347: letter-spacing hover tween — extends R344/R345
|
|
8249
10535
|
// hover-letter-spacing family into the chrome strip.
|
|
8250
10536
|
letterSpacing: hoveredZoomLevel ? '0.5px' : '0',
|
|
10537
|
+
// Round 420 / Loop: zoom-level readout gains a SECOND
|
|
10538
|
+
// hover axis — fontWeight 500 → 600 on hover. Sibling
|
|
10539
|
+
// to R347 (same element, hover letter-spacing tween).
|
|
10540
|
+
// The chrome strip's only data display now has a two-
|
|
10541
|
+
// axis hover signature (letter-spacing + fontWeight),
|
|
10542
|
+
// matching the R416 chip-row chip digit hover-bold
|
|
10543
|
+
// pattern at the chrome scope. Pre-R420 hovering only
|
|
10544
|
+
// spread the digits 0 → 0.5px; the weight stayed at
|
|
10545
|
+
// R332's 'font-medium' (500) baseline. Post-R420
|
|
10546
|
+
// hover lifts BOTH letter-spacing AND weight so the
|
|
10547
|
+
// percent reads with the same data-tier emphasis
|
|
10548
|
+
// intensification the chip-row chips do on hover.
|
|
10549
|
+
// Inline fontWeight overrides the className's
|
|
10550
|
+
// 'font-medium' since they target the same property.
|
|
10551
|
+
// 200ms transition list extends to font-weight for
|
|
10552
|
+
// smooth easing. data-topo-chrome-zoom-level-hover
|
|
10553
|
+
// attr surfaces the hover state for tests.
|
|
10554
|
+
fontWeight: hoveredZoomLevel ? 600 : 500,
|
|
8251
10555
|
/* Round 264 / Loop: zoom level readout gains theme-toggle
|
|
8252
10556
|
transition. The span has theme-driven color (pal.
|
|
8253
10557
|
legendText) + border-x (pal.containerBorder via the
|
|
@@ -8256,7 +10560,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8256
10560
|
on theme flip while siblings eased. Sibling treatment
|
|
8257
10561
|
to the nodeSize + zoom wrapper transitions added this
|
|
8258
10562
|
round. */
|
|
8259
|
-
transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out',
|
|
10563
|
+
transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out',
|
|
8260
10564
|
}}
|
|
8261
10565
|
title="Current zoom level"
|
|
8262
10566
|
>
|
|
@@ -8278,11 +10582,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8278
10582
|
{/* R352 sibling — zoom-in icon picks up the same
|
|
8279
10583
|
group-hover:scale-110 family. Mirror change at
|
|
8280
10584
|
the zoom-out icon above. */}
|
|
10585
|
+
{/* R454 sibling — zoom-in icon picks up the same
|
|
10586
|
+
group-hover:[stroke-width:2.8] family lift. */}
|
|
8281
10587
|
<svg
|
|
8282
10588
|
width="12" height="12" viewBox="0 0 24 24"
|
|
8283
10589
|
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
8284
10590
|
aria-hidden
|
|
8285
|
-
className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-in' ? ' anet-chrome-pop' : ''}`}
|
|
10591
|
+
className={`transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:[stroke-width:2.8] transform-gpu${chromePopping === 'zoom-in' ? ' anet-chrome-pop' : ''}`}
|
|
8286
10592
|
data-topo-chrome-zoom-in-icon
|
|
8287
10593
|
><path d="M12 5v14M5 12h14" /></svg>
|
|
8288
10594
|
</button>
|
|
@@ -8299,7 +10605,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8299
10605
|
onBlur={() => setHoveredReset(false)}
|
|
8300
10606
|
// R196: press-state deepens before R184 reset-spin fires on
|
|
8301
10607
|
// release — mouse-down dim then 450ms spin = full handshake.
|
|
8302
|
-
|
|
10608
|
+
/* Round 400 / Loop · milestone: chrome reset + fullscreen
|
|
10609
|
+
buttons gain hover:-translate-y-px lift — closes the
|
|
10610
|
+
hover-lift gesture vocabulary across every standalone
|
|
10611
|
+
interactive HTML element in TopoGraph. Segmented
|
|
10612
|
+
controls (zoom -/+, nodeSize S/M/L, Layout Ring/Grid)
|
|
10613
|
+
intentionally stay planted: lifting one segment of a
|
|
10614
|
+
unified strip would tear the visual unity of the
|
|
10615
|
+
segmented control. Only the standalone chrome buttons
|
|
10616
|
+
(reset, fullscreen) get the lift.
|
|
10617
|
+
Gesture vocabulary post-R400 (now complete across HTML):
|
|
10618
|
+
chip-row chips (3×) -1 px R398, R399
|
|
10619
|
+
filter pin pills (4×) -1 px R397
|
|
10620
|
+
recent-signal row -1 px R143
|
|
10621
|
+
legend row -1 px R144
|
|
10622
|
+
reset button -1 px R400 (this round)
|
|
10623
|
+
fullscreen button -1 px R400 (this round)
|
|
10624
|
+
Every standalone interactive HTML surface in TopoGraph
|
|
10625
|
+
now lifts on hover. data-topo-chrome-reset-hover-lift
|
|
10626
|
+
attr surfaces the lift for tests. */
|
|
10627
|
+
className="p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 hover:-translate-y-px transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60"
|
|
10628
|
+
data-topo-chrome-reset-hover-lift="true"
|
|
8303
10629
|
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}
|
|
8304
10630
|
aria-label="Reset view"
|
|
8305
10631
|
title="Reset zoom + pan (0, or double-click the canvas)"
|
|
@@ -8319,13 +10645,28 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8319
10645
|
(2.5) all share one weight. View-box (24×24) and
|
|
8320
10646
|
display size (13×13) unchanged, so geometry stays
|
|
8321
10647
|
pixel-stable — only the stroke deepens. */}
|
|
10648
|
+
{/* Round 453 / Loop: chrome reset icon strokeWidth hover
|
|
10649
|
+
lift — 2.5 → 2.8 on hoveredReset && !resetSpinning.
|
|
10650
|
+
Sibling to R443 runtime badge inner-icon sw lift
|
|
10651
|
+
(2.4→2.8) — both chrome icons now thicken on hover
|
|
10652
|
+
for tactile feedback. Pre-R453 reset hover was a
|
|
10653
|
+
rotate-only cue (R350); R453 adds a stroke-weight
|
|
10654
|
+
axis so the affordance reads with both motion (R350
|
|
10655
|
+
rotate -8°) AND geometry (R453 sw +0.3). Gated on
|
|
10656
|
+
!resetSpinning so the R184 spin keyframe owns paint
|
|
10657
|
+
during its 450ms run. 200ms stroke-width transition
|
|
10658
|
+
appended to the style list matches R350 transform
|
|
10659
|
+
cadence. data-topo-chrome-reset-icon-stroke-width
|
|
10660
|
+
attr exposes the resolved value for tests. */}
|
|
8322
10661
|
<svg
|
|
8323
10662
|
width="13" height="13" viewBox="0 0 24 24"
|
|
8324
|
-
fill="none" stroke="currentColor"
|
|
10663
|
+
fill="none" stroke="currentColor"
|
|
10664
|
+
strokeWidth={hoveredReset && !resetSpinning ? '2.8' : '2.5'}
|
|
8325
10665
|
strokeLinecap="round" strokeLinejoin="round"
|
|
8326
10666
|
aria-hidden
|
|
8327
10667
|
className={resetSpinning ? 'anet-reset-spin' : undefined}
|
|
8328
10668
|
data-topo-chrome-reset-icon
|
|
10669
|
+
data-topo-chrome-reset-icon-stroke-width={hoveredReset && !resetSpinning ? '2.8' : '2.5'}
|
|
8329
10670
|
// R350: hover-rotate preview of the R184 click-spin.
|
|
8330
10671
|
// Gated on !resetSpinning so the anet-reset-spin keyframe
|
|
8331
10672
|
// owns transform during its 450ms run. transformOrigin
|
|
@@ -8334,7 +10675,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8334
10675
|
style={{
|
|
8335
10676
|
transform: hoveredReset && !resetSpinning ? 'rotate(-8deg)' : 'rotate(0deg)',
|
|
8336
10677
|
transformOrigin: 'center',
|
|
8337
|
-
transition: 'transform 200ms ease-out',
|
|
10678
|
+
transition: 'transform 200ms ease-out, stroke-width 200ms ease-out',
|
|
8338
10679
|
}}
|
|
8339
10680
|
data-topo-chrome-reset-icon-hover={hoveredReset && !resetSpinning ? 'true' : 'false'}
|
|
8340
10681
|
>
|
|
@@ -8375,11 +10716,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8375
10716
|
// icon hover-affordance arc (zoom-out / zoom-in / reset /
|
|
8376
10717
|
// fullscreen now all carry an icon-level hover gesture in
|
|
8377
10718
|
// addition to the bg hover).
|
|
8378
|
-
|
|
10719
|
+
// R400: hover translateY(-1px) lift — see reset button above for family doc.
|
|
10720
|
+
className={`group p-1.5 rounded-md border hover:-translate-y-px transition-colors transition-transform duration-200 ease-out transform-gpu focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${
|
|
8379
10721
|
isFullscreen
|
|
8380
10722
|
? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'
|
|
8381
10723
|
: 'hover:bg-cyan-500/5 active:bg-cyan-500/15'
|
|
8382
10724
|
}${chromePopping === 'fullscreen' ? ' anet-chrome-pop' : ''}`}
|
|
10725
|
+
data-topo-chrome-fullscreen-hover-lift="true"
|
|
8383
10726
|
style={{
|
|
8384
10727
|
borderColor: pal.containerBorder,
|
|
8385
10728
|
...(isFullscreen
|
|
@@ -8402,12 +10745,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8402
10745
|
gpu hint promotes the svg to its own compositor layer
|
|
8403
10746
|
for crisper edges during the scale tween. Closes the
|
|
8404
10747
|
chrome-strip per-icon hover-affordance arc. */}
|
|
10748
|
+
{/* R455 — fullscreen ENTER + EXIT icons pick up the same
|
|
10749
|
+
group-hover:[stroke-width:2.8] family lift as the
|
|
10750
|
+
zoom +/− icons (R454) and chrome reset icon (R453).
|
|
10751
|
+
Chrome icon hover sw lift family now 6 anchors —
|
|
10752
|
+
R208/R443 runtime badge + R453/R454-zoom-out/zoom-in
|
|
10753
|
+
+ R455 fullscreen (this round). transition-[transform,
|
|
10754
|
+
stroke-width] expands existing transition-transform
|
|
10755
|
+
so the sw lift eases under R352 scale-110 cadence. */}
|
|
8405
10756
|
{isFullscreen ? (
|
|
8406
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu" data-topo-chrome-fullscreen-icon="exit">
|
|
10757
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:[stroke-width:2.8] transform-gpu" data-topo-chrome-fullscreen-icon="exit">
|
|
8407
10758
|
<path d="M8 3v4a1 1 0 0 1-1 1H3M21 8h-4a1 1 0 0 1-1-1V3M3 16h4a1 1 0 0 1 1 1v4M16 21v-4a1 1 0 0 1 1-1h4" />
|
|
8408
10759
|
</svg>
|
|
8409
10760
|
) : (
|
|
8410
|
-
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu" data-topo-chrome-fullscreen-icon="enter">
|
|
10761
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden className="transition-[transform,stroke-width] duration-200 ease-out group-hover:scale-110 group-hover:[stroke-width:2.8] transform-gpu" data-topo-chrome-fullscreen-icon="enter">
|
|
8411
10762
|
<path d="M3 8V5a2 2 0 0 1 2-2h3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M21 16v3a2 2 0 0 1-2 2h-3" />
|
|
8412
10763
|
</svg>
|
|
8413
10764
|
)}
|