@sleep2agi/agent-network-dashboard 0.4.6-preview.151 → 0.4.6-preview.153
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/{0d45f.svln5hm.js → 0fzk_ocmoh5w2.js} +1 -1
- package/.next/static/chunks/{05b69-7w4fvbn.css → 0g_sxsw.42had.css} +1 -1
- package/.next/static/chunks/{0f6m4wjhlakrw.js → 138cyngurs2f9.js} +1 -1
- package/.next/static/chunks/13o6jyrf7z4kk.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +87 -34
- package/app/globals.css +19 -4
- package/package.json +1 -1
- package/scripts/topo-spoke-busyness-test.mjs +110 -0
- package/scripts/topo-sweep-busyness-test.mjs +94 -0
- package/.next/static/chunks/0g1ap~wkozy~6.js +0 -4
- /package/.next/static/{jNoJAOY9PpVhTslXxUvqQ → yEklOmrUTGrENL-pX_YPo}/_buildManifest.js +0 -0
- /package/.next/static/{jNoJAOY9PpVhTslXxUvqQ → yEklOmrUTGrENL-pX_YPo}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{jNoJAOY9PpVhTslXxUvqQ → yEklOmrUTGrENL-pX_YPo}/_ssgManifest.js +0 -0
|
@@ -2106,20 +2106,43 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2106
2106
|
scan without being noisy. Inline transform-origin on the
|
|
2107
2107
|
<g> wrapper ensures Chrome / Firefox rotate around (cx,cy)
|
|
2108
2108
|
instead of the SVG viewBox corner. */}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2109
|
+
{(() => {
|
|
2110
|
+
// R146: radar sweep rotation buckets on workingCount, joining
|
|
2111
|
+
// R84 hub breath / R131 outer orbit / R132 group march /
|
|
2112
|
+
// R145 idle spokes as the 5th and final layer in the busyness-
|
|
2113
|
+
// driven motion family. 8 / 6 / 4 / 3 seconds — same 0 / 1-2 /
|
|
2114
|
+
// 3-5 / 6+ thresholds R84 uses. "Busier fleet = more frequent
|
|
2115
|
+
// scans" feels semantically right for a radar idiom. R45
|
|
2116
|
+
// baseline 6s sits at bucket 1 so the calm/busy spread bracks
|
|
2117
|
+
// around the historical default.
|
|
2118
|
+
const busy = workingCount === 0 ? 0
|
|
2119
|
+
: workingCount <= 2 ? 1
|
|
2120
|
+
: workingCount <= 5 ? 2
|
|
2121
|
+
: 3;
|
|
2122
|
+
const sweepDur = [8, 6, 4, 3][busy];
|
|
2123
|
+
return (
|
|
2124
|
+
<g
|
|
2125
|
+
style={{
|
|
2126
|
+
transformOrigin: `${cx}px ${cy}px`,
|
|
2127
|
+
transformBox: 'view-box',
|
|
2128
|
+
pointerEvents: 'none',
|
|
2129
|
+
// CSS var consumed by `.anet-topo-sweep` (line 848 of
|
|
2130
|
+
// globals.css). React's CSSProperties type doesn't model
|
|
2131
|
+
// custom properties → cast through Record<string, string>.
|
|
2132
|
+
...({ ['--sweep-dur']: `${sweepDur}s` } as Record<string, string>),
|
|
2133
|
+
} as React.CSSProperties}
|
|
2134
|
+
className="anet-topo-sweep"
|
|
2135
|
+
opacity={isLight ? 0.7 : 1}
|
|
2136
|
+
data-topo-sweep-bucket={busy}
|
|
2137
|
+
data-topo-sweep-dur={sweepDur}
|
|
2138
|
+
>
|
|
2139
|
+
<path
|
|
2140
|
+
d={`M ${cx} ${cy} L ${cx + 330} ${cy} A 330 330 0 0 0 ${cx + 330 * Math.cos(-Math.PI / 4.5)} ${cy + 330 * Math.sin(-Math.PI / 4.5)} Z`}
|
|
2141
|
+
fill="url(#topo-sweep)"
|
|
2142
|
+
/>
|
|
2143
|
+
</g>
|
|
2144
|
+
);
|
|
2145
|
+
})()}
|
|
2123
2146
|
|
|
2124
2147
|
{/* radar rings — pure decoration at fixed radii, independent of
|
|
2125
2148
|
node positions so the radar aesthetic is preserved across tier
|
|
@@ -2264,27 +2287,57 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
2264
2287
|
{/* hub links — round 46: idle spokes now have animated
|
|
2265
2288
|
stroke-dashoffset so dashes flow outward from the hub
|
|
2266
2289
|
("command relay" feel). Active spokes carrying live message
|
|
2267
|
-
flow stay as solid bright strokes.
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2290
|
+
flow stay as solid bright strokes.
|
|
2291
|
+
R145 / Loop: idle-spoke animation cadence buckets on
|
|
2292
|
+
workingCount, mirroring R84 hub-breath / R131 outer-
|
|
2293
|
+
ring orbit / R132 groupbox-march coordination. Same
|
|
2294
|
+
0 / 1-2 / 3-5 / 6+ thresholds. Idle ↔ idle network
|
|
2295
|
+
has a slow "command relay" feel; as work accumulates
|
|
2296
|
+
the dashes accelerate outward, completing the 4th
|
|
2297
|
+
motion layer in the busyness-driven family:
|
|
2298
|
+
R84 hub breath (centre)
|
|
2299
|
+
R131 outer ring orbit (periphery)
|
|
2300
|
+
R132 groupbox march (per-team, grid layout)
|
|
2301
|
+
R145 idle-spoke flow (ring layout, hub→nodes)
|
|
2302
|
+
4 surfaces, 1 signal. Same cadence ladder 2.8/2.4/2.0/
|
|
2303
|
+
1.6s — 1.75× range from calm to busy, capped so the
|
|
2304
|
+
network never feels frantic. */}
|
|
2305
|
+
{layout === 'ring' && (() => {
|
|
2306
|
+
const busy = workingCount === 0 ? 0
|
|
2307
|
+
: workingCount <= 2 ? 1
|
|
2308
|
+
: workingCount <= 5 ? 2
|
|
2309
|
+
: 3;
|
|
2310
|
+
const spokeDur = [2.8, 2.4, 2.0, 1.6][busy];
|
|
2311
|
+
return onlineNodes.map((session, idx) => {
|
|
2312
|
+
const pos = nodePositions[session.alias];
|
|
2313
|
+
if (!pos) return null;
|
|
2314
|
+
const path = curvePath({ x: cx, y: cy }, pos, 0);
|
|
2315
|
+
const isActiveSpoke = activeAliases.has(session.alias);
|
|
2273
2316
|
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2317
|
+
return (
|
|
2318
|
+
<path
|
|
2319
|
+
key={`hub-${session.alias}`}
|
|
2320
|
+
d={path}
|
|
2321
|
+
fill="none"
|
|
2322
|
+
stroke={isActiveSpoke ? pal.spokeStroke.active : pal.spokeStroke.idle}
|
|
2323
|
+
strokeWidth={isActiveSpoke ? 2 : 1}
|
|
2324
|
+
strokeDasharray={isActiveSpoke ? 'none' : '6 14'}
|
|
2325
|
+
opacity={isActiveSpoke ? 0.7 : 0.45}
|
|
2326
|
+
className={isActiveSpoke ? undefined : 'anet-topo-spoke-flow'}
|
|
2327
|
+
data-topo-spoke-bucket={isActiveSpoke ? undefined : busy}
|
|
2328
|
+
data-topo-spoke-dur={isActiveSpoke ? undefined : spokeDur}
|
|
2329
|
+
style={isActiveSpoke ? undefined : ({
|
|
2330
|
+
animationDelay: `${-(idx * 0.25)}s`,
|
|
2331
|
+
// CSS var consumed by `.anet-topo-spoke-flow`
|
|
2332
|
+
// (line 859 of globals.css). React's CSSProperties
|
|
2333
|
+
// type doesn't model custom properties → cast
|
|
2334
|
+
// through Record<string, string>.
|
|
2335
|
+
...({ ['--spoke-dur']: `${spokeDur}s` } as Record<string, string>),
|
|
2336
|
+
} as React.CSSProperties)}
|
|
2337
|
+
/>
|
|
2338
|
+
);
|
|
2339
|
+
});
|
|
2340
|
+
})()}
|
|
2288
2341
|
|
|
2289
2342
|
{/* #111: prefix-group boundary boxes (Vincent 4722). Grid layout
|
|
2290
2343
|
only — groupBoxes is empty in ring mode. Rendered behind the
|
package/app/globals.css
CHANGED
|
@@ -840,24 +840,39 @@ body {
|
|
|
840
840
|
|
|
841
841
|
/* Round 45 — TopoGraph rotating radar sweep wedge. 6s slow rotation
|
|
842
842
|
gives the topology canvas a real "radar scan" feel without distracting
|
|
843
|
-
from the agent nodes.
|
|
843
|
+
from the agent nodes.
|
|
844
|
+
R146: split shorthand into longhand so per-render inline style can
|
|
845
|
+
override animation-duration via --sweep-dur, joining the R145
|
|
846
|
+
--spoke-dur / R132 --march-dur pattern. Default 6s preserves R45's
|
|
847
|
+
original cadence for any caller that forgets the var. */
|
|
844
848
|
@keyframes anet-topo-sweep-kf {
|
|
845
849
|
0% { transform: rotate(0deg); }
|
|
846
850
|
100% { transform: rotate(360deg); }
|
|
847
851
|
}
|
|
848
852
|
.anet-topo-sweep {
|
|
849
|
-
animation: anet-topo-sweep-kf
|
|
853
|
+
animation-name: anet-topo-sweep-kf;
|
|
854
|
+
animation-duration: var(--sweep-dur, 6s);
|
|
855
|
+
animation-timing-function: linear;
|
|
856
|
+
animation-iteration-count: infinite;
|
|
850
857
|
}
|
|
851
858
|
|
|
852
859
|
/* Round 46 — animated dashed spokes flowing from hub outward. Each spoke
|
|
853
860
|
gets a small animationDelay (set inline) so the dashes don't all flow
|
|
854
|
-
in sync — reads as "command relay" pulse rather than a metronome.
|
|
861
|
+
in sync — reads as "command relay" pulse rather than a metronome.
|
|
862
|
+
R145: split shorthand into longhand so per-render inline style can
|
|
863
|
+
override animation-duration via the --spoke-dur custom property —
|
|
864
|
+
same idiom R132 uses for --march-dur on group boxes. Default 2.4s
|
|
865
|
+
when --spoke-dur isn't set, so any future caller that forgets the
|
|
866
|
+
var keeps R46's original cadence. */
|
|
855
867
|
@keyframes anet-topo-spoke-flow-kf {
|
|
856
868
|
from { stroke-dashoffset: 0; }
|
|
857
869
|
to { stroke-dashoffset: -40; }
|
|
858
870
|
}
|
|
859
871
|
.anet-topo-spoke-flow {
|
|
860
|
-
animation: anet-topo-spoke-flow-kf
|
|
872
|
+
animation-name: anet-topo-spoke-flow-kf;
|
|
873
|
+
animation-duration: var(--spoke-dur, 2.4s);
|
|
874
|
+
animation-timing-function: linear;
|
|
875
|
+
animation-iteration-count: infinite;
|
|
861
876
|
}
|
|
862
877
|
|
|
863
878
|
/* Round 85 — group-box "marching ants" perimeter drift.
|
package/package.json
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* Round 145 verification: R46 idle-spoke flow rate buckets on
|
|
2
|
+
* workingCount, completing the 4-layer busyness-driven motion
|
|
3
|
+
* family:
|
|
4
|
+
* R84 hub breath (centre)
|
|
5
|
+
* R131 outer-ring orbit (periphery)
|
|
6
|
+
* R132 groupbox march (per-team, grid layout)
|
|
7
|
+
* R145 idle-spoke flow (ring layout, hub→nodes) — new
|
|
8
|
+
*
|
|
9
|
+
* Bucket ladder shared across the four layers (same R84
|
|
10
|
+
* thresholds):
|
|
11
|
+
* workingCount === 0 → bucket 0
|
|
12
|
+
* workingCount in [1, 2] → bucket 1
|
|
13
|
+
* workingCount in [3, 5] → bucket 2
|
|
14
|
+
* workingCount >= 6 → bucket 3
|
|
15
|
+
*
|
|
16
|
+
* Idle-spoke durations per bucket: 2.8 / 2.4 / 2.0 / 1.6 seconds
|
|
17
|
+
* (R46 baseline 2.4s is bucket 1).
|
|
18
|
+
*
|
|
19
|
+
* Spokes only render in ring layout. Active spokes (carrying flow
|
|
20
|
+
* traffic) don't get the animation — they're solid bright strokes.
|
|
21
|
+
* Only idle spokes carry the .anet-topo-spoke-flow class and the
|
|
22
|
+
* --spoke-dur var. The test seeds 0 messages so all spokes are idle.
|
|
23
|
+
*/
|
|
24
|
+
import { chromium } from 'playwright';
|
|
25
|
+
import { readFileSync } from 'node:fs';
|
|
26
|
+
|
|
27
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
28
|
+
|
|
29
|
+
async function probe(workingCount, totalCount) {
|
|
30
|
+
const browser = await chromium.launch({ headless: true });
|
|
31
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
32
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
33
|
+
await ctx.addInitScript(() => {
|
|
34
|
+
try {
|
|
35
|
+
localStorage.setItem('anet-theme', 'cyber');
|
|
36
|
+
sessionStorage.setItem('anet_v3_auth', '1');
|
|
37
|
+
// ring layout is R145's target — it's also the default, but
|
|
38
|
+
// pinning explicitly defends against future default flips.
|
|
39
|
+
localStorage.setItem('anet-topo-layout', 'ring');
|
|
40
|
+
} catch {}
|
|
41
|
+
});
|
|
42
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
43
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
44
|
+
const r = await route.fetch();
|
|
45
|
+
const b = await r.json();
|
|
46
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
47
|
+
const sessions = [];
|
|
48
|
+
for (let i = 0; i < totalCount; i++) {
|
|
49
|
+
sessions.push({
|
|
50
|
+
alias: `node${i}`,
|
|
51
|
+
status: i < workingCount ? 'working' : 'idle',
|
|
52
|
+
model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
53
|
+
network_id: nid, project_dir: null,
|
|
54
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
58
|
+
});
|
|
59
|
+
// 0 messages → no active spokes → every spoke is idle (.anet-topo-spoke-flow)
|
|
60
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
61
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
62
|
+
|
|
63
|
+
const page = await ctx.newPage();
|
|
64
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
65
|
+
await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
|
|
66
|
+
await page.waitForTimeout(400);
|
|
67
|
+
|
|
68
|
+
const out = await page.evaluate(() => {
|
|
69
|
+
const spokes = [...document.querySelectorAll('path.anet-topo-spoke-flow')];
|
|
70
|
+
return spokes.map(p => ({
|
|
71
|
+
bucket: p.getAttribute('data-topo-spoke-bucket'),
|
|
72
|
+
dur: p.getAttribute('data-topo-spoke-dur'),
|
|
73
|
+
// Resolved animation-duration from computed style — confirms
|
|
74
|
+
// the CSS var bridge through to the cascade.
|
|
75
|
+
cssDur: getComputedStyle(p).getPropertyValue('animation-duration').trim(),
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
await browser.close();
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// One probe per bucket
|
|
83
|
+
const b0 = await probe(0, 4); // 0 working / 4 idle → bucket 0 (2.8s)
|
|
84
|
+
const b1 = await probe(2, 4); // 2 working / 2 idle → bucket 1 (2.4s)
|
|
85
|
+
const b2 = await probe(4, 5); // 4 working / 1 idle → bucket 2 (2.0s)
|
|
86
|
+
const b3 = await probe(7, 8); // 7 working / 1 idle → bucket 3 (1.6s)
|
|
87
|
+
|
|
88
|
+
const consistent = (spokes, bucket, dur) =>
|
|
89
|
+
spokes.length > 0 &&
|
|
90
|
+
spokes.every(s => s.bucket === String(bucket) && s.dur === String(dur) && s.cssDur === `${dur}s`);
|
|
91
|
+
|
|
92
|
+
const results = {
|
|
93
|
+
b0_someSpokes: b0.length > 0,
|
|
94
|
+
b0_bucket0_2_8s: consistent(b0, 0, 2.8),
|
|
95
|
+
b1_bucket1_2_4s: consistent(b1, 1, 2.4),
|
|
96
|
+
b2_bucket2_2_0s: consistent(b2, 2, 2.0),
|
|
97
|
+
b3_bucket3_1_6s: consistent(b3, 3, 1.6),
|
|
98
|
+
// dur strictly decreases as busy climbs
|
|
99
|
+
dur_monotonic:
|
|
100
|
+
Number(b0[0]?.dur) > Number(b1[0]?.dur) &&
|
|
101
|
+
Number(b1[0]?.dur) > Number(b2[0]?.dur) &&
|
|
102
|
+
Number(b2[0]?.dur) > Number(b3[0]?.dur),
|
|
103
|
+
};
|
|
104
|
+
const ok = Object.values(results).every(Boolean);
|
|
105
|
+
console.log(`${ok ? '✅' : '❌'} spoke busyness:`, JSON.stringify(results),
|
|
106
|
+
`\n b0(0w/4)=`, b0.slice(0,1),
|
|
107
|
+
`\n b1(2w/4)=`, b1.slice(0,1),
|
|
108
|
+
`\n b2(4w/5)=`, b2.slice(0,1),
|
|
109
|
+
`\n b3(7w/8)=`, b3.slice(0,1));
|
|
110
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* Round 146 verification: R45 radar sweep rotation rate buckets on
|
|
2
|
+
* workingCount, completing the 5-layer busyness-driven motion family:
|
|
3
|
+
*
|
|
4
|
+
* R84 hub grounding-halo breath — 4 / 3.2 / 2.7 / 2.4 s
|
|
5
|
+
* R131 outer-ring orbit period — 16 / 14 / 12 / 10 s
|
|
6
|
+
* R132 groupbox marching ants — 14 / 12 / 10 / 8 s (grid only)
|
|
7
|
+
* R145 idle-spoke flow rate — 2.8 / 2.4 / 2.0 / 1.6 s (ring only)
|
|
8
|
+
* R146 radar sweep rotation — 8 / 6 / 4 / 3 s (both layouts)
|
|
9
|
+
*
|
|
10
|
+
* Shared R84 thresholds (0 / 1-2 / 3-5 / 6+) drive every layer. The
|
|
11
|
+
* sweep is the only layer visible in BOTH layouts and ties the family
|
|
12
|
+
* together at the canvas-wide scope.
|
|
13
|
+
*
|
|
14
|
+
* R45 baseline 6s sits at bucket 1 — preserved when workingCount in
|
|
15
|
+
* [1, 2] so existing demos and screenshots stay identical.
|
|
16
|
+
*/
|
|
17
|
+
import { chromium } from 'playwright';
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
21
|
+
|
|
22
|
+
async function probe(workingCount, totalCount) {
|
|
23
|
+
const browser = await chromium.launch({ headless: true });
|
|
24
|
+
const ctx = await browser.newContext({ viewport: { width: 1400, height: 1500 } });
|
|
25
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
26
|
+
await ctx.addInitScript(() => {
|
|
27
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
28
|
+
});
|
|
29
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
30
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
31
|
+
const r = await route.fetch();
|
|
32
|
+
const b = await r.json();
|
|
33
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
34
|
+
const sessions = [];
|
|
35
|
+
for (let i = 0; i < totalCount; i++) {
|
|
36
|
+
sessions.push({
|
|
37
|
+
alias: `node${i}`,
|
|
38
|
+
status: i < workingCount ? 'working' : 'idle',
|
|
39
|
+
model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
40
|
+
network_id: nid, project_dir: null,
|
|
41
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
await route.fulfill({ response: r, json: { ...b, sessions } });
|
|
45
|
+
});
|
|
46
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: [] } }));
|
|
47
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
48
|
+
|
|
49
|
+
const page = await ctx.newPage();
|
|
50
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
51
|
+
await page.waitForFunction((n) => document.querySelectorAll('g[data-node]').length === n, totalCount, { timeout: 30000 });
|
|
52
|
+
await page.waitForSelector('.anet-topo-sweep', { timeout: 10000 });
|
|
53
|
+
await page.waitForTimeout(400);
|
|
54
|
+
|
|
55
|
+
const out = await page.evaluate(() => {
|
|
56
|
+
const g = document.querySelector('.anet-topo-sweep');
|
|
57
|
+
return {
|
|
58
|
+
bucket: g?.getAttribute('data-topo-sweep-bucket'),
|
|
59
|
+
dur: g?.getAttribute('data-topo-sweep-dur'),
|
|
60
|
+
cssDur: g ? getComputedStyle(g).getPropertyValue('animation-duration').trim() : null,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
await browser.close();
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const b0 = await probe(0, 4); // 0 working → bucket 0 (8s)
|
|
68
|
+
const b1 = await probe(2, 4); // 2 working → bucket 1 (6s, R45 baseline)
|
|
69
|
+
const b2 = await probe(4, 5); // 4 working → bucket 2 (4s)
|
|
70
|
+
const b3 = await probe(7, 8); // 7 working → bucket 3 (3s)
|
|
71
|
+
|
|
72
|
+
const consistent = (probe, bucket, dur) =>
|
|
73
|
+
probe.bucket === String(bucket) && probe.dur === String(dur) && probe.cssDur === `${dur}s`;
|
|
74
|
+
|
|
75
|
+
const results = {
|
|
76
|
+
b0_bucket0_8s: consistent(b0, 0, 8),
|
|
77
|
+
b1_bucket1_6s: consistent(b1, 1, 6),
|
|
78
|
+
b2_bucket2_4s: consistent(b2, 2, 4),
|
|
79
|
+
b3_bucket3_3s: consistent(b3, 3, 3),
|
|
80
|
+
// dur strictly decreases as busy climbs
|
|
81
|
+
dur_monotonic:
|
|
82
|
+
Number(b0.dur) > Number(b1.dur) &&
|
|
83
|
+
Number(b1.dur) > Number(b2.dur) &&
|
|
84
|
+
Number(b2.dur) > Number(b3.dur),
|
|
85
|
+
// R45 baseline preservation: bucket 1 still 6s
|
|
86
|
+
baseline_preserved: b1.dur === '6',
|
|
87
|
+
};
|
|
88
|
+
const ok = Object.values(results).every(Boolean);
|
|
89
|
+
console.log(`${ok ? '✅' : '❌'} sweep busyness:`, JSON.stringify(results),
|
|
90
|
+
`\n b0(0w/4)=`, b0,
|
|
91
|
+
`\n b1(2w/4)=`, b1,
|
|
92
|
+
`\n b2(4w/5)=`, b2,
|
|
93
|
+
`\n b3(7w/8)=`, b3);
|
|
94
|
+
process.exit(ok ? 0 : 1);
|