@sleep2agi/agent-network-dashboard 0.5.1-preview.99 → 0.5.1
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/05admfiu6qfp2.js +1 -0
- package/.next/static/chunks/0a4hmfvj-81x5.css +2 -0
- package/.next/static/chunks/0d9mlqf.rjey5.js +1 -0
- package/.next/static/chunks/11puuje6at2jt.js +4 -0
- package/.next/static/chunks/{03~~oirxz7~vc.js → 15ub0_3b099x1.js} +1 -1
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/ServersDrawer.tsx +17 -0
- package/app/components/TopoGraph.tsx +722 -76
- 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-any-hover-attr-test.mjs +83 -0
- package/scripts/topo-any-pinned-attr-test.mjs +86 -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-zoom-icon-sw-test.mjs +90 -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-particle-hover-r-test.mjs +113 -0
- package/scripts/topo-endpoint-ring-r-hover-test.mjs +89 -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-label-count-fw-test.mjs +100 -0
- package/scripts/topo-group-label-fw-pin-test.mjs +99 -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-group-tint-rx-pin-test.mjs +107 -0
- package/scripts/topo-hub-core-fill-hover-test.mjs +85 -0
- package/scripts/topo-hub-halo-r-hover-test.mjs +82 -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-minimap-viewport-opacity-test.mjs +96 -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-sub-text-fw-test.mjs +75 -0
- package/scripts/topo-overlap-stale-build-guard-test.mjs +66 -0
- package/scripts/topo-overlap-test.mjs +42 -1
- package/scripts/topo-recent-row-count-pin-fw-test.mjs +106 -0
- package/scripts/topo-recent-row-pip-hover-r-test.mjs +104 -0
- package/scripts/topo-runtime-icon-hover-test.mjs +96 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__03b.f76._.js.map +0 -1
- package/.next/static/chunks/0.sf46gnv4wwm.js +0 -1
- package/.next/static/chunks/017hq2-5l~_98.css +0 -2
- package/.next/static/chunks/0_igeywsok2_-.js +0 -1
- package/.next/static/chunks/0igz0bww16uvc.js +0 -4
- /package/.next/static/{4rHxTzLwe2XC9M1rN-MpJ → gowHgWkn_kpMCSBn8RFaa}/_buildManifest.js +0 -0
- /package/.next/static/{4rHxTzLwe2XC9M1rN-MpJ → gowHgWkn_kpMCSBn8RFaa}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{4rHxTzLwe2XC9M1rN-MpJ → gowHgWkn_kpMCSBn8RFaa}/_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
|
|
@@ -3457,6 +3458,81 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3457
3458
|
return `Agent network topology — ${parts.join(' · ')}. Tab to navigate nodes, double-click canvas to reset view.`;
|
|
3458
3459
|
})()}
|
|
3459
3460
|
data-topo-canvas-aria
|
|
3461
|
+
/* Round 466 / Loop — aggregate hover signal on the root SVG.
|
|
3462
|
+
Exposes a single boolean `data-topo-any-hover` that
|
|
3463
|
+
reflects whether ANY hover state in the topology is
|
|
3464
|
+
active. Composed from the existing per-surface hover
|
|
3465
|
+
vars; doesn't introduce new state. Useful for:
|
|
3466
|
+
- Playwright tests asserting "topology entered a hover
|
|
3467
|
+
mode" without enumerating per-surface attrs
|
|
3468
|
+
- external CSS hooks targeting `[data-topo-any-hover=
|
|
3469
|
+
"true"]` to dim adjacent UI (e.g. chrome strip)
|
|
3470
|
+
while the user is inspecting the canvas
|
|
3471
|
+
- debug overlays that visualise hover dwell-time
|
|
3472
|
+
The 6 hover sources contributing:
|
|
3473
|
+
hoveredAlias (node circle / card / alias text)
|
|
3474
|
+
hoveredHub (hub center, halo, ring)
|
|
3475
|
+
hoveredEdgeKey (flow link path / particle / endpoint)
|
|
3476
|
+
hoveredGroupLabel (cluster name / count / pips)
|
|
3477
|
+
hoveredStatus (legend row)
|
|
3478
|
+
hoveredVendor (vendor chip in chip row)
|
|
3479
|
+
Read-only computed attr — zero re-render cost beyond the
|
|
3480
|
+
React update that already fires when any of those state
|
|
3481
|
+
vars flips. Geometry / paint untouched. */
|
|
3482
|
+
data-topo-any-hover={
|
|
3483
|
+
(hoveredAlias || hoveredHub || hoveredEdgeKey || hoveredGroupLabel ||
|
|
3484
|
+
hoveredStatus || hoveredVendor) ? 'true' : 'false'
|
|
3485
|
+
}
|
|
3486
|
+
/* Round 467 / Loop — pin-aggregate sibling to R466 hover-
|
|
3487
|
+
aggregate. Exposes `data-topo-any-pinned` reflecting
|
|
3488
|
+
whether ANY sticky inspection mode is active. Composed
|
|
3489
|
+
from the 4 pinned state vars:
|
|
3490
|
+
pinnedStatus (legend row click → status filter)
|
|
3491
|
+
pinnedGroup (group label click → cluster lock)
|
|
3492
|
+
pinnedVendor (vendor chip click → vendor filter)
|
|
3493
|
+
pinnedEdgeKey (edge click → edge focus)
|
|
3494
|
+
Together with R466 the root svg now carries a 2-bit
|
|
3495
|
+
inspection-mode surface:
|
|
3496
|
+
data-topo-any-hover — transient (mouse hover)
|
|
3497
|
+
data-topo-any-pinned — sticky (click-to-lock)
|
|
3498
|
+
Useful for:
|
|
3499
|
+
- Playwright tests: one-line query for either mode
|
|
3500
|
+
- external CSS hooks: render a persistent "filter
|
|
3501
|
+
active" badge when pinned, distinct from the
|
|
3502
|
+
transient hover dim
|
|
3503
|
+
- Esc-handler tests: assert all 4 pins clear after
|
|
3504
|
+
the universal-cancel Escape press (R62/R63/R88/
|
|
3505
|
+
R116 — single Esc collapses every pin)
|
|
3506
|
+
Read-only computed disjunction; no new state, zero
|
|
3507
|
+
re-render cost beyond the React pin-flip updates. */
|
|
3508
|
+
data-topo-any-pinned={
|
|
3509
|
+
(pinnedStatus || pinnedGroup || pinnedVendor || pinnedEdgeKey) ? 'true' : 'false'
|
|
3510
|
+
}
|
|
3511
|
+
/* Round 462 / Loop — surface DASHBOARD_VERSION on the root SVG
|
|
3512
|
+
element as `data-dashboard-version`. Directly closes the
|
|
3513
|
+
feedback_dash_zombie_port_3000.md memory rule: "verify ships
|
|
3514
|
+
via SVG DOM, not tmux 'Ready' — zombie next-servers + stale
|
|
3515
|
+
global installs silently serve old code". Pre-R462 the only
|
|
3516
|
+
ways to know which preview the dash was serving were:
|
|
3517
|
+
1. parse the npm registry for the latest tag (network)
|
|
3518
|
+
2. fetch /api/dashboard/version (API surface, no DOM)
|
|
3519
|
+
3. inspect the /login footer or /settings page (off-route)
|
|
3520
|
+
Test scripts that probe TopoGraph DOM (overlap, group-label
|
|
3521
|
+
tint, pip strip, etc.) couldn't tell whether the dash was
|
|
3522
|
+
actually serving the build they expected to verify. R462
|
|
3523
|
+
threads DASHBOARD_VERSION through to the root <svg> so:
|
|
3524
|
+
- Playwright probes can read svg[data-dashboard-version]
|
|
3525
|
+
directly + fail-fast on stale-build mismatch
|
|
3526
|
+
- the memory rule's manual zombie check ("inspect SVG
|
|
3527
|
+
dom") becomes a one-attr probe
|
|
3528
|
+
- operators DOM-inspect to confirm the live version
|
|
3529
|
+
matches the npm tag without leaving the topology page
|
|
3530
|
+
Geometry/visual impact: ZERO (data-* attrs don't paint).
|
|
3531
|
+
The version string is build-time injected via the existing
|
|
3532
|
+
DASHBOARD_VERSION constant (R51 footer + R51 settings page
|
|
3533
|
+
already consume it from app/lib/version.ts → reads
|
|
3534
|
+
package.json pkg.version). No business logic added. */
|
|
3535
|
+
data-dashboard-version={DASHBOARD_VERSION}
|
|
3460
3536
|
onPointerDown={onPointerDown}
|
|
3461
3537
|
onPointerMove={onPointerMove}
|
|
3462
3538
|
onPointerUp={onPointerUp}
|
|
@@ -4241,7 +4317,29 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4241
4317
|
y={box.y}
|
|
4242
4318
|
width={box.w}
|
|
4243
4319
|
height={box.h}
|
|
4244
|
-
rx
|
|
4320
|
+
/* Round 464 / Loop: group-box rx 14 → 16 on isPinned.
|
|
4321
|
+
Geometric softening at the corner radius — locked
|
|
4322
|
+
groups read with subtly rounder shoulders than
|
|
4323
|
+
hovered/idle. +2px reads as a calm \"settled in\"
|
|
4324
|
+
posture (subtler than a fill or stroke bump but
|
|
4325
|
+
unmistakable across the whole cluster boundary).
|
|
4326
|
+
Pin signature on the group-box rect now spans 7
|
|
4327
|
+
axes:
|
|
4328
|
+
R63 text fill brighten
|
|
4329
|
+
R142 drop-shadow filter
|
|
4330
|
+
R432 text letter-spacing 0→0.5
|
|
4331
|
+
R444 count tspan fw 500→600
|
|
4332
|
+
R457 parent text fw 700→800
|
|
4333
|
+
codex p.125 text opacity 0.55→1
|
|
4334
|
+
R464 corner rx 14→16 (this round)
|
|
4335
|
+
transition list (R461) already covers x/y/width/
|
|
4336
|
+
height 200ms ease-out; appended `rx 200ms ease-
|
|
4337
|
+
out` so the rounding eases alongside the geometry
|
|
4338
|
+
axes. SVG2 CSS animation on rx: Chrome 95+ /
|
|
4339
|
+
Safari 16+ / FF 70+ (same matrix as x/y/w/h).
|
|
4340
|
+
data-group-box-rx exposes the resolved value. */
|
|
4341
|
+
rx={isPinned ? '16' : '14'}
|
|
4342
|
+
data-group-box-rx={isPinned ? '16' : '14'}
|
|
4245
4343
|
fill={isLight ? '#0f172a' : '#a5b4fc'}
|
|
4246
4344
|
// R68: 3-tier opacity + stroke ladder.
|
|
4247
4345
|
// pinned → fill 0.08 / 0.13, stroke 3 px (locked)
|
|
@@ -4281,6 +4379,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4281
4379
|
data-group-box-pinned={isPinned ? 'true' : 'false'}
|
|
4282
4380
|
data-group-box-linecap="round"
|
|
4283
4381
|
data-group-box-linejoin="round"
|
|
4382
|
+
data-group-box-geom-transition="x,y,width,height"
|
|
4284
4383
|
// R85: ambient "marching ants" drift on the perimeter
|
|
4285
4384
|
// when this group has at least one working member, and
|
|
4286
4385
|
// neither pin nor hover is active (those treatments
|
|
@@ -4318,8 +4417,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4318
4417
|
while stroke / fill-opacity / filter all eased.
|
|
4319
4418
|
Closes the last theme-toggle snap on the group
|
|
4320
4419
|
box surface — same idiom R246 + R247 used at
|
|
4321
|
-
per-node label-card and side-panel scopes.
|
|
4322
|
-
|
|
4420
|
+
per-node label-card and side-panel scopes.
|
|
4421
|
+
Round 461 / Loop: extend the transition list to
|
|
4422
|
+
all 4 geometry axes (x, y, width, height) so
|
|
4423
|
+
when a cluster grows / shrinks (member joins,
|
|
4424
|
+
leaves, prefix rebalance, dense toggle, status
|
|
4425
|
+
flip) the BIG outer container slides into the
|
|
4426
|
+
new bounds at the same 200ms cadence the R460
|
|
4427
|
+
inner hitbox tint rect now uses. Pre-R461 the
|
|
4428
|
+
outer 200×140 px box snap-jumped on cluster
|
|
4429
|
+
resize while the inner 160×18 hitbox slid —
|
|
4430
|
+
jarring two-rate motion at the same surface.
|
|
4431
|
+
R461 unifies both rects to slide as one, with
|
|
4432
|
+
the parent box driving the visual envelope and
|
|
4433
|
+
the inner hitbox tracking the bottom-edge tint.
|
|
4434
|
+
Hero D #147 motion-coherence at the FULL cluster
|
|
4435
|
+
container tier (not just the label tint).
|
|
4436
|
+
data-group-box-geom-transition attr exposed. */
|
|
4437
|
+
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',
|
|
4323
4438
|
pointerEvents: 'none',
|
|
4324
4439
|
// CSS var consumed by `.anet-topo-groupbox-live`
|
|
4325
4440
|
// (line 877 of globals.css). React's CSSProperties
|
|
@@ -4397,27 +4512,83 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4397
4512
|
].filter(Boolean).join('\n')}</title>
|
|
4398
4513
|
);
|
|
4399
4514
|
})()}
|
|
4515
|
+
{/* v0.11.0 #147 Hero D — Vincent 5401: "太大太丑,
|
|
4516
|
+
都放到框的右下角的小字". First-cut bottom-right
|
|
4517
|
+
placement collided with bottom-row nodes (cluster
|
|
4518
|
+
geometry has no bottom padding; only GROUP_TOP=12
|
|
4519
|
+
top band). Pivoted to Option C from #147 spec:
|
|
4520
|
+
keep top-left anchor BUT shrink fontSize (13 → 9)
|
|
4521
|
+
and dim default opacity (1 → 0.55, hover/pin
|
|
4522
|
+
restore to 1). Satisfies "太大太丑" via the size +
|
|
4523
|
+
opacity axes while keeping the existing geometry
|
|
4524
|
+
contract that topo-overlap-test gates. Hitbox
|
|
4525
|
+
rect width tightens to min(box.w-12, 160) to
|
|
4526
|
+
track the narrower label render. */}
|
|
4527
|
+
{/* Round 465 / Loop — hitbox tint rect rx 4 → 5 on
|
|
4528
|
+
pinnedGroup match. Mirrors R464 (parent group-box
|
|
4529
|
+
rx 14 → 16 on isPinned) at the hitbox tier. The
|
|
4530
|
+
R460 hitbox carried fixed rx=4 since codex p.125
|
|
4531
|
+
pivoted it to the bottom-of-band position; the
|
|
4532
|
+
pin-state geometric softening was only on the BIG
|
|
4533
|
+
outer container, not the small hitbox underneath.
|
|
4534
|
+
R465 adds +1 px corner rounding on pin so the
|
|
4535
|
+
tint rect echoes the parent's locked posture at
|
|
4536
|
+
its own scale (8% relative bump matches R464's
|
|
4537
|
+
14→16 ≈ 14% scaled to the smaller rect).
|
|
4538
|
+
Transition list (R460 fill/opacity/x/width 200ms
|
|
4539
|
+
ease-out) extends to include `rx 200ms ease-out`
|
|
4540
|
+
so the rounding eases under the same cadence.
|
|
4541
|
+
SVG2 CSS animation on rx: Chrome 95+ / Safari
|
|
4542
|
+
16+ / FF 70+ (same matrix as x/y/w/h).
|
|
4543
|
+
data-group-label-tint-rx exposes the resolved
|
|
4544
|
+
value for tests. */}
|
|
4400
4545
|
<rect
|
|
4401
4546
|
x={box.x + 6}
|
|
4402
4547
|
y={box.y + 2}
|
|
4403
|
-
width={Math.min(box.w - 12,
|
|
4404
|
-
height={
|
|
4405
|
-
rx=
|
|
4406
|
-
|
|
4407
|
-
group labels — same idiom R104 added to recent-
|
|
4408
|
-
signal rows and R105 to the legend rows. The
|
|
4409
|
-
tint colour is pal.legendAccent (cyan) since
|
|
4410
|
-
groups don't carry an inherent swatch the way
|
|
4411
|
-
legend rows do; this matches R68's group-box
|
|
4412
|
-
isPinned/isHovered accent stroke for consistency.
|
|
4413
|
-
hover < pinned opacity so locked vs preview is
|
|
4414
|
-
discriminable at a glance. */
|
|
4548
|
+
width={Math.min(box.w - 12, 160)}
|
|
4549
|
+
height={18}
|
|
4550
|
+
rx={pinnedGroup === box.key ? '5' : '4'}
|
|
4551
|
+
data-group-label-tint-rx={pinnedGroup === box.key ? '5' : '4'}
|
|
4415
4552
|
fill={pinnedGroup === box.key || hoveredGroupLabel === box.key ? pal.legendAccent : 'transparent'}
|
|
4416
4553
|
opacity={pinnedGroup === box.key ? (isLight ? 0.16 : 0.20)
|
|
4417
4554
|
: hoveredGroupLabel === box.key ? (isLight ? 0.09 : 0.13)
|
|
4418
4555
|
: 1}
|
|
4419
4556
|
data-group-label-tinted={pinnedGroup === box.key ? 'pinned' : hoveredGroupLabel === box.key ? 'hover' : 'none'}
|
|
4420
|
-
|
|
4557
|
+
/* Round 459 / Loop — cadence-sync follow-on to codex
|
|
4558
|
+
preview.125 (Hero D #147). Codex's parent <text>
|
|
4559
|
+
transition list now reads:
|
|
4560
|
+
'fill 200ms, letter-spacing 200ms,
|
|
4561
|
+
font-weight 200ms, opacity 200ms'
|
|
4562
|
+
— 200ms ease-out across every axis. The label
|
|
4563
|
+
hitbox tint rect underneath was still at 150ms
|
|
4564
|
+
(legacy R107 cadence), so the tint snapped in
|
|
4565
|
+
50ms ahead of the parent label brightening —
|
|
4566
|
+
a small but perceivable mistimed cascade when
|
|
4567
|
+
hovering or clicking to pin a cluster. R459
|
|
4568
|
+
lifts both axes to 200ms to lock the tint
|
|
4569
|
+
under the label as one motion-coherent state
|
|
4570
|
+
flip. Hover/pin/unpin all feel as a single
|
|
4571
|
+
unified ease rather than "tint pops, label
|
|
4572
|
+
follows". data-group-label-tint-transition
|
|
4573
|
+
attr exposes the timing for tests. */
|
|
4574
|
+
/* Round 460 / Loop — extend the R459-200ms tint rect
|
|
4575
|
+
transition list to include `x` + `width` so the
|
|
4576
|
+
hitbox slides into place when a cluster grows or
|
|
4577
|
+
shrinks (member joins / leaves / status change
|
|
4578
|
+
re-pricing box.w). Pre-R460 every resize snap-
|
|
4579
|
+
jumped the hitbox bounds — a small but visible
|
|
4580
|
+
glitch right at the moment the operator's
|
|
4581
|
+
attention is on the cluster. SVG2 CSS animation
|
|
4582
|
+
on geometry attrs has shipped in Chrome 95+ /
|
|
4583
|
+
Safari 16+ / FF 70+; the runtime gracefully
|
|
4584
|
+
no-ops on older browsers. Sibling motion idiom
|
|
4585
|
+
to R134 / R141 / R142 (panel rect transitions)
|
|
4586
|
+
at the group-label hitbox tier.
|
|
4587
|
+
data-group-label-tint-geom-transition attr
|
|
4588
|
+
exposes the geometry-axis presence for tests. */
|
|
4589
|
+
data-group-label-tint-transition="200ms"
|
|
4590
|
+
data-group-label-tint-geom-transition="x,width,rx"
|
|
4591
|
+
style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out, x 200ms ease-out, width 200ms ease-out, rx 200ms ease-out' }}
|
|
4421
4592
|
/>
|
|
4422
4593
|
{/* Round 218 / Loop: group label gains a letter-spacing
|
|
4423
4594
|
transition on pin — the text subtly spaces out
|
|
@@ -4463,16 +4634,47 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4463
4634
|
R432 group label text (this round)
|
|
4464
4635
|
R218 transition list ('fill 200ms, letter-spacing
|
|
4465
4636
|
200ms') untouched — additive conditional case. */}
|
|
4637
|
+
{/* Round 457 / Loop: group label parent text fontWeight
|
|
4638
|
+
700 → 800 on isPinned. Adds typographic weight axis
|
|
4639
|
+
to the group-label parent text, sibling to R432
|
|
4640
|
+
letter-spacing tween at the same surface. Pre-R457
|
|
4641
|
+
pin lifted ls 0 → 0.5px (R218→R432 3-tier) but the
|
|
4642
|
+
fw stayed planted at R63's 700 — locked groups
|
|
4643
|
+
read as wider-but-same-weight. R457 adds the
|
|
4644
|
+
weight axis so pinned groups read as tightened
|
|
4645
|
+
AND wider, matching the R416/R424/R425/R426/R444/
|
|
4646
|
+
R445/R446 "data tightens under attention" idiom
|
|
4647
|
+
(now extended to the parent-text scope at the
|
|
4648
|
+
group-label tier). R63 fill brighten + R432
|
|
4649
|
+
letter-spacing 0/0.25/0.5 3-tier + R55 transition
|
|
4650
|
+
list all preserved; extends to include 'font-
|
|
4651
|
+
weight 200ms ease-out' so the bump eases under
|
|
4652
|
+
the same cadence. */}
|
|
4653
|
+
{/* v0.11.0 #147 Hero D — Vincent 5401 ask: "dash 网络
|
|
4654
|
+
图里面这个工程的名字也太大了, 超级丑". Per Vincent
|
|
4655
|
+
screenshot 实测. Initial attempt moved label to
|
|
4656
|
+
bottom-right (#147 spec Option A); topo-overlap-test
|
|
4657
|
+
caught 7 grid collisions because cluster boxes have
|
|
4658
|
+
no bottom padding. Pivot to Option C: keep top-left
|
|
4659
|
+
anchor, shrink fontSize 13 → 9 (-31%, watermark
|
|
4660
|
+
register), dim default opacity 1 → 0.55 (hover/pin
|
|
4661
|
+
restore to 1). Net Twitter-grok improvement:
|
|
4662
|
+
cluster labels no longer dominate the canvas at
|
|
4663
|
+
rest; operator still hovers to find specific groups.
|
|
4664
|
+
Position unchanged to preserve the existing
|
|
4665
|
+
geometry that overlap-test gates. */}
|
|
4466
4666
|
<text
|
|
4467
4667
|
x={box.x + 12}
|
|
4468
|
-
y={box.y +
|
|
4668
|
+
y={box.y + 12}
|
|
4469
4669
|
fill={isHovered ? pal.legendHeadline : pal.legendText}
|
|
4470
|
-
fontSize="
|
|
4670
|
+
fontSize="9"
|
|
4471
4671
|
fontFamily="monospace"
|
|
4472
|
-
fontWeight=
|
|
4672
|
+
fontWeight={isPinned ? '800' : '700'}
|
|
4673
|
+
opacity={isPinned || isHovered ? 1 : 0.55}
|
|
4473
4674
|
data-group-label-hovered={isHovered && !isPinned ? 'true' : 'false'}
|
|
4675
|
+
data-group-label-font-weight={isPinned ? '800' : '700'}
|
|
4474
4676
|
style={{
|
|
4475
|
-
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',
|
|
4677
|
+
transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out, font-weight 200ms ease-out, opacity 200ms ease-out',
|
|
4476
4678
|
letterSpacing: isPinned ? '0.5px' :
|
|
4477
4679
|
isHovered ? '0.25px' : '0px',
|
|
4478
4680
|
}}
|
|
@@ -4528,14 +4730,47 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4528
4730
|
fill-inherit from parent label (hover-deepen-own-
|
|
4529
4731
|
hue family) preserved. data-group-label-count-
|
|
4530
4732
|
font-weight attr exposes the value for tests. */}
|
|
4733
|
+
{/* Round 444 / Loop: group label count tspan
|
|
4734
|
+
fontWeight 500 → 600 on isPinned. Extends the
|
|
4735
|
+
"data tightens under attention" typographic-
|
|
4736
|
+
weight pattern to a 5th anchor at the group-
|
|
4737
|
+
label-count scope:
|
|
4738
|
+
R416 chip-digit (chip hover)
|
|
4739
|
+
R424 panel-digit (panel hover)
|
|
4740
|
+
R425 hub-digit (hub hover)
|
|
4741
|
+
R426 edge-badge-digit (pin/hot)
|
|
4742
|
+
R444 group-label-count (pinned) ← this round
|
|
4743
|
+
Same idiom — when the group is locked, its
|
|
4744
|
+
member-count tightens typographically alongside
|
|
4745
|
+
the R432 letter-spacing spread (0 → 0.5px) on
|
|
4746
|
+
the parent label. Hover keeps rest fw (500) so
|
|
4747
|
+
the locked vs preview distinction at the type
|
|
4748
|
+
level stays intact — same gate R432 used.
|
|
4749
|
+
Monospace + R225 tabular-nums lock the digit
|
|
4750
|
+
width across fw changes; bbox unchanged; overlap-
|
|
4751
|
+
test invariants hold. transition list adds
|
|
4752
|
+
'font-weight 200ms ease-out' matching R432
|
|
4753
|
+
letter-spacing cadence. R229 fill-inherit
|
|
4754
|
+
preserved (parent text fill still drives the
|
|
4755
|
+
hover/pin color). data-group-label-count-font-
|
|
4756
|
+
weight + -pinned attrs exposed for tests. */}
|
|
4757
|
+
{/* v0.11.0 #147 — count tspan tracks parent fontSize:
|
|
4758
|
+
11 → 8 to match the new 9px label scale (parent
|
|
4759
|
+
dropped 13 → 9 with same -2px gap to the count
|
|
4760
|
+
suffix). dx="4" replaces dx="6" — the smaller
|
|
4761
|
+
glyph baseline doesn't need the wider gutter. */}
|
|
4531
4762
|
<tspan
|
|
4532
|
-
dx="
|
|
4533
|
-
fontSize="
|
|
4534
|
-
fontWeight=
|
|
4763
|
+
dx="4"
|
|
4764
|
+
fontSize="8"
|
|
4765
|
+
fontWeight={isPinned ? '600' : '500'}
|
|
4535
4766
|
data-group-label-count={box.key}
|
|
4536
4767
|
data-group-label-count-value={box.count}
|
|
4537
|
-
data-group-label-count-
|
|
4538
|
-
|
|
4768
|
+
data-group-label-count-pinned={isPinned ? 'true' : 'false'}
|
|
4769
|
+
data-group-label-count-font-weight={isPinned ? '600' : '500'}
|
|
4770
|
+
style={{
|
|
4771
|
+
fontVariantNumeric: 'tabular-nums',
|
|
4772
|
+
transition: 'font-weight 200ms ease-out',
|
|
4773
|
+
}}
|
|
4539
4774
|
>· {box.count}</tspan>
|
|
4540
4775
|
{/* Round 58 / Loop: status mix pip strip. Compact text-
|
|
4541
4776
|
based chips (e.g. "2w 1i") so the strip stays inside
|
|
@@ -4592,11 +4827,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4592
4827
|
breakdown. Multi-tier groups (e.g. `alpha · 3
|
|
4593
4828
|
2w 1i`) render unchanged — those pips genuinely
|
|
4594
4829
|
add breakdown info that the total doesn't carry. */}
|
|
4830
|
+
{/* Round 458 / Loop — Hero D #147 finishing polish on top of
|
|
4831
|
+
N站牛/codex preview.125 (Option C: top-left label fontSize
|
|
4832
|
+
13→9 + opacity 0.55 rest / 1 hover+pin, count tspan 11→8).
|
|
4833
|
+
That ship left the 3 status pips at fontSize=11 — visibly
|
|
4834
|
+
DOMINATING the now-9px parent label they trail. Result on
|
|
4835
|
+
a 5-member cluster: `alpha · 5 3w 2i` renders inside-out
|
|
4836
|
+
as "tiny name + tiny count + BIG bright pips" rather than
|
|
4837
|
+
a coherent right-tail of metadata. R458 scales the 3 pips
|
|
4838
|
+
to fontSize=8 (matches count tspan) and tightens dx 8/4/4
|
|
4839
|
+
→ 6/3/3 (gutter ratio 0.73/0.36 glyph-widths @ 11px ≈
|
|
4840
|
+
0.75/0.38 glyph-widths @ 8px — same visual rhythm at the
|
|
4841
|
+
smaller scale). The whole group-label bottom-right strip
|
|
4842
|
+
now reads as a unified 9/8/8/8 typographic ladder:
|
|
4843
|
+
name (parent <text>) fontSize 9 fw 700/800
|
|
4844
|
+
· count (1st tspan) fontSize 8 fw 500/600
|
|
4845
|
+
Nw (2nd tspan) fontSize 8 fw 600
|
|
4846
|
+
Ni (3rd tspan) fontSize 8 fw 600
|
|
4847
|
+
No (4th tspan) fontSize 8 fw 600
|
|
4848
|
+
Closes Vincent /goal 5401 ("太大太丑") at the pip-strip
|
|
4849
|
+
tier; with codex preview.125 the spec is fully realized.
|
|
4850
|
+
Geometry-only attribute changes — bbox tightens slightly
|
|
4851
|
+
(8px chars vs 11px chars stay inside the original 240px
|
|
4852
|
+
hitbox max) so topo-overlap-test invariants hold.
|
|
4853
|
+
tabular-nums + anet-fade-in + theme-eased fill 200ms
|
|
4854
|
+
preserved on every tspan. */}
|
|
4595
4855
|
{box.statuses.working > 0 && box.statuses.working !== box.count && (
|
|
4596
4856
|
<tspan
|
|
4597
|
-
dx="
|
|
4857
|
+
dx="6"
|
|
4598
4858
|
fill={isLight ? '#059669' : '#22c55e'}
|
|
4599
|
-
fontSize="
|
|
4859
|
+
fontSize="8"
|
|
4600
4860
|
fontWeight="600"
|
|
4601
4861
|
className="anet-fade-in"
|
|
4602
4862
|
data-group-pip="working"
|
|
@@ -4605,9 +4865,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4605
4865
|
)}
|
|
4606
4866
|
{box.statuses.idle > 0 && box.statuses.idle !== box.count && (
|
|
4607
4867
|
<tspan
|
|
4608
|
-
dx="
|
|
4868
|
+
dx="3"
|
|
4609
4869
|
fill={isLight ? '#0d9488' : '#2dd4bf'}
|
|
4610
|
-
fontSize="
|
|
4870
|
+
fontSize="8"
|
|
4611
4871
|
fontWeight="600"
|
|
4612
4872
|
className="anet-fade-in"
|
|
4613
4873
|
data-group-pip="idle"
|
|
@@ -4616,9 +4876,9 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4616
4876
|
)}
|
|
4617
4877
|
{box.statuses.offline > 0 && box.statuses.offline !== box.count && (
|
|
4618
4878
|
<tspan
|
|
4619
|
-
dx="
|
|
4879
|
+
dx="3"
|
|
4620
4880
|
fill={isLight ? '#94a3b8' : '#6b7280'}
|
|
4621
|
-
fontSize="
|
|
4881
|
+
fontSize="8"
|
|
4622
4882
|
fontWeight="600"
|
|
4623
4883
|
className="anet-fade-in"
|
|
4624
4884
|
data-group-pip="offline"
|
|
@@ -4972,25 +5232,29 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4972
5232
|
all preserved. data-edge-particle-radius attr
|
|
4973
5233
|
exposes the value for tests. */
|
|
4974
5234
|
<circle
|
|
4975
|
-
|
|
5235
|
+
/* Round 439 / Loop: edge flow particle radius hover
|
|
5236
|
+
lift — r 4.5 → 5.5 on (isHoveredEdge ||
|
|
5237
|
+
isEndpointHoveredEdge). Continues edge paint-
|
|
5238
|
+
layer parity arc (R436 visible path sw / R437
|
|
5239
|
+
flow-rail sw / R439 particle r) so the whole
|
|
5240
|
+
edge surface — including the moving particle —
|
|
5241
|
+
lifts on hover, not just the static stripes.
|
|
5242
|
+
+1px radius gives ~50% area boost. Subtler than
|
|
5243
|
+
1.4× sw bump on visible path because the
|
|
5244
|
+
particle is already small + motion-bright;
|
|
5245
|
+
+1px reads as "the dot caught attention"
|
|
5246
|
+
without overshadowing the path lift. R252
|
|
5247
|
+
transition list extends to include r 200ms so
|
|
5248
|
+
the size change eases under the same fill/
|
|
5249
|
+
opacity cadence. */
|
|
5250
|
+
r={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}
|
|
4976
5251
|
fill={pal.flowParticle}
|
|
4977
5252
|
filter={isLight ? undefined : 'url(#topo-glow)'}
|
|
4978
5253
|
opacity={Math.min(1, fresh * edgeOpacityMul)}
|
|
4979
5254
|
data-edge-particle={link.key}
|
|
4980
|
-
data-edge-particle-radius=
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
Pre-R252 pal.flowParticle (cyber #fef08a yellow
|
|
4984
|
-
↔ light #f59e0b amber) snapped on theme toggle
|
|
4985
|
-
while every other edge element eased (R245
|
|
4986
|
-
paths, R251 badge, R242 chat-target, R233
|
|
4987
|
-
endpoint ring). opacity is freshness-driven so
|
|
4988
|
-
it transitions per-frame as fresh decays anyway
|
|
4989
|
-
— but adding opacity to the explicit transition
|
|
4990
|
-
list also covers theme toggle (R3 className
|
|
4991
|
-
transition-opacity duration-300 was previously
|
|
4992
|
-
absent on this circle). */
|
|
4993
|
-
style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out' }}
|
|
5255
|
+
data-edge-particle-radius={(isHoveredEdge || isEndpointHoveredEdge) ? 5.5 : 4.5}
|
|
5256
|
+
data-edge-particle-lifted={(isHoveredEdge || isEndpointHoveredEdge) ? 'true' : 'false'}
|
|
5257
|
+
style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out, r 200ms ease-out' }}
|
|
4994
5258
|
>
|
|
4995
5259
|
<animateMotion
|
|
4996
5260
|
dur={`${duration}s`}
|
|
@@ -5722,13 +5986,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5722
5986
|
// Mode='spline' + R245 ease-in-out keySplines all
|
|
5723
5987
|
// preserved. data-topo-hub-halo-radius attr exposes
|
|
5724
5988
|
// value for tests.
|
|
5989
|
+
/* Round 451 / Loop: hub center halo radius lift on
|
|
5990
|
+
hoveredHub — r 20 → 22 (+2px, ~21% area). Adds another
|
|
5991
|
+
geometric axis to the hub-hover signature stack
|
|
5992
|
+
alongside R177 ring radius lift + R209 digit scale +
|
|
5993
|
+
R425 digit fw + R370 halo opacity + R386 highlight
|
|
5994
|
+
opacity + R441 core fill chroma. Pre-R451 the halo
|
|
5995
|
+
r stayed planted at R408's 20px while the rest of
|
|
5996
|
+
the hub structure responded to hover. R451 makes
|
|
5997
|
+
the halo breath outward on hover so the focal pulse
|
|
5998
|
+
intensifies under attention. SMIL `<animate>` on
|
|
5999
|
+
opacity continues independently (animateAttr=
|
|
6000
|
+
'opacity' vs CSS-property r — non-conflicting). R408
|
|
6001
|
+
base radius 20 preserved as rest; +2 hover delta
|
|
6002
|
+
keeps clearance from the R177 hub-hover-ring at
|
|
6003
|
+
r=17 hover (halo is BEHIND the ring, halo r=22 sits
|
|
6004
|
+
5px beyond the ring's hover-r=17, still well within
|
|
6005
|
+
the hub canvas envelope). data-topo-hub-halo-radius
|
|
6006
|
+
attr now reports the dynamic value. */
|
|
6007
|
+
const isHaloHovered = !reducedMotion && hoveredHub;
|
|
6008
|
+
const haloR = isHaloHovered ? 22 : 20;
|
|
5725
6009
|
return (
|
|
5726
6010
|
<circle
|
|
5727
|
-
cx={cx} cy={cy}
|
|
6011
|
+
cx={cx} cy={cy}
|
|
5728
6012
|
fill={isLight ? '#d1fae5' : '#10b981'}
|
|
5729
6013
|
opacity={isLight ? 0.42 : 0.12}
|
|
5730
6014
|
data-hub-busyness={busy}
|
|
5731
|
-
data-topo-hub-halo-radius=
|
|
6015
|
+
data-topo-hub-halo-radius={haloR}
|
|
6016
|
+
data-topo-hub-halo-hovered={isHaloHovered ? 'true' : 'false'}
|
|
5732
6017
|
data-topo-hub-halo-trough={isLight ? troughLight : troughDark}
|
|
5733
6018
|
data-topo-hub-halo-peak={isLight ? peakLight : peakDark}
|
|
5734
6019
|
/* Round 253 / Loop: hub grounding halo fill transition
|
|
@@ -5737,8 +6022,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5737
6022
|
animate on opacity continued running. CSS fill
|
|
5738
6023
|
transition is independent of the SMIL animate
|
|
5739
6024
|
(different attributes), so they compose without
|
|
5740
|
-
conflict.
|
|
5741
|
-
|
|
6025
|
+
conflict.
|
|
6026
|
+
R451: r as CSS property (R197/R198 idiom) so the
|
|
6027
|
+
hover-radius tween eases smoothly under the same
|
|
6028
|
+
200ms cadence as fill. */
|
|
6029
|
+
style={{
|
|
6030
|
+
r: `${haloR}px`,
|
|
6031
|
+
transition: 'fill 200ms ease-out, r 200ms ease-out',
|
|
6032
|
+
} as React.CSSProperties}
|
|
5742
6033
|
>
|
|
5743
6034
|
{/* Round 244 / Loop: hub grounding halo breath gets
|
|
5744
6035
|
ease-in-out keySplines, matching the active-node
|
|
@@ -5783,12 +6074,45 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
5783
6074
|
attr added for test introspection (the parent <g> at
|
|
5784
6075
|
line 3587 has data-topo-hub but the core specifically
|
|
5785
6076
|
is the canvas anchor). */}
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
6077
|
+
{(() => {
|
|
6078
|
+
/* Round 441 / Loop: hub center core fill brighten on
|
|
6079
|
+
hoveredHub. Pre-R441 the core was static (cyber
|
|
6080
|
+
emerald-500 #10b981 / light emerald-600 #059669) and
|
|
6081
|
+
the hub-hover gesture lifted ring radius (R177) +
|
|
6082
|
+
digit scale (R209) + digit fw (R425) + halo opacity
|
|
6083
|
+
(R370) + highlight opacity (R386) but the focal core
|
|
6084
|
+
ITSELF stayed planted at rest tone. R441 shifts the
|
|
6085
|
+
fill one emerald tier brighter on hover so the canvas
|
|
6086
|
+
anchor itself responds:
|
|
6087
|
+
cyber emerald-500 → emerald-400 (#10b981 → #34d399)
|
|
6088
|
+
light emerald-600 → emerald-500 (#059669 → #10b981)
|
|
6089
|
+
Same +100 step on the emerald scale across both themes.
|
|
6090
|
+
Pure paint axis; no geometry change. R248 fill 200ms
|
|
6091
|
+
transition already in the style list eases the shift.
|
|
6092
|
+
Closes the chroma axis on the hub-hover gesture stack:
|
|
6093
|
+
R177 ring radius lift geometry
|
|
6094
|
+
R209 digit scale 1.08 geometry
|
|
6095
|
+
R425 digit fw 700 → 800 typography
|
|
6096
|
+
R370 halo opacity 0.7 → 0.8 paint
|
|
6097
|
+
R386 highlight opacity paint
|
|
6098
|
+
R441 core fill brighten chroma ← this round
|
|
6099
|
+
data-topo-hub-core-hovered + -fill attrs exposed
|
|
6100
|
+
for tests. */
|
|
6101
|
+
const isCoreHovered = !reducedMotion && hoveredHub;
|
|
6102
|
+
const coreFill = isLight
|
|
6103
|
+
? (isCoreHovered ? '#10b981' : '#059669')
|
|
6104
|
+
: (isCoreHovered ? '#34d399' : '#10b981');
|
|
6105
|
+
return (
|
|
6106
|
+
<circle
|
|
6107
|
+
cx={cx} cy={cy} r="10"
|
|
6108
|
+
fill={coreFill}
|
|
6109
|
+
data-topo-hub-core
|
|
6110
|
+
data-topo-hub-core-hovered={isCoreHovered ? 'true' : 'false'}
|
|
6111
|
+
data-topo-hub-core-fill={coreFill}
|
|
6112
|
+
style={{ transition: 'fill 200ms ease-out' }}
|
|
6113
|
+
/>
|
|
6114
|
+
);
|
|
6115
|
+
})()}
|
|
5792
6116
|
{/* R130 / Loop: when workingCount > 0, the decorative inner
|
|
5793
6117
|
highlight gets replaced with the workingCount digit. The
|
|
5794
6118
|
R84 busyness breath already encodes the same metric
|
|
@@ -6570,13 +6894,77 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6570
6894
|
tion list ('fill,opacity' 300ms ease-out) unchanged.
|
|
6571
6895
|
data-node-halo-offline-opacity attr exposes the
|
|
6572
6896
|
resolved value for tests. */}
|
|
6897
|
+
{(() => {
|
|
6898
|
+
/* Round 440 / Loop: node halo opacity hover lift —
|
|
6899
|
+
lifts toward full on the matched node. Pure paint
|
|
6900
|
+
axis: rest values unchanged for un-hovered halos,
|
|
6901
|
+
hover state lifts the matched halo's alpha by
|
|
6902
|
+
+0.15 on each tier:
|
|
6903
|
+
online cyber 0.65 → 0.80
|
|
6904
|
+
online light 0.85 → 1.00 (capped)
|
|
6905
|
+
offline cyber 0.30 → 0.45
|
|
6906
|
+
offline light 0.45 → 0.60
|
|
6907
|
+
Same paint-only mental model as R430 hub-spoke
|
|
6908
|
+
opacity lift + R429 label-card body opacity lift,
|
|
6909
|
+
now at the per-node halo scope. No geometry
|
|
6910
|
+
change so R51 sentinels stay safe and the overlap-
|
|
6911
|
+
test invariant is unchanged (test runs at rest).
|
|
6912
|
+
Closes a chroma/presence axis on the per-node
|
|
6913
|
+
hover signature alongside the 12-layer cue stack
|
|
6914
|
+
(R26/R217/R142/R427/R428/R429 card + R430/R435/
|
|
6915
|
+
R436/R437/R94 link + R438 ring). R407 offline
|
|
6916
|
+
halo opacity floor (cyber 0.30 / light 0.45) is
|
|
6917
|
+
the rest branch unchanged. Existing transition-
|
|
6918
|
+
[fill,opacity] duration-300 className handles
|
|
6919
|
+
the easing. data-node-halo-hovered exposes the
|
|
6920
|
+
gate; data-node-halo-resolved-opacity exposes
|
|
6921
|
+
the four-state resolved value for tests. */
|
|
6922
|
+
const isHaloHovered = !reducedMotion && hoveredAlias === session.alias;
|
|
6923
|
+
/* Round 456 / Loop: light-theme offline node halo
|
|
6924
|
+
rest opacity 0.45 → 0.50. Stale-state legibility
|
|
6925
|
+
lift family extension (10th anchor) at the per-
|
|
6926
|
+
node halo light-theme scope:
|
|
6927
|
+
R317 subordinate-text gray-500 → gray-400
|
|
6928
|
+
R358 freshness floor 0.25 → 0.30
|
|
6929
|
+
R372 minimap offline-dot 0.5 → 0.6
|
|
6930
|
+
R404 hub-halo cyber trough 0.08 → 0.10
|
|
6931
|
+
R405 hub-halo light trough 0.32 → 0.34
|
|
6932
|
+
R406 edge freshness floor 0.35 → 0.40
|
|
6933
|
+
R407 node halo offline opacity
|
|
6934
|
+
cyber 0.25 → 0.30
|
|
6935
|
+
light 0.4 → 0.45
|
|
6936
|
+
R419 hub-spoke idle 0.45 → 0.50
|
|
6937
|
+
R452 dense alias rest 0.9 → 0.95
|
|
6938
|
+
R456 node halo offline LIGHT 0.45 → 0.50 ← this round
|
|
6939
|
+
Pre-R456 light-theme offline halo at 0.45 sat at
|
|
6940
|
+
the upper end of "near-floor" but read as soft-
|
|
6941
|
+
focus on the lighter canvas; +0.05 (~11 % opacity
|
|
6942
|
+
gain) lifts it to 0.50 — the midpoint between
|
|
6943
|
+
R407 rest 0.45 and R440 hover 0.60 — closing the
|
|
6944
|
+
gap so offline halos read more confidently as
|
|
6945
|
+
present-but-stale anchors. Cyber theme stays at
|
|
6946
|
+
R407's 0.30 (cyber backdrop is dark; the cyber
|
|
6947
|
+
offline halo against #080814 contains a stronger
|
|
6948
|
+
contrast envelope than light, so doesn't need
|
|
6949
|
+
the same lift). R440 hover 0.45→0.60 light + R12
|
|
6950
|
+
status.halo color + R407 transition list all
|
|
6951
|
+
preserved. */
|
|
6952
|
+
const haloOpacity = (() => {
|
|
6953
|
+
if (isOnline) {
|
|
6954
|
+
return isLight ? (isHaloHovered ? 1 : 0.85) : (isHaloHovered ? 0.80 : 0.65);
|
|
6955
|
+
}
|
|
6956
|
+
return isLight ? (isHaloHovered ? 0.60 : 0.50) : (isHaloHovered ? 0.45 : 0.30);
|
|
6957
|
+
})();
|
|
6958
|
+
return (
|
|
6573
6959
|
<circle
|
|
6574
6960
|
cx={pos.x}
|
|
6575
6961
|
cy={pos.y}
|
|
6576
6962
|
r={radius + 8}
|
|
6577
6963
|
fill={status.halo}
|
|
6578
|
-
opacity={
|
|
6964
|
+
opacity={haloOpacity}
|
|
6579
6965
|
data-node-halo-offline-opacity={isOnline ? undefined : (isLight ? 0.45 : 0.30)}
|
|
6966
|
+
data-node-halo-hovered={isHaloHovered ? 'true' : 'false'}
|
|
6967
|
+
data-node-halo-resolved-opacity={haloOpacity}
|
|
6580
6968
|
className="transition-[fill,opacity] duration-300 ease-out"
|
|
6581
6969
|
data-node-halo-breath={!reducedMotion && session.status === 'working' ? 'on' : 'off'}
|
|
6582
6970
|
data-node-halo-breath-offset={
|
|
@@ -6635,6 +7023,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6635
7023
|
/>
|
|
6636
7024
|
)}
|
|
6637
7025
|
</circle>
|
|
7026
|
+
);
|
|
7027
|
+
})()}
|
|
6638
7028
|
{/* Round 111 / Loop: edge-endpoint emphasis ring. R49
|
|
6639
7029
|
already keeps endpoint nodes at opacity 1 while
|
|
6640
7030
|
others dim when an edge is hovered, but the
|
|
@@ -6691,11 +7081,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6691
7081
|
ring stroke-width). data-edge-endpoint-ring-
|
|
6692
7082
|
stroke-width attr surfaces the chosen value for
|
|
6693
7083
|
test introspection. */
|
|
7084
|
+
/* Round 442 / Loop: endpoint emphasis ring radius
|
|
7085
|
+
hover lift — r=radius+7 → radius+8 on isEndpoint,
|
|
7086
|
+
closing a 3-axis hover-elevation parity at endpoint
|
|
7087
|
+
ring scope (r + sw + opacity):
|
|
7088
|
+
opacity R182 0 → 0.85/0.9
|
|
7089
|
+
sw R233 1.6 → 2.4
|
|
7090
|
+
r R442 +7 → +8 ← this round
|
|
7091
|
+
Mirrors the 3-axis trios already established at
|
|
7092
|
+
hub hover-ring (R177/R370/R385) and edge badge
|
|
7093
|
+
(R164/R394/R395). Pre-R442 the endpoint ring
|
|
7094
|
+
faded in + thickened on edge-hover but its radius
|
|
7095
|
+
stayed locked at radius+7 — only the paint/weight
|
|
7096
|
+
axes lifted while the GEOMETRY stayed unchanged.
|
|
7097
|
+
+1px (~radius+7 to radius+8) gives a subtle outward
|
|
7098
|
+
pulse on hover without crowding the status ring
|
|
7099
|
+
(which sits at radius from R438 sw3.5 hover) or
|
|
7100
|
+
the halo (radius+8 from R440 opacity hover — the
|
|
7101
|
+
endpoint ring sits at the SAME radius as the halo
|
|
7102
|
+
but with stroke=cyan vs fill=status.halo so they
|
|
7103
|
+
don't visually collide). The transition list
|
|
7104
|
+
extends to include 'r 180ms ease-out' so the new
|
|
7105
|
+
axis eases under the same R233 cadence. SVG `r`
|
|
7106
|
+
on a <circle> uses CSS-property syntax for inter-
|
|
7107
|
+
polation (same idiom R197/R198 used on the
|
|
7108
|
+
legend swatch). data-edge-endpoint-ring-radius
|
|
7109
|
+
attr exposes the resolved value for tests. */
|
|
7110
|
+
const endpointR = isEndpoint ? radius + 8 : radius + 7;
|
|
6694
7111
|
return (
|
|
6695
7112
|
<circle
|
|
6696
7113
|
cx={pos.x}
|
|
6697
7114
|
cy={pos.y}
|
|
6698
|
-
r={radius + 7}
|
|
6699
7115
|
fill="none"
|
|
6700
7116
|
stroke={pal.flowEdge}
|
|
6701
7117
|
strokeWidth={isEndpoint ? 2.4 : 1.6}
|
|
@@ -6703,7 +7119,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6703
7119
|
data-edge-endpoint-ring
|
|
6704
7120
|
data-edge-endpoint-active={isEndpoint ? 'true' : 'false'}
|
|
6705
7121
|
data-edge-endpoint-ring-stroke-width={isEndpoint ? 2.4 : 1.6}
|
|
6706
|
-
|
|
7122
|
+
data-edge-endpoint-ring-radius={endpointR}
|
|
7123
|
+
style={{
|
|
7124
|
+
pointerEvents: 'none',
|
|
7125
|
+
r: `${endpointR}px`,
|
|
7126
|
+
transition: 'opacity 180ms ease-out, stroke-width 180ms ease-out, r 180ms ease-out',
|
|
7127
|
+
} as React.CSSProperties}
|
|
6707
7128
|
/>
|
|
6708
7129
|
);
|
|
6709
7130
|
})()}
|
|
@@ -6963,8 +7384,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
6963
7384
|
transition: 'r 150ms ease-out, stroke-width 150ms ease-out',
|
|
6964
7385
|
} as React.CSSProperties}
|
|
6965
7386
|
/>
|
|
7387
|
+
{/* Round 443 / Loop: runtime badge inner-icon
|
|
7388
|
+
strokeWidth lift on node hover — 2.4 → 2.8 on
|
|
7389
|
+
isNodeActive. Pre-R443 the outer badge ring
|
|
7390
|
+
lifted (R208 r + sw both grow on hover) but
|
|
7391
|
+
the inner icon path stayed locked at sw=2.4.
|
|
7392
|
+
The two layers of the runtime badge were
|
|
7393
|
+
out of phase: ring thickened, icon stayed
|
|
7394
|
+
thin. R443 closes the 2-axis hover signature
|
|
7395
|
+
on the badge so both ring and icon lift
|
|
7396
|
+
together. +0.4 absolute delta matches the
|
|
7397
|
+
R208 ring's +0.5 sw delta (badge ring 1.5 →
|
|
7398
|
+
2.0 absolute), proportional to the icon's
|
|
7399
|
+
heavier base of 2.4. Pure paint axis;
|
|
7400
|
+
strokeLinecap='round' + strokeLinejoin='round'
|
|
7401
|
+
preserved. transition list extends to include
|
|
7402
|
+
'stroke-width 150ms ease-out' matching R208
|
|
7403
|
+
outer-ring cadence. data-runtime-badge-icon
|
|
7404
|
+
+ -active attrs exposed for tests. */}
|
|
6966
7405
|
<g transform={`translate(${bx - icon / 2} ${by - icon / 2}) scale(${icon / 24})`}>
|
|
6967
|
-
<path
|
|
7406
|
+
<path
|
|
7407
|
+
d={rt.iconPath}
|
|
7408
|
+
fill="none"
|
|
7409
|
+
stroke={rt.color}
|
|
7410
|
+
strokeWidth={isNodeActive ? '2.8' : '2.4'}
|
|
7411
|
+
strokeLinecap="round"
|
|
7412
|
+
strokeLinejoin="round"
|
|
7413
|
+
data-runtime-badge-icon={session.alias}
|
|
7414
|
+
data-runtime-badge-icon-active={isNodeActive ? 'true' : 'false'}
|
|
7415
|
+
data-runtime-badge-icon-stroke-width={isNodeActive ? '2.8' : '2.4'}
|
|
7416
|
+
style={{ transition: 'stroke-width 150ms ease-out' }}
|
|
7417
|
+
/>
|
|
6968
7418
|
</g>
|
|
6969
7419
|
</g>
|
|
6970
7420
|
);
|
|
@@ -7269,12 +7719,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7269
7719
|
hover scope. R211 fill 300ms transition
|
|
7270
7720
|
preserved (additive letter-spacing branch
|
|
7271
7721
|
+ appended 'letter-spacing 200ms ease-out'). */}
|
|
7722
|
+
{/* Round 448 / Loop: node sub-text fontWeight
|
|
7723
|
+
400 → 500 (font-medium). Sibling to R363
|
|
7724
|
+
(recent-row text fw 400→500) + R364 (legend-
|
|
7725
|
+
row label fw 400→500) — same "small mono
|
|
7726
|
+
text at fontSize=9-11 needs 500-tier weight
|
|
7727
|
+
for legibility" pattern, now applied to the
|
|
7728
|
+
per-node sub-text line. At fontSize=8-9
|
|
7729
|
+
monospace against the label-card chrome
|
|
7730
|
+
(pal.labelBox.fill cyber #020617 / light
|
|
7731
|
+
#ffffff), the default fw=400 sits at the
|
|
7732
|
+
legibility floor; fw=500 (font-medium) lifts
|
|
7733
|
+
it into a clearly readable band without
|
|
7734
|
+
changing geometry. R211 fill 300ms +
|
|
7735
|
+
R428 letter-spacing 0→0.2 hover + R427
|
|
7736
|
+
alias-text + R429 body opacity all preserved.
|
|
7737
|
+
Pure typography lift; no layout shift; the
|
|
7738
|
+
alias-text fw=700 (R427) still wins so the
|
|
7739
|
+
alias > status hierarchy holds at the type
|
|
7740
|
+
level. data-node-sub-text-font-weight attr
|
|
7741
|
+
exposes the value for tests. */}
|
|
7272
7742
|
<text
|
|
7273
7743
|
x="0" y={subY} textAnchor="middle"
|
|
7274
7744
|
fill={status.primary}
|
|
7275
7745
|
fontSize={subFs} fontFamily="monospace"
|
|
7746
|
+
fontWeight="500"
|
|
7276
7747
|
data-node-sub-text={session.alias}
|
|
7277
7748
|
data-node-sub-text-hovered={hoveredAlias === session.alias ? 'true' : 'false'}
|
|
7749
|
+
data-node-sub-text-font-weight="500"
|
|
7278
7750
|
style={{
|
|
7279
7751
|
transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',
|
|
7280
7752
|
letterSpacing: hoveredAlias === session.alias ? '0.2px' : '0px',
|
|
@@ -7301,6 +7773,26 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7301
7773
|
// CSS pseudo-class; only the transition-property
|
|
7302
7774
|
// moves to inline. Big fleets benefit most — this is
|
|
7303
7775
|
// the path users see when their dashboard is busiest.
|
|
7776
|
+
/* Round 452 / Loop: dense plain-text alias rest
|
|
7777
|
+
opacity 0.9 → 0.95. Closes the alpha gap on the
|
|
7778
|
+
dense fleet's per-node label, sibling to R449
|
|
7779
|
+
legend-count-active 0.95→1.0 and R450 minimap
|
|
7780
|
+
viewport rest 0.9→0.95 — same "close the
|
|
7781
|
+
active-presence alpha gap" idiom applied here
|
|
7782
|
+
to the dense-mode alias text at fontSize=9-10
|
|
7783
|
+
monospace. Pre-R452 dense aliases at α=0.9 sat
|
|
7784
|
+
just below full alpha; for un-hovered nodes in
|
|
7785
|
+
a busy >16-node fleet this is the only label
|
|
7786
|
+
readable, so the 10% alpha gap added a subtle
|
|
7787
|
+
"soft-focused chrome" feel where the labels
|
|
7788
|
+
should read as definitive. +0.05 lift makes
|
|
7789
|
+
them confidently present without erasing the
|
|
7790
|
+
status.text + R110 stroke halo + paintOrder
|
|
7791
|
+
layering. R26 group-hover translate + R212
|
|
7792
|
+
fill 300ms transition + R110 stroke=container-
|
|
7793
|
+
Bg halo all preserved. data-node-dense-alias-
|
|
7794
|
+
text-opacity attr exposes the resolved value
|
|
7795
|
+
for tests. */
|
|
7304
7796
|
<text
|
|
7305
7797
|
x={pos.x}
|
|
7306
7798
|
y={pos.y + radius + denseDrop}
|
|
@@ -7309,9 +7801,10 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
7309
7801
|
fontSize={denseFs}
|
|
7310
7802
|
fontFamily="monospace"
|
|
7311
7803
|
fontWeight="700"
|
|
7312
|
-
opacity={0.
|
|
7804
|
+
opacity={0.95}
|
|
7313
7805
|
className="group-hover:-translate-y-[1.5px]"
|
|
7314
7806
|
data-node-dense-alias-text={session.alias}
|
|
7807
|
+
data-node-dense-alias-text-opacity="0.95"
|
|
7315
7808
|
style={{
|
|
7316
7809
|
pointerEvents: 'none',
|
|
7317
7810
|
paintOrder: 'stroke',
|
|
@@ -8333,13 +8826,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8333
8826
|
R383 recent-row pip radius 1.8 → 2.0 (this round)
|
|
8334
8827
|
data-recent-row-freshness-radius attr
|
|
8335
8828
|
bumps to '2.0' for tests. */
|
|
8336
|
-
|
|
8829
|
+
/* Round 447 / Loop: recent-row freshness pip
|
|
8830
|
+
radius lift on (isRowHovered || isRowPinned)
|
|
8831
|
+
— r 2.0 → 2.5 (+0.5px, sibling to R442
|
|
8832
|
+
endpoint-ring r lift). Adds a geometric
|
|
8833
|
+
axis to the recent-row hover/pin gesture
|
|
8834
|
+
alongside R143 translateY + R104 row bg-
|
|
8835
|
+
tint + R434 letter-spacing + R445 count
|
|
8836
|
+
fw. Pre-R447 the pip stayed at r=2.0 always
|
|
8837
|
+
— the freshness alpha (R162) tracked
|
|
8838
|
+
recency but didn't telegraph "this row is
|
|
8839
|
+
in focus" geometrically. R447 lifts the
|
|
8840
|
+
pip outward by 25% area (π·2.5² / π·2.0²
|
|
8841
|
+
= 1.56) on attention, closing a 5-axis
|
|
8842
|
+
row-attention signature (geometry + paint
|
|
8843
|
+
+ typography + spacing + position).
|
|
8844
|
+
SVG `r` as CSS property for interpolation
|
|
8845
|
+
(R197/R198 idiom). transition list extends
|
|
8846
|
+
to include 'r 200ms ease-out' matching the
|
|
8847
|
+
opacity cadence. data-recent-row-freshness-
|
|
8848
|
+
lifted attr exposes the gate for tests. */
|
|
8337
8849
|
fill={pal.legendAccent}
|
|
8338
8850
|
opacity={alpha}
|
|
8339
8851
|
data-recent-row-freshness={link.key}
|
|
8340
8852
|
data-recent-row-freshness-alpha={alpha.toFixed(2)}
|
|
8341
|
-
data-recent-row-freshness-radius=
|
|
8342
|
-
|
|
8853
|
+
data-recent-row-freshness-radius={(isRowHovered || isRowPinned) ? 2.5 : 2.0}
|
|
8854
|
+
data-recent-row-freshness-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}
|
|
8855
|
+
style={{
|
|
8856
|
+
pointerEvents: 'none',
|
|
8857
|
+
r: `${(isRowHovered || isRowPinned) ? 2.5 : 2.0}px`,
|
|
8858
|
+
transition: 'opacity 200ms ease-out, r 200ms ease-out',
|
|
8859
|
+
} as React.CSSProperties}
|
|
8343
8860
|
/>
|
|
8344
8861
|
);
|
|
8345
8862
|
})()}
|
|
@@ -8486,13 +9003,36 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
8486
9003
|
distinct, plus the fill flip from legendText
|
|
8487
9004
|
→ amber carries the dramatic part of the cue).
|
|
8488
9005
|
Sibling treatment in the data-weight tier. */}
|
|
9006
|
+
{/* Round 445 / Loop: extend the R320 cold/hot fw
|
|
9007
|
+
binary (600/700) to ALSO fire on isRowPinned —
|
|
9008
|
+
pinned-cold now lifts to 700 alongside the
|
|
9009
|
+
existing hot-triggered lift. Sibling to R444
|
|
9010
|
+
group-label-count-pin (500→600) at the
|
|
9011
|
+
recent-row scope. Both panel-row counts now
|
|
9012
|
+
respond to pin with a typographic weight lift,
|
|
9013
|
+
part of the "data tightens under attention"
|
|
9014
|
+
family (R416/R424/R425/R426/R444/R445).
|
|
9015
|
+
Effective tiers:
|
|
9016
|
+
cold + un-pinned → fw 600
|
|
9017
|
+
cold + pinned → fw 700 ← this round
|
|
9018
|
+
hot (any pin state) → fw 700 (R320 preserved)
|
|
9019
|
+
hot is still amber-filled (R127); cold pin
|
|
9020
|
+
stays at the parent fill, so the two routes
|
|
9021
|
+
to fw=700 are visually distinct (color vs
|
|
9022
|
+
no color). transition list adds 'font-
|
|
9023
|
+
weight 200ms ease-out' so the lift eases
|
|
9024
|
+
under the same R320 fill cadence. data-
|
|
9025
|
+
recent-row-count-pinned attr exposes the
|
|
9026
|
+
pin gate for tests. */}
|
|
8489
9027
|
<tspan
|
|
8490
9028
|
fill={isHot ? hotStroke : undefined}
|
|
8491
|
-
fontWeight={isHot ? '700' : '600'}
|
|
9029
|
+
fontWeight={(isHot || isRowPinned) ? '700' : '600'}
|
|
8492
9030
|
data-recent-row-count
|
|
9031
|
+
data-recent-row-count-pinned={isRowPinned ? 'true' : 'false'}
|
|
9032
|
+
data-recent-row-count-font-weight={(isHot || isRowPinned) ? '700' : '600'}
|
|
8493
9033
|
{...(isHot ? { 'data-recent-row-count-hot': 'true' } : {})}
|
|
8494
9034
|
style={{
|
|
8495
|
-
transition: 'fill 300ms ease-out',
|
|
9035
|
+
transition: 'fill 300ms ease-out, font-weight 200ms ease-out',
|
|
8496
9036
|
fontVariantNumeric: 'tabular-nums',
|
|
8497
9037
|
}}
|
|
8498
9038
|
>
|
|
@@ -9318,20 +9858,60 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9318
9858
|
labels to single status words, the count
|
|
9319
9859
|
becomes proportionally more important; this
|
|
9320
9860
|
round emphasizes that role typographically. */}
|
|
9861
|
+
{/* Round 446 / Loop: legend per-row count fontWeight
|
|
9862
|
+
lift 600 → 700 on isPinned. Mirror of R444 group-
|
|
9863
|
+
label-count + R445 recent-row-count at the
|
|
9864
|
+
legend-row scope. Closes the 3-panel-row family
|
|
9865
|
+
for the "data tightens under attention" pattern —
|
|
9866
|
+
every panel-row count now responds to pin with a
|
|
9867
|
+
typographic-weight bump:
|
|
9868
|
+
R444 group-label-count 500 → 600
|
|
9869
|
+
R445 recent-row-count 600 → 700 (cold-pin route)
|
|
9870
|
+
R446 legend-row-count 600 → 700 ← this round
|
|
9871
|
+
Hover gate (hoveredStatus===row.key) keeps rest
|
|
9872
|
+
fw=600 so the locked-vs-preview distinction at
|
|
9873
|
+
the type level stays intact — same gate R433 used
|
|
9874
|
+
on the parent <text> letter-spacing tween. R309
|
|
9875
|
+
fw=600 baseline + R204 empty-row opacity dim +
|
|
9876
|
+
R225 tabular-nums all preserved. transition list
|
|
9877
|
+
extends to include 'font-weight 150ms ease-out'
|
|
9878
|
+
matching R433 fill/letter-spacing cadence.
|
|
9879
|
+
data-legend-count-pinned + -font-weight attrs
|
|
9880
|
+
exposed for tests. */}
|
|
9321
9881
|
<text
|
|
9322
9882
|
x="215" y={row.y1}
|
|
9323
9883
|
textAnchor="end"
|
|
9324
9884
|
fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? row.fill : pal.legendText}
|
|
9325
9885
|
fontSize="11"
|
|
9326
9886
|
fontFamily="monospace"
|
|
9327
|
-
fontWeight=
|
|
9887
|
+
fontWeight={isPinned ? '700' : '600'}
|
|
9888
|
+
/* Round 449 / Loop: legend-row count active-state
|
|
9889
|
+
opacity 0.95 → 1.0 on (hoveredStatus===row.key
|
|
9890
|
+
|| isPinned). Pre-R449 R204 lifted populated-row
|
|
9891
|
+
active opacity from rest 0.65 to 0.95 — visibly
|
|
9892
|
+
brighter but kept a 5 pct alpha gap (1 - 0.95).
|
|
9893
|
+
R449 closes the gap to 1.0 so the active count
|
|
9894
|
+
reads as confidently present alongside the R446
|
|
9895
|
+
fw=600→700 + R433 letter-spacing tween. Theme-
|
|
9896
|
+
consistency / canvas-presence family extension
|
|
9897
|
+
(7th anchor on the active-presence lift sub-
|
|
9898
|
+
family): R370 hub hover-ring 0.7→0.8, R371 edge-
|
|
9899
|
+
badge rest 0.82→0.85, R372 minimap offline-dot
|
|
9900
|
+
0.5→0.6, R386 hub-highlight idle 0.9→0.95, R387
|
|
9901
|
+
hover-detail panel 0.94→0.97, R429 label-card
|
|
9902
|
+
body 0.94→1.0, R449 legend-count active 0.95→1.0
|
|
9903
|
+
← this round. Empty-row opacity (R204: 0.28
|
|
9904
|
+
light / 0.30 cyber) and idle 0.65 rest both
|
|
9905
|
+
preserved. */
|
|
9328
9906
|
opacity={row.count === 0
|
|
9329
9907
|
? (isLight ? 0.28 : 0.30)
|
|
9330
|
-
: (hoveredStatus === row.key || isPinned ?
|
|
9908
|
+
: (hoveredStatus === row.key || isPinned ? 1 : 0.65)}
|
|
9331
9909
|
data-legend-count={row.key}
|
|
9332
9910
|
data-legend-count-empty={row.count === 0 ? 'true' : 'false'}
|
|
9911
|
+
data-legend-count-pinned={isPinned ? 'true' : 'false'}
|
|
9912
|
+
data-legend-count-font-weight={isPinned ? '700' : '600'}
|
|
9333
9913
|
data-legend-count-fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? 'tier' : 'neutral'}
|
|
9334
|
-
style={{ pointerEvents: 'none', transition: 'opacity 150ms ease-out, fill 150ms ease-out', fontVariantNumeric: 'tabular-nums' }}
|
|
9914
|
+
style={{ pointerEvents: 'none', transition: 'opacity 150ms ease-out, fill 150ms ease-out, font-weight 150ms ease-out', fontVariantNumeric: 'tabular-nums' }}
|
|
9335
9915
|
>{row.count}</text>
|
|
9336
9916
|
</g>
|
|
9337
9917
|
);
|
|
@@ -9718,7 +10298,29 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9718
10298
|
// R346: strokeWidth + opacity tween on container hover.
|
|
9719
10299
|
strokeWidth={hoveredMinimap ? '1.75' : '1.5'}
|
|
9720
10300
|
strokeLinejoin="round"
|
|
9721
|
-
|
|
10301
|
+
/* Round 450 / Loop · milestone: minimap viewport rest
|
|
10302
|
+
opacity 0.9 → 0.95. Closes half the alpha gap on
|
|
10303
|
+
the wayfinding indicator while preserving the
|
|
10304
|
+
R346 hover delta to 1.0. Pre-R450 the rest viewport
|
|
10305
|
+
sat at 0.9 (10 pct alpha gap) — adequate but
|
|
10306
|
+
under-confident for the user's primary "you are
|
|
10307
|
+
here" indicator on the minimap. R450 lifts to 0.95
|
|
10308
|
+
so the rest read is more present without erasing
|
|
10309
|
+
the hover lift cue (the +0.05 rest-to-hover delta
|
|
10310
|
+
is small but pairs with R346 sw 1.5→1.75 to keep
|
|
10311
|
+
hover clearly distinguishable). Sibling to R449
|
|
10312
|
+
legend-count active opacity 0.95→1.0 — same
|
|
10313
|
+
"close the active-presence alpha gap" idiom now
|
|
10314
|
+
applied to the REST tier of the wayfinding rect
|
|
10315
|
+
(the minimap viewport stays at canvas-presence
|
|
10316
|
+
register even when un-hovered since it's the
|
|
10317
|
+
spatial referent). Theme-consistency / canvas-
|
|
10318
|
+
presence family (8th anchor on the active-
|
|
10319
|
+
presence lift sub-arc).
|
|
10320
|
+
R287 strokeWidth=1.5 + R379 strokeLinejoin='round'
|
|
10321
|
+
+ R346 hover-state tweens + R393 rx=2 + R199
|
|
10322
|
+
smoothView x/y/w/h transition all preserved. */
|
|
10323
|
+
opacity={hoveredMinimap ? '1' : '0.95'}
|
|
9722
10324
|
data-topo-minimap-viewport
|
|
9723
10325
|
data-topo-minimap-viewport-rx="2"
|
|
9724
10326
|
data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}
|
|
@@ -9921,11 +10523,30 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
9921
10523
|
its own compositor layer for crisper edges during
|
|
9922
10524
|
the scale tween. Sibling change on zoom-in icon
|
|
9923
10525
|
below. */}
|
|
10526
|
+
{/* Round 454 / Loop: extend R453 chrome reset icon hover
|
|
10527
|
+
sw lift to zoom +/− icons via Tailwind arbitrary class
|
|
10528
|
+
group-hover:[stroke-width:2.8]. Chrome icon hover sw
|
|
10529
|
+
lift family now 5 anchors:
|
|
10530
|
+
R208 runtime badge outer ring 1.5 → 2
|
|
10531
|
+
R443 runtime badge inner icon 2.4 → 2.8
|
|
10532
|
+
R453 chrome reset icon 2.5 → 2.8
|
|
10533
|
+
R454 chrome zoom-out icon 2.5 → 2.8 ← this round
|
|
10534
|
+
R454 chrome zoom-in icon 2.5 → 2.8 ← this round
|
|
10535
|
+
Tailwind v4 arbitrary-value group-hover variant
|
|
10536
|
+
resolves [stroke-width:2.8] as a CSS property which
|
|
10537
|
+
overrides the static strokeWidth='2.5' attribute on
|
|
10538
|
+
hover. transition-[stroke-width] appended to the
|
|
10539
|
+
existing transition-transform list so the sw tween
|
|
10540
|
+
eases under the same 200ms cadence as R352 group-
|
|
10541
|
+
hover:scale-110. R186 anet-chrome-pop keyframe still
|
|
10542
|
+
owns transform during click via CSS-animation
|
|
10543
|
+
precedence over transition-transform. Sibling change
|
|
10544
|
+
on zoom-in icon below. */}
|
|
9924
10545
|
<svg
|
|
9925
10546
|
width="12" height="12" viewBox="0 0 24 24"
|
|
9926
10547
|
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
9927
10548
|
aria-hidden
|
|
9928
|
-
className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-out' ? ' anet-chrome-pop' : ''}`}
|
|
10549
|
+
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' : ''}`}
|
|
9929
10550
|
data-topo-chrome-zoom-out-icon
|
|
9930
10551
|
><path d="M5 12h14" /></svg>
|
|
9931
10552
|
</button>
|
|
@@ -10030,11 +10651,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10030
10651
|
{/* R352 sibling — zoom-in icon picks up the same
|
|
10031
10652
|
group-hover:scale-110 family. Mirror change at
|
|
10032
10653
|
the zoom-out icon above. */}
|
|
10654
|
+
{/* R454 sibling — zoom-in icon picks up the same
|
|
10655
|
+
group-hover:[stroke-width:2.8] family lift. */}
|
|
10033
10656
|
<svg
|
|
10034
10657
|
width="12" height="12" viewBox="0 0 24 24"
|
|
10035
10658
|
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
10036
10659
|
aria-hidden
|
|
10037
|
-
className={`transition-transform duration-200 ease-out group-hover:scale-110 transform-gpu${chromePopping === 'zoom-in' ? ' anet-chrome-pop' : ''}`}
|
|
10660
|
+
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' : ''}`}
|
|
10038
10661
|
data-topo-chrome-zoom-in-icon
|
|
10039
10662
|
><path d="M12 5v14M5 12h14" /></svg>
|
|
10040
10663
|
</button>
|
|
@@ -10091,13 +10714,28 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10091
10714
|
(2.5) all share one weight. View-box (24×24) and
|
|
10092
10715
|
display size (13×13) unchanged, so geometry stays
|
|
10093
10716
|
pixel-stable — only the stroke deepens. */}
|
|
10717
|
+
{/* Round 453 / Loop: chrome reset icon strokeWidth hover
|
|
10718
|
+
lift — 2.5 → 2.8 on hoveredReset && !resetSpinning.
|
|
10719
|
+
Sibling to R443 runtime badge inner-icon sw lift
|
|
10720
|
+
(2.4→2.8) — both chrome icons now thicken on hover
|
|
10721
|
+
for tactile feedback. Pre-R453 reset hover was a
|
|
10722
|
+
rotate-only cue (R350); R453 adds a stroke-weight
|
|
10723
|
+
axis so the affordance reads with both motion (R350
|
|
10724
|
+
rotate -8°) AND geometry (R453 sw +0.3). Gated on
|
|
10725
|
+
!resetSpinning so the R184 spin keyframe owns paint
|
|
10726
|
+
during its 450ms run. 200ms stroke-width transition
|
|
10727
|
+
appended to the style list matches R350 transform
|
|
10728
|
+
cadence. data-topo-chrome-reset-icon-stroke-width
|
|
10729
|
+
attr exposes the resolved value for tests. */}
|
|
10094
10730
|
<svg
|
|
10095
10731
|
width="13" height="13" viewBox="0 0 24 24"
|
|
10096
|
-
fill="none" stroke="currentColor"
|
|
10732
|
+
fill="none" stroke="currentColor"
|
|
10733
|
+
strokeWidth={hoveredReset && !resetSpinning ? '2.8' : '2.5'}
|
|
10097
10734
|
strokeLinecap="round" strokeLinejoin="round"
|
|
10098
10735
|
aria-hidden
|
|
10099
10736
|
className={resetSpinning ? 'anet-reset-spin' : undefined}
|
|
10100
10737
|
data-topo-chrome-reset-icon
|
|
10738
|
+
data-topo-chrome-reset-icon-stroke-width={hoveredReset && !resetSpinning ? '2.8' : '2.5'}
|
|
10101
10739
|
// R350: hover-rotate preview of the R184 click-spin.
|
|
10102
10740
|
// Gated on !resetSpinning so the anet-reset-spin keyframe
|
|
10103
10741
|
// owns transform during its 450ms run. transformOrigin
|
|
@@ -10106,7 +10744,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10106
10744
|
style={{
|
|
10107
10745
|
transform: hoveredReset && !resetSpinning ? 'rotate(-8deg)' : 'rotate(0deg)',
|
|
10108
10746
|
transformOrigin: 'center',
|
|
10109
|
-
transition: 'transform 200ms ease-out',
|
|
10747
|
+
transition: 'transform 200ms ease-out, stroke-width 200ms ease-out',
|
|
10110
10748
|
}}
|
|
10111
10749
|
data-topo-chrome-reset-icon-hover={hoveredReset && !resetSpinning ? 'true' : 'false'}
|
|
10112
10750
|
>
|
|
@@ -10176,12 +10814,20 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
10176
10814
|
gpu hint promotes the svg to its own compositor layer
|
|
10177
10815
|
for crisper edges during the scale tween. Closes the
|
|
10178
10816
|
chrome-strip per-icon hover-affordance arc. */}
|
|
10817
|
+
{/* R455 — fullscreen ENTER + EXIT icons pick up the same
|
|
10818
|
+
group-hover:[stroke-width:2.8] family lift as the
|
|
10819
|
+
zoom +/− icons (R454) and chrome reset icon (R453).
|
|
10820
|
+
Chrome icon hover sw lift family now 6 anchors —
|
|
10821
|
+
R208/R443 runtime badge + R453/R454-zoom-out/zoom-in
|
|
10822
|
+
+ R455 fullscreen (this round). transition-[transform,
|
|
10823
|
+
stroke-width] expands existing transition-transform
|
|
10824
|
+
so the sw lift eases under R352 scale-110 cadence. */}
|
|
10179
10825
|
{isFullscreen ? (
|
|
10180
|
-
<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">
|
|
10826
|
+
<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">
|
|
10181
10827
|
<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" />
|
|
10182
10828
|
</svg>
|
|
10183
10829
|
) : (
|
|
10184
|
-
<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">
|
|
10830
|
+
<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">
|
|
10185
10831
|
<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" />
|
|
10186
10832
|
</svg>
|
|
10187
10833
|
)}
|