@sleep2agi/agent-network-dashboard 0.4.6-preview.121 → 0.4.6-preview.123
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 +4 -4
- 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 +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin/page_client-reference-manifest.js +1 -1
- package/.next/server/app/admin.html +2 -2
- package/.next/server/app/admin.rsc +2 -2
- package/.next/server/app/admin.segments/_full.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_index.segment.rsc +2 -2
- package/.next/server/app/admin.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +2 -2
- package/.next/server/app/logs.rsc +2 -2
- package/.next/server/app/logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/messages/page_client-reference-manifest.js +1 -1
- package/.next/server/app/messages.html +2 -2
- package/.next/server/app/messages.rsc +2 -2
- package/.next/server/app/messages.segments/_full.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_index.segment.rsc +2 -2
- package/.next/server/app/messages.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
- package/.next/server/app/node/page_client-reference-manifest.js +1 -1
- package/.next/server/app/node.html +2 -2
- package/.next/server/app/node.rsc +2 -2
- package/.next/server/app/node.segments/_full.segment.rsc +2 -2
- package/.next/server/app/node.segments/_head.segment.rsc +1 -1
- package/.next/server/app/node.segments/_index.segment.rsc +2 -2
- package/.next/server/app/node.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/node.segments/node.segment.rsc +1 -1
- package/.next/server/app/nodes/page_client-reference-manifest.js +1 -1
- package/.next/server/app/nodes.html +2 -2
- package/.next/server/app/nodes.rsc +2 -2
- package/.next/server/app/nodes.segments/_full.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_index.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +2 -2
- package/.next/server/app/server-logs.rsc +2 -2
- package/.next/server/app/server-logs.segments/_full.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
- package/.next/server/app/settings/networks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/networks.html +2 -2
- package/.next/server/app/settings/networks.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +2 -2
- package/.next/server/app/settings/tokens.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +3 -3
- package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks/page_client-reference-manifest.js +1 -1
- package/.next/server/app/tasks.html +2 -2
- package/.next/server/app/tasks.rsc +2 -2
- package/.next/server/app/tasks.segments/_full.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_index.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/056qnpaojmgob.js +4 -0
- package/.next/static/chunks/{0a-6zp8m0ux5..js → 13q82hspm0t83.js} +1 -1
- package/.next/static/chunks/1450fa_~v9qpc.css +2 -0
- package/.next/static/chunks/{0ekib0be3t9i5.js → 155clh0u_d7o~.js} +1 -1
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +78 -17
- package/package.json +1 -1
- package/scripts/topo-hub-hover-ring-test.mjs +82 -0
- package/scripts/topo-recent-row-pin-test.mjs +126 -0
- package/.next/static/chunks/001t1axy5kvz8.css +0 -2
- package/.next/static/chunks/106~a5kttxze-.js +0 -4
- /package/.next/static/{fh5pUqgTFNt162QarYePi → 1T3W_wqzriDiRMGvMe9qe}/_buildManifest.js +0 -0
- /package/.next/static/{fh5pUqgTFNt162QarYePi → 1T3W_wqzriDiRMGvMe9qe}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{fh5pUqgTFNt162QarYePi → 1T3W_wqzriDiRMGvMe9qe}/_ssgManifest.js +0 -0
|
@@ -821,12 +821,27 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
821
821
|
// visible without reading the tooltip. The set is the link's two aliases
|
|
822
822
|
// (null when no edge hovered); node opacity composes this after inFocus.
|
|
823
823
|
const [hoveredEdgeKey, setHoveredEdgeKey] = useState<string | null>(null);
|
|
824
|
+
// Round 116 / Loop: sticky variant of hoveredEdgeKey. R56 made the
|
|
825
|
+
// recent-signal rows brighten the matching edge on hover, but the
|
|
826
|
+
// filter released on mouseleave — no way to lock a flow for a
|
|
827
|
+
// closer look. Click-to-pin closes that gap, matching the
|
|
828
|
+
// established hover→pin idiom (R60 status, R61 legend, R63 group,
|
|
829
|
+
// R88 vendor). activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey
|
|
830
|
+
// below so hover still wins for spot-comparison while a pin is set.
|
|
831
|
+
const [pinnedEdgeKey, setPinnedEdgeKey] = useState<string | null>(null);
|
|
832
|
+
const activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey;
|
|
824
833
|
// Round 77 / Loop: hovering the "N active links" header chip globally
|
|
825
834
|
// brightens every flow edge (1.5× opacity). Transient affordance — the
|
|
826
835
|
// chip already says HOW MANY active flows exist; this answers WHERE
|
|
827
836
|
// they are in one glance, without the user scanning the canvas for
|
|
828
837
|
// moving particles. Reset on mouse leave.
|
|
829
838
|
const [hoveredActiveLinks, setHoveredActiveLinks] = useState(false);
|
|
839
|
+
// Round 115 / Loop: hub-center hover state. R52 made the hub
|
|
840
|
+
// clickable (fit view) + cursor:pointer, but there was no
|
|
841
|
+
// hover-time visual feedback — the click target felt guessed at
|
|
842
|
+
// rather than confirmed. This state drives a subtle hint ring on
|
|
843
|
+
// hover so users see the affordance before committing the click.
|
|
844
|
+
const [hoveredHub, setHoveredHub] = useState(false);
|
|
830
845
|
// Round 80 / Loop: vendor-letter hover in the distribution chip. The
|
|
831
846
|
// chip already names vendor mix (`C:5 G:3 ?:1`); hover a letter and
|
|
832
847
|
// every node from OTHER vendors dims. Surfaces the breakdown spatially
|
|
@@ -940,10 +955,12 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
940
955
|
// R74 listener for layout + view palette commands lives below
|
|
941
956
|
// fitView's declaration — see further down in the file.
|
|
942
957
|
const hoveredEdgeEndpoints = useMemo<Set<string> | null>(() => {
|
|
943
|
-
|
|
944
|
-
|
|
958
|
+
// R116: compose hover ?? pin so pinning a row via click keeps the
|
|
959
|
+
// endpoint ring + edge ladder lit after mouseleave.
|
|
960
|
+
if (!activeEdgeKey) return null;
|
|
961
|
+
const link = flowLinks.find(l => l.key === activeEdgeKey);
|
|
945
962
|
return link ? new Set([link.from, link.to]) : null;
|
|
946
|
-
}, [
|
|
963
|
+
}, [activeEdgeKey, flowLinks]);
|
|
947
964
|
|
|
948
965
|
// --- Round 103 (issue #81): fullscreen + zoom + pan interaction layer ---
|
|
949
966
|
// DIY native (no d3 / svg-pan-zoom): wrap the topology content in a single
|
|
@@ -1204,13 +1221,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1204
1221
|
// R63: extends to pinnedGroup too. Clears WHATEVER pin is active
|
|
1205
1222
|
// (one Esc collapses all topology pins) so the keyboard escape
|
|
1206
1223
|
// route stays a single key, not "Esc maybe Esc again".
|
|
1207
|
-
else if (e.key === 'Escape' && !chatAlias && (pinnedStatus || pinnedGroup || pinnedVendor)) {
|
|
1208
|
-
// R88: extend the universal-cancel to
|
|
1209
|
-
// collapses every topology pin (matches R62/R63's
|
|
1210
|
-
// not Esc-maybe-Esc-again" promise).
|
|
1211
|
-
if (pinnedStatus)
|
|
1212
|
-
if (pinnedGroup)
|
|
1213
|
-
if (pinnedVendor)
|
|
1224
|
+
else if (e.key === 'Escape' && !chatAlias && (pinnedStatus || pinnedGroup || pinnedVendor || pinnedEdgeKey)) {
|
|
1225
|
+
// R88/R116: extend the universal-cancel to vendor + edge too.
|
|
1226
|
+
// One Esc collapses every topology pin (matches R62/R63's
|
|
1227
|
+
// "single key, not Esc-maybe-Esc-again" promise).
|
|
1228
|
+
if (pinnedStatus) setPinnedStatus(null);
|
|
1229
|
+
if (pinnedGroup) setPinnedGroup(null);
|
|
1230
|
+
if (pinnedVendor) setPinnedVendor(null);
|
|
1231
|
+
if (pinnedEdgeKey) setPinnedEdgeKey(null);
|
|
1214
1232
|
e.preventDefault();
|
|
1215
1233
|
}
|
|
1216
1234
|
};
|
|
@@ -1221,7 +1239,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1221
1239
|
// and pinnedStatus so the Escape branch reads fresh state (re-binding
|
|
1222
1240
|
// the listener on these state changes is sub-ms — cheaper than refs).
|
|
1223
1241
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1224
|
-
}, [fitView, chatAlias, pinnedStatus, pinnedGroup, pinnedVendor]);
|
|
1242
|
+
}, [fitView, chatAlias, pinnedStatus, pinnedGroup, pinnedVendor, pinnedEdgeKey]);
|
|
1225
1243
|
|
|
1226
1244
|
const toggleFullscreen = () => {
|
|
1227
1245
|
const el = containerRef.current;
|
|
@@ -2229,7 +2247,8 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2229
2247
|
// background to the focus. Singletons fall through unchanged
|
|
2230
2248
|
// (their group key is the alias itself, so bothInHoveredGroup
|
|
2231
2249
|
// is impossible for a non-self-edge).
|
|
2232
|
-
|
|
2250
|
+
// R116: composes hover ?? pin — a pinned edge stays "hot" after the cursor leaves.
|
|
2251
|
+
const isHoveredEdge = activeEdgeKey === link.key;
|
|
2233
2252
|
const fromGroup = groupKeys[link.from] ?? link.from;
|
|
2234
2253
|
const toGroup = groupKeys[link.to] ?? link.to;
|
|
2235
2254
|
const bothInHoveredGroup = !!activeGroup && fromGroup === activeGroup && toGroup === activeGroup;
|
|
@@ -2242,7 +2261,7 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2242
2261
|
// the edge-hover (2.0×) or node-hover (1.7×) priorities.
|
|
2243
2262
|
const edgeOpacityMul = isHoveredEdge
|
|
2244
2263
|
? 2.0
|
|
2245
|
-
:
|
|
2264
|
+
: activeEdgeKey
|
|
2246
2265
|
? 0.35
|
|
2247
2266
|
: !hoveredAlias && !activeGroup
|
|
2248
2267
|
? (hoveredActiveLinks ? 1.5 : 1)
|
|
@@ -2432,11 +2451,14 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2432
2451
|
dual-circle "lit lamp" core is unchanged. */}
|
|
2433
2452
|
{layout === 'ring' && (<g
|
|
2434
2453
|
data-topo-hub
|
|
2454
|
+
data-topo-hub-hovered={hoveredHub ? 'true' : 'false'}
|
|
2435
2455
|
style={{ cursor: 'pointer' }}
|
|
2436
2456
|
// Stop pointerdown from reaching the SVG pan handler — same
|
|
2437
2457
|
// reason as the node <g>: a captured pointer makes Chromium
|
|
2438
2458
|
// fire the follow-up click on the SVG, not this group.
|
|
2439
2459
|
onPointerDown={(e) => e.stopPropagation()}
|
|
2460
|
+
onMouseEnter={() => setHoveredHub(true)}
|
|
2461
|
+
onMouseLeave={() => setHoveredHub(false)}
|
|
2440
2462
|
// Round 52 / Loop: hub is the visual anchor but had no click
|
|
2441
2463
|
// action — users discovered fit-to-content only via the `f`
|
|
2442
2464
|
// key or the chrome button. Wire the hub to fitView() so the
|
|
@@ -2509,6 +2531,23 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2509
2531
|
{/* core — 20px diameter, larger inner highlight reads as a "lit lamp" */}
|
|
2510
2532
|
<circle cx={cx} cy={cy} r="10" fill={isLight ? '#059669' : '#10b981'} />
|
|
2511
2533
|
<circle cx={cx} cy={cy} r="5" fill="#d1fae5" opacity="0.9" />
|
|
2534
|
+
{/* R115 / Loop: hover hint ring. Stroke-only circle at r=14
|
|
2535
|
+
that fades in when the hub is hovered — the same idea
|
|
2536
|
+
R44 used for node avatars (group-hover stroke). r=14
|
|
2537
|
+
sits comfortably outside the r=10 core and INSIDE the
|
|
2538
|
+
r=18 grounding halo, so the hover indicator is fully
|
|
2539
|
+
contained within the existing hub footprint (no bbox
|
|
2540
|
+
growth, overlap test unchanged). pointerEvents:none so
|
|
2541
|
+
the hint can't intercept the click that produced it. */}
|
|
2542
|
+
<circle
|
|
2543
|
+
cx={cx} cy={cy} r="14"
|
|
2544
|
+
fill="none"
|
|
2545
|
+
stroke={isLight ? '#059669' : '#10b981'}
|
|
2546
|
+
strokeWidth="1.5"
|
|
2547
|
+
opacity={hoveredHub ? (isLight ? 0.85 : 0.7) : 0}
|
|
2548
|
+
data-topo-hub-hover-ring
|
|
2549
|
+
style={{ pointerEvents: 'none', transition: 'opacity 180ms ease-out' }}
|
|
2550
|
+
/>
|
|
2512
2551
|
</g>)}
|
|
2513
2552
|
|
|
2514
2553
|
{/* agent nodes */}
|
|
@@ -3102,14 +3141,32 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3102
3141
|
const rawAt = link.last_at ? relativeAgo(link.last_at) : null;
|
|
3103
3142
|
const lastAt = rawAt ? rawAt.replace(/\s+ago$/, '') : null;
|
|
3104
3143
|
const isRowHovered = hoveredEdgeKey === link.key;
|
|
3144
|
+
const isRowPinned = pinnedEdgeKey === link.key;
|
|
3145
|
+
const isRowActive = isRowHovered || isRowPinned;
|
|
3105
3146
|
return (
|
|
3106
3147
|
<g
|
|
3107
3148
|
key={link.key}
|
|
3108
3149
|
data-recent-row={link.key}
|
|
3109
3150
|
data-recent-row-hovered={isRowHovered ? 'true' : 'false'}
|
|
3151
|
+
data-recent-row-pinned={isRowPinned ? 'true' : 'false'}
|
|
3152
|
+
role="button"
|
|
3153
|
+
tabIndex={0}
|
|
3154
|
+
aria-pressed={isRowPinned}
|
|
3110
3155
|
style={{ cursor: 'pointer' }}
|
|
3156
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
3111
3157
|
onMouseEnter={() => setHoveredEdgeKey(link.key)}
|
|
3112
3158
|
onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}
|
|
3159
|
+
// R116: click toggles pin. activeEdgeKey =
|
|
3160
|
+
// hoveredEdgeKey ?? pinnedEdgeKey so the matching
|
|
3161
|
+
// edge stays "hot" after mouseleave; click again
|
|
3162
|
+
// (or Esc) releases.
|
|
3163
|
+
onClick={() => setPinnedEdgeKey(prev => prev === link.key ? null : link.key)}
|
|
3164
|
+
onKeyDown={(e) => {
|
|
3165
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
3166
|
+
e.preventDefault();
|
|
3167
|
+
setPinnedEdgeKey(prev => prev === link.key ? null : link.key);
|
|
3168
|
+
}
|
|
3169
|
+
}}
|
|
3113
3170
|
>
|
|
3114
3171
|
{/* R104: subtle row-background tint on hover. R56
|
|
3115
3172
|
already brightens the matching edge on the canvas,
|
|
@@ -3118,17 +3175,21 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3118
3175
|
Filling the rect at hover with `pal.legendAccent`
|
|
3119
3176
|
at low alpha gives the row visual feedback at the
|
|
3120
3177
|
source surface, mirroring the list-item idiom from
|
|
3121
|
-
the chip-row pills.
|
|
3178
|
+
the chip-row pills. R116: pinned rows tint
|
|
3179
|
+
stronger than hovered ones so locked vs preview
|
|
3180
|
+
is discriminable. */}
|
|
3122
3181
|
<rect
|
|
3123
3182
|
x="6" y={38 + index * 16 - 10}
|
|
3124
3183
|
width="218" height="14" rx="3"
|
|
3125
|
-
fill={
|
|
3126
|
-
opacity={
|
|
3184
|
+
fill={isRowActive ? pal.legendAccent : 'transparent'}
|
|
3185
|
+
opacity={isRowPinned ? (isLight ? 0.18 : 0.22)
|
|
3186
|
+
: isRowHovered ? (isLight ? 0.10 : 0.14)
|
|
3187
|
+
: 1}
|
|
3127
3188
|
style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}
|
|
3128
3189
|
/>
|
|
3129
3190
|
<text
|
|
3130
3191
|
x="13" y={38 + index * 16}
|
|
3131
|
-
fill={
|
|
3192
|
+
fill={isRowActive ? pal.legendHeadline : pal.legendText}
|
|
3132
3193
|
fontSize="9"
|
|
3133
3194
|
fontFamily="monospace"
|
|
3134
3195
|
style={{ transition: 'fill 150ms ease-out' }}
|
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* Round 115 verification: hub center hover-state visual feedback.
|
|
2
|
+
* R52 made the hub clickable (fit view) + cursor:pointer, but
|
|
3
|
+
* hovering showed nothing. Now a thin r=14 stroke ring fades in on
|
|
4
|
+
* hover and out on leave. Sits inside the existing r=18 halo so
|
|
5
|
+
* overlap footprint unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Probes:
|
|
8
|
+
* - baseline: ring opacity 0, data-topo-hub-hovered="false"
|
|
9
|
+
* - hover hub: ring opacity > 0, hovered="true"
|
|
10
|
+
* - mouseleave: opacity 0, hovered="false"
|
|
11
|
+
*/
|
|
12
|
+
import { chromium } from 'playwright';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
16
|
+
const browser = await chromium.launch({ headless: true });
|
|
17
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
18
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
19
|
+
await ctx.addInitScript(() => {
|
|
20
|
+
try {
|
|
21
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
22
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
23
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
24
|
+
} catch {}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
28
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
29
|
+
const r = await route.fetch();
|
|
30
|
+
const b = await r.json();
|
|
31
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
32
|
+
const mk = (alias) => ({
|
|
33
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
34
|
+
network_id: nid, project_dir: null,
|
|
35
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
36
|
+
});
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma')] } });
|
|
38
|
+
});
|
|
39
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
40
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
41
|
+
const page = await ctx.newPage();
|
|
42
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
43
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
44
|
+
await page.waitForSelector('[data-topo-hub]', { timeout: 10000 });
|
|
45
|
+
await page.waitForTimeout(400);
|
|
46
|
+
|
|
47
|
+
const read = () => page.evaluate(() => {
|
|
48
|
+
const hub = document.querySelector('[data-topo-hub]');
|
|
49
|
+
const ring = document.querySelector('[data-topo-hub-hover-ring]');
|
|
50
|
+
return {
|
|
51
|
+
hovered: hub?.getAttribute('data-topo-hub-hovered') || '',
|
|
52
|
+
ringOpacity: +(ring?.getAttribute('opacity') || '0'),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const before = await read();
|
|
57
|
+
|
|
58
|
+
await page.locator('[data-topo-hub]').hover();
|
|
59
|
+
await page.waitForTimeout(300);
|
|
60
|
+
const onHover = await read();
|
|
61
|
+
|
|
62
|
+
// Move pointer far away.
|
|
63
|
+
await page.mouse.move(10, 10);
|
|
64
|
+
await page.waitForTimeout(300);
|
|
65
|
+
const afterLeave = await read();
|
|
66
|
+
|
|
67
|
+
await browser.close();
|
|
68
|
+
|
|
69
|
+
const results = {
|
|
70
|
+
before_notHovered: before.hovered === 'false',
|
|
71
|
+
before_ringHidden: before.ringOpacity === 0,
|
|
72
|
+
hover_attrTrue: onHover.hovered === 'true',
|
|
73
|
+
hover_ringVisible: onHover.ringOpacity > 0.5,
|
|
74
|
+
leave_attrFalse: afterLeave.hovered === 'false',
|
|
75
|
+
leave_ringHidden: afterLeave.ringOpacity === 0,
|
|
76
|
+
};
|
|
77
|
+
const ok = Object.values(results).every(Boolean);
|
|
78
|
+
console.log(`${ok ? '✅' : '❌'} hub-hover ring:`, JSON.stringify(results),
|
|
79
|
+
`\n before=`, before,
|
|
80
|
+
`\n onHover=`, onHover,
|
|
81
|
+
`\n afterLeave=`, afterLeave);
|
|
82
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* Round 116 verification: recent-signal row click pins the edge.
|
|
2
|
+
* R56 made hover transient; R116 makes click sticky — survives
|
|
3
|
+
* mouseleave, releases on second click or Esc. activeEdgeKey =
|
|
4
|
+
* hoveredEdgeKey ?? pinnedEdgeKey, so hover-while-pinned previews
|
|
5
|
+
* another edge transiently and mouseleave falls back to the pin.
|
|
6
|
+
*
|
|
7
|
+
* Drives 2 flow rows. Click row 0 → row's aria-pressed=true,
|
|
8
|
+
* data-recent-row-pinned=true, the edge stays bright after mouseleave
|
|
9
|
+
* (non-endpoints stay dim, endpoint ring stays). Click row 0 again
|
|
10
|
+
* → release. Pin row 0, hover row 1 → row-1 preview wins; leave →
|
|
11
|
+
* row-0 pin restores. Esc clears.
|
|
12
|
+
*/
|
|
13
|
+
import { chromium } from 'playwright';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
17
|
+
const browser = await chromium.launch({ headless: true });
|
|
18
|
+
|
|
19
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
20
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
21
|
+
await ctx.addInitScript(() => {
|
|
22
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
26
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
27
|
+
const r = await route.fetch();
|
|
28
|
+
const b = await r.json();
|
|
29
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
30
|
+
const mk = (alias) => ({
|
|
31
|
+
alias, status: 'idle', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
32
|
+
network_id: nid, project_dir: null,
|
|
33
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
34
|
+
});
|
|
35
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [mk('alpha'), mk('beta'), mk('gamma'), mk('delta')] } });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const mkMsg = (id, from_alias, to_alias, ageMs) => ({
|
|
40
|
+
id, from_alias, to_alias, content: 'hi', network_id: 'default',
|
|
41
|
+
created_at: new Date(now - ageMs).toISOString(),
|
|
42
|
+
});
|
|
43
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [
|
|
44
|
+
mkMsg('m1', 'alpha', 'beta', 30 * 1000),
|
|
45
|
+
mkMsg('m2', 'gamma', 'delta', 60 * 1000),
|
|
46
|
+
] } }));
|
|
47
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
48
|
+
const page = await ctx.newPage();
|
|
49
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
50
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
51
|
+
await page.waitForSelector('[data-recent-row]', { timeout: 10000 });
|
|
52
|
+
await page.waitForTimeout(400);
|
|
53
|
+
|
|
54
|
+
const rowState = (key) => page.evaluate((k) => {
|
|
55
|
+
const g = document.querySelector(`[data-recent-row="${k}"]`);
|
|
56
|
+
return {
|
|
57
|
+
pinned: g?.getAttribute('data-recent-row-pinned') || '',
|
|
58
|
+
aria: g?.getAttribute('aria-pressed') || '',
|
|
59
|
+
};
|
|
60
|
+
}, key);
|
|
61
|
+
const endpointRingFor = (alias) => page.evaluate((a) => {
|
|
62
|
+
const g = document.querySelector(`g[data-node="${a}"]`);
|
|
63
|
+
return !!g?.querySelector('[data-edge-endpoint-ring]');
|
|
64
|
+
}, alias);
|
|
65
|
+
|
|
66
|
+
const before = await rowState('alpha->beta');
|
|
67
|
+
|
|
68
|
+
// Click row 0 → pin.
|
|
69
|
+
await page.locator('[data-recent-row="alpha->beta"]').click();
|
|
70
|
+
await page.mouse.move(10, 10);
|
|
71
|
+
await page.waitForTimeout(300);
|
|
72
|
+
const afterPin = await rowState('alpha->beta');
|
|
73
|
+
const alphaRingPin = await endpointRingFor('alpha');
|
|
74
|
+
const betaRingPin = await endpointRingFor('beta');
|
|
75
|
+
const gammaRingPin = await endpointRingFor('gamma');
|
|
76
|
+
|
|
77
|
+
// Click again → release.
|
|
78
|
+
await page.locator('[data-recent-row="alpha->beta"]').click();
|
|
79
|
+
await page.mouse.move(10, 10);
|
|
80
|
+
await page.waitForTimeout(300);
|
|
81
|
+
const afterRelease = await rowState('alpha->beta');
|
|
82
|
+
const alphaRingRelease = await endpointRingFor('alpha');
|
|
83
|
+
|
|
84
|
+
// Pin row 0 again, hover row 1 — preview wins.
|
|
85
|
+
await page.locator('[data-recent-row="alpha->beta"]').click();
|
|
86
|
+
await page.mouse.move(10, 10);
|
|
87
|
+
await page.waitForTimeout(200);
|
|
88
|
+
await page.locator('[data-recent-row="gamma->delta"]').hover();
|
|
89
|
+
await page.waitForTimeout(300);
|
|
90
|
+
const alphaWhileHoverGamma = await endpointRingFor('alpha');
|
|
91
|
+
const gammaWhileHover = await endpointRingFor('gamma');
|
|
92
|
+
|
|
93
|
+
// Leave gamma row — row-0 pin restores.
|
|
94
|
+
await page.mouse.move(10, 10);
|
|
95
|
+
await page.waitForTimeout(300);
|
|
96
|
+
const alphaAfterLeave = await endpointRingFor('alpha');
|
|
97
|
+
const gammaAfterLeave = await endpointRingFor('gamma');
|
|
98
|
+
|
|
99
|
+
// Esc clears.
|
|
100
|
+
await page.keyboard.press('Escape');
|
|
101
|
+
await page.waitForTimeout(300);
|
|
102
|
+
const afterEsc = await rowState('alpha->beta');
|
|
103
|
+
const alphaAfterEsc = await endpointRingFor('alpha');
|
|
104
|
+
|
|
105
|
+
await browser.close();
|
|
106
|
+
|
|
107
|
+
const results = {
|
|
108
|
+
before_notPinned: before.pinned === 'false' && before.aria === 'false',
|
|
109
|
+
pin_attrTrue: afterPin.pinned === 'true' && afterPin.aria === 'true',
|
|
110
|
+
pin_endpointsLit: alphaRingPin === true && betaRingPin === true,
|
|
111
|
+
pin_nonEndpointDark: gammaRingPin === false,
|
|
112
|
+
releaseAfterReclick: afterRelease.pinned === 'false' && alphaRingRelease === false,
|
|
113
|
+
hoverPreview_gammaLit: gammaWhileHover === true,
|
|
114
|
+
hoverPreview_alphaDark: alphaWhileHoverGamma === false,
|
|
115
|
+
leave_pinRestored: alphaAfterLeave === true && gammaAfterLeave === false,
|
|
116
|
+
esc_clearsPin: afterEsc.pinned === 'false' && alphaAfterEsc === false,
|
|
117
|
+
};
|
|
118
|
+
const ok = Object.values(results).every(Boolean);
|
|
119
|
+
console.log(`${ok ? '✅' : '❌'} recent-row pin:`, JSON.stringify(results),
|
|
120
|
+
`\n afterPin=`, afterPin,
|
|
121
|
+
`\n rings@pin: alpha=`, alphaRingPin, ' beta=', betaRingPin, ' gamma=', gammaRingPin,
|
|
122
|
+
`\n afterRelease=`, afterRelease,
|
|
123
|
+
`\n hoverPreview: alpha=`, alphaWhileHoverGamma, ' gamma=', gammaWhileHover,
|
|
124
|
+
`\n afterLeave: alpha=`, alphaAfterLeave, ' gamma=', gammaAfterLeave,
|
|
125
|
+
`\n afterEsc=`, afterEsc);
|
|
126
|
+
process.exit(ok ? 0 : 1);
|