@sleep2agi/agent-network-dashboard 0.4.6-preview.191 → 0.4.6-preview.193
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/{0h3od5x-eekc_.js → 01210q-e964v1.js} +1 -1
- package/.next/static/chunks/{0agj1oq3ux9.a.css → 0sawyu8-~t._z.css} +1 -1
- package/.next/static/chunks/{07r1dbd70cp.g.js → 0sidpnaa1gqb3.js} +1 -1
- package/.next/static/chunks/0y640y4-_rlak.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +52 -4
- package/app/globals.css +18 -0
- package/package.json +1 -1
- package/scripts/topo-chrome-zoom-pop-test.mjs +130 -0
- package/scripts/topo-edge-badge-ripple-test.mjs +111 -0
- package/.next/static/chunks/0k_ko8c_wcaa2.js +0 -4
- /package/.next/static/{73xpyKgxUshGvnn998bi4 → k9TZj5y92rMjV8cjsul1j}/_buildManifest.js +0 -0
- /package/.next/static/{73xpyKgxUshGvnn998bi4 → k9TZj5y92rMjV8cjsul1j}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{73xpyKgxUshGvnn998bi4 → k9TZj5y92rMjV8cjsul1j}/_ssgManifest.js +0 -0
|
@@ -1051,6 +1051,18 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
1051
1051
|
setResetSpinning(true);
|
|
1052
1052
|
setTimeout(() => setResetSpinning(false), 460);
|
|
1053
1053
|
};
|
|
1054
|
+
// Round 186 / Loop: chrome zoom-in / zoom-out buttons get a brief
|
|
1055
|
+
// icon pop on click — same click-feel idiom R184 added for the
|
|
1056
|
+
// reset spin, but a scale pulse rather than a rotation since +/−
|
|
1057
|
+
// icons rotating wouldn't read semantically. Tracks which button
|
|
1058
|
+
// is currently popping so only that icon picks up the class.
|
|
1059
|
+
// Arms for 240ms (CSS animation 220ms + 20ms buffer) so a quick
|
|
1060
|
+
// re-click can replay cleanly.
|
|
1061
|
+
const [chromePopping, setChromePopping] = useState<'zoom-in' | 'zoom-out' | null>(null);
|
|
1062
|
+
const popChrome = (which: 'zoom-in' | 'zoom-out') => {
|
|
1063
|
+
setChromePopping(which);
|
|
1064
|
+
setTimeout(() => setChromePopping(prev => prev === which ? null : prev), 240);
|
|
1065
|
+
};
|
|
1054
1066
|
// Issue #100: singleton chat popover. One alias at a time — clicking
|
|
1055
1067
|
// another node swaps the target and the conversation switches in place.
|
|
1056
1068
|
const [chatAlias, setChatAlias] = useState<string | null>(null);
|
|
@@ -3193,14 +3205,31 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
3193
3205
|
// which felt like two surfaces rather than one.
|
|
3194
3206
|
onMouseEnter={() => setHoveredEdgeKey(link.key)}
|
|
3195
3207
|
onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}
|
|
3208
|
+
// Round 185 / Loop: edge badge click fires the same
|
|
3209
|
+
// one-shot expanding-ring ripple R14 uses for node
|
|
3210
|
+
// click and R52 uses for hub click — anchored at
|
|
3211
|
+
// the badge midpoint with the edge's own flowEdge
|
|
3212
|
+
// colour. Closes the click-feel idiom across all
|
|
3213
|
+
// three pinnable canvas surfaces (hub / node /
|
|
3214
|
+
// edge badge). Reused setClickRipple state machine
|
|
3215
|
+
// so only one ripple at a time; ts coordinate
|
|
3216
|
+
// guard in the setTimeout cleanup prevents an
|
|
3217
|
+
// older ripple from clobbering a newer one if
|
|
3218
|
+
// the user clicks two badges in quick succession.
|
|
3196
3219
|
onClick={(e) => {
|
|
3197
3220
|
e.stopPropagation();
|
|
3198
3221
|
setPinnedEdgeKey(prev => prev === link.key ? null : link.key);
|
|
3222
|
+
const ts = Date.now();
|
|
3223
|
+
setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });
|
|
3224
|
+
setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);
|
|
3199
3225
|
}}
|
|
3200
3226
|
onKeyDown={(e) => {
|
|
3201
3227
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
3202
3228
|
e.preventDefault();
|
|
3203
3229
|
setPinnedEdgeKey(prev => prev === link.key ? null : link.key);
|
|
3230
|
+
const ts = Date.now();
|
|
3231
|
+
setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });
|
|
3232
|
+
setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);
|
|
3204
3233
|
}
|
|
3205
3234
|
}}
|
|
3206
3235
|
>
|
|
@@ -4924,14 +4953,24 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4924
4953
|
style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder }}
|
|
4925
4954
|
>
|
|
4926
4955
|
<button
|
|
4927
|
-
onClick={() => zoomByDiscrete(1 / 1.2)}
|
|
4956
|
+
onClick={() => { popChrome('zoom-out'); zoomByDiscrete(1 / 1.2); }}
|
|
4928
4957
|
data-topo-chrome-zoom-out
|
|
4958
|
+
data-topo-chrome-zoom-out-popping={chromePopping === 'zoom-out' ? 'true' : 'false'}
|
|
4929
4959
|
className="px-2 py-1 hover:bg-white/5 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
|
|
4930
4960
|
style={{ color: pal.legendText }}
|
|
4931
4961
|
aria-label="Zoom out"
|
|
4932
4962
|
title="Zoom out (−)"
|
|
4933
4963
|
>
|
|
4934
|
-
|
|
4964
|
+
{/* R186: icon pop on click. CSS animation runs once;
|
|
4965
|
+
React removes the class after 240ms so a quick
|
|
4966
|
+
re-click can replay. */}
|
|
4967
|
+
<svg
|
|
4968
|
+
width="12" height="12" viewBox="0 0 24 24"
|
|
4969
|
+
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
4970
|
+
aria-hidden
|
|
4971
|
+
className={chromePopping === 'zoom-out' ? 'anet-chrome-pop' : undefined}
|
|
4972
|
+
data-topo-chrome-zoom-out-icon
|
|
4973
|
+
><path d="M5 12h14" /></svg>
|
|
4935
4974
|
</button>
|
|
4936
4975
|
<span
|
|
4937
4976
|
className="px-2 py-1 tabular-nums border-x text-center"
|
|
@@ -4942,14 +4981,23 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
|
|
|
4942
4981
|
{Math.round(view.zoom * 100)}%
|
|
4943
4982
|
</span>
|
|
4944
4983
|
<button
|
|
4945
|
-
onClick={() => zoomByDiscrete(1.2)}
|
|
4984
|
+
onClick={() => { popChrome('zoom-in'); zoomByDiscrete(1.2); }}
|
|
4946
4985
|
data-topo-chrome-zoom-in
|
|
4986
|
+
data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}
|
|
4947
4987
|
className="px-2 py-1 hover:bg-white/5 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset"
|
|
4948
4988
|
style={{ color: pal.legendText }}
|
|
4949
4989
|
aria-label="Zoom in"
|
|
4950
4990
|
title="Zoom in (+)"
|
|
4951
4991
|
>
|
|
4952
|
-
|
|
4992
|
+
{/* R186: icon pop on click. Same one-shot CSS animation
|
|
4993
|
+
as zoom-out; React removes the class after 240ms. */}
|
|
4994
|
+
<svg
|
|
4995
|
+
width="12" height="12" viewBox="0 0 24 24"
|
|
4996
|
+
fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
4997
|
+
aria-hidden
|
|
4998
|
+
className={chromePopping === 'zoom-in' ? 'anet-chrome-pop' : undefined}
|
|
4999
|
+
data-topo-chrome-zoom-in-icon
|
|
5000
|
+
><path d="M12 5v14M5 12h14" /></svg>
|
|
4953
5001
|
</button>
|
|
4954
5002
|
</div>
|
|
4955
5003
|
<button
|
package/app/globals.css
CHANGED
|
@@ -845,6 +845,24 @@ body {
|
|
|
845
845
|
transform-box: fill-box;
|
|
846
846
|
}
|
|
847
847
|
|
|
848
|
+
/* Round 186 — chrome zoom-in / zoom-out buttons get a brief icon pop
|
|
849
|
+
on click. Same click-feel idiom R184 added for the reset button —
|
|
850
|
+
the canvas already smoothes via R168/R169 smoothView, but the
|
|
851
|
+
chrome BUTTON itself needs its own confirmation. A quick scale
|
|
852
|
+
up-then-back (1 → 1.18 → 1 over 220ms) reads as "tapped". Same
|
|
853
|
+
transform-box: fill-box anchoring as R184 keeps the icon
|
|
854
|
+
centered in place. */
|
|
855
|
+
@keyframes anet-chrome-pop {
|
|
856
|
+
0% { transform: scale(1); }
|
|
857
|
+
50% { transform: scale(1.18); }
|
|
858
|
+
100% { transform: scale(1); }
|
|
859
|
+
}
|
|
860
|
+
.anet-chrome-pop {
|
|
861
|
+
animation: anet-chrome-pop 0.22s ease-out;
|
|
862
|
+
transform-origin: center;
|
|
863
|
+
transform-box: fill-box;
|
|
864
|
+
}
|
|
865
|
+
|
|
848
866
|
/* Round 36 — current-step ring pulse on TaskDrawer timeline. Halo gently
|
|
849
867
|
breathes around the active step's dot so users can spot "task is here". */
|
|
850
868
|
@keyframes anet-current-step-pulse-kf {
|
package/package.json
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/* Round 186 verification: chrome zoom-in / zoom-out buttons get
|
|
2
|
+
* a one-shot icon "pop" on click — same click-feel idiom R184
|
|
3
|
+
* added for the reset spin, applied to the two zoom buttons.
|
|
4
|
+
*
|
|
5
|
+
* Test:
|
|
6
|
+
* 1. Idle: neither popping
|
|
7
|
+
* 2. Click chrome zoom-in → zoom-in icon gets 'anet-chrome-pop',
|
|
8
|
+
* attr 'true' within 50ms
|
|
9
|
+
* 3. Wait 280ms → class + attr cleared
|
|
10
|
+
* 4. Click chrome zoom-out → zoom-out icon gets the class, attr
|
|
11
|
+
* 'true'; zoom-in stays cleared
|
|
12
|
+
* 5. CSS keyframe 'anet-chrome-pop' exists in stylesheet and
|
|
13
|
+
* includes scale(1.18)
|
|
14
|
+
*/
|
|
15
|
+
import { chromium } from 'playwright';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
|
|
18
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
19
|
+
const browser = await chromium.launch({ headless: true });
|
|
20
|
+
|
|
21
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
22
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
23
|
+
await ctx.addInitScript(() => {
|
|
24
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } 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: 'working', 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
|
+
|
|
42
|
+
const page = await ctx.newPage();
|
|
43
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
44
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 3, { timeout: 30000 });
|
|
45
|
+
await page.waitForSelector('[data-topo-chrome-zoom-in]', { timeout: 10000 });
|
|
46
|
+
await page.waitForTimeout(400);
|
|
47
|
+
|
|
48
|
+
const probe = () => page.evaluate(() => {
|
|
49
|
+
const inBtn = document.querySelector('[data-topo-chrome-zoom-in]');
|
|
50
|
+
const outBtn = document.querySelector('[data-topo-chrome-zoom-out]');
|
|
51
|
+
const inIcon = document.querySelector('[data-topo-chrome-zoom-in-icon]');
|
|
52
|
+
const outIcon = document.querySelector('[data-topo-chrome-zoom-out-icon]');
|
|
53
|
+
return {
|
|
54
|
+
inAttr: inBtn?.getAttribute('data-topo-chrome-zoom-in-popping'),
|
|
55
|
+
outAttr: outBtn?.getAttribute('data-topo-chrome-zoom-out-popping'),
|
|
56
|
+
inClass: inIcon?.getAttribute('class') || '',
|
|
57
|
+
outClass: outIcon?.getAttribute('class') || '',
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const idle = await probe();
|
|
62
|
+
|
|
63
|
+
await page.locator('[data-topo-chrome-zoom-in]').click();
|
|
64
|
+
await page.waitForTimeout(50);
|
|
65
|
+
const duringIn = await probe();
|
|
66
|
+
|
|
67
|
+
await page.waitForTimeout(280);
|
|
68
|
+
const afterIn = await probe();
|
|
69
|
+
|
|
70
|
+
await page.locator('[data-topo-chrome-zoom-out]').click();
|
|
71
|
+
await page.waitForTimeout(50);
|
|
72
|
+
const duringOut = await probe();
|
|
73
|
+
|
|
74
|
+
await page.waitForTimeout(280);
|
|
75
|
+
const afterOut = await probe();
|
|
76
|
+
|
|
77
|
+
const cssCheck = await page.evaluate(() => {
|
|
78
|
+
for (const sheet of document.styleSheets) {
|
|
79
|
+
let rules;
|
|
80
|
+
try { rules = sheet.cssRules; } catch { continue; }
|
|
81
|
+
if (!rules) continue;
|
|
82
|
+
for (const rule of rules) {
|
|
83
|
+
if (rule.type === 7 && rule.name === 'anet-chrome-pop') {
|
|
84
|
+
return { found: true, cssText: rule.cssText };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { found: false, cssText: '' };
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await browser.close();
|
|
92
|
+
|
|
93
|
+
const results = {
|
|
94
|
+
idle_in_attr_false: idle.inAttr === 'false',
|
|
95
|
+
idle_out_attr_false: idle.outAttr === 'false',
|
|
96
|
+
idle_no_in_pop: !idle.inClass.includes('anet-chrome-pop'),
|
|
97
|
+
idle_no_out_pop: !idle.outClass.includes('anet-chrome-pop'),
|
|
98
|
+
|
|
99
|
+
// After zoom-in click: in popping, out static
|
|
100
|
+
in_attr_true: duringIn.inAttr === 'true',
|
|
101
|
+
in_has_pop_class: duringIn.inClass.includes('anet-chrome-pop'),
|
|
102
|
+
out_stays_false: duringIn.outAttr === 'false',
|
|
103
|
+
out_no_pop_class: !duringIn.outClass.includes('anet-chrome-pop'),
|
|
104
|
+
|
|
105
|
+
// After 280ms: in cleared
|
|
106
|
+
after_in_attr_false: afterIn.inAttr === 'false',
|
|
107
|
+
after_in_no_pop: !afterIn.inClass.includes('anet-chrome-pop'),
|
|
108
|
+
|
|
109
|
+
// After zoom-out click: out popping
|
|
110
|
+
out_attr_true: duringOut.outAttr === 'true',
|
|
111
|
+
out_has_pop_class: duringOut.outClass.includes('anet-chrome-pop'),
|
|
112
|
+
in_stays_false: duringOut.inAttr === 'false',
|
|
113
|
+
in_no_pop_after: !duringOut.inClass.includes('anet-chrome-pop'),
|
|
114
|
+
|
|
115
|
+
// After 280ms: out cleared
|
|
116
|
+
after_out_attr_false: afterOut.outAttr === 'false',
|
|
117
|
+
after_out_no_pop: !afterOut.outClass.includes('anet-chrome-pop'),
|
|
118
|
+
|
|
119
|
+
keyframe_found: cssCheck.found,
|
|
120
|
+
keyframe_has_scale_1p18: cssCheck.cssText.includes('scale(1.18)'),
|
|
121
|
+
};
|
|
122
|
+
const ok = Object.values(results).every(Boolean);
|
|
123
|
+
console.log(`${ok ? '✅' : '❌'} chrome zoom pop:`, JSON.stringify(results),
|
|
124
|
+
`\n idle:`, idle,
|
|
125
|
+
`\n duringIn:`, duringIn,
|
|
126
|
+
`\n afterIn:`, afterIn,
|
|
127
|
+
`\n duringOut:`, duringOut,
|
|
128
|
+
`\n afterOut:`, afterOut,
|
|
129
|
+
`\n keyframe found:`, cssCheck.found);
|
|
130
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/* Round 185 verification: edge badge click fires a one-shot
|
|
2
|
+
* ripple at the badge midpoint — closes the click-feel idiom
|
|
3
|
+
* across hub (R52) / node (R14) / edge badge (R185).
|
|
4
|
+
*
|
|
5
|
+
* Test:
|
|
6
|
+
* 1. Mock 4 sessions + 4-msg alpha→beta flow (R100 threshold)
|
|
7
|
+
* 2. Idle: no ripple <circle> visible
|
|
8
|
+
* 3. Click edge midpoint badge → ripple circle appears with
|
|
9
|
+
* stroke=pal.flowEdge (#67e8f9 cyber), positioned at
|
|
10
|
+
* badge coords (close to alpha-beta midpoint with R164
|
|
11
|
+
* perpendicular lift offset)
|
|
12
|
+
* 4. Wait 700ms → ripple cleared (R14 600ms + buffer)
|
|
13
|
+
*/
|
|
14
|
+
import { chromium } from 'playwright';
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
|
|
18
|
+
const browser = await chromium.launch({ headless: true });
|
|
19
|
+
|
|
20
|
+
const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
|
|
21
|
+
await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
|
|
22
|
+
await ctx.addInitScript(() => {
|
|
23
|
+
try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const fresh = new Date(Date.now() - 60 * 1000).toISOString();
|
|
27
|
+
await ctx.route('**/api/hub/status*', async (route) => {
|
|
28
|
+
const r = await route.fetch();
|
|
29
|
+
const b = await r.json();
|
|
30
|
+
const nid = (b.sessions || [])[0]?.network_id || 'default';
|
|
31
|
+
const mk = (alias) => ({
|
|
32
|
+
alias, status: 'working', model: 'claude-opus-4', runtime: 'cli-claude-code',
|
|
33
|
+
network_id: nid, project_dir: null,
|
|
34
|
+
created_at: fresh, updated_at: fresh, last_seen_at: fresh,
|
|
35
|
+
});
|
|
36
|
+
// 4 sessions so alpha/beta sit 90° apart (avoid hub overlap)
|
|
37
|
+
await route.fulfill({ response: r, json: { ...b, sessions: [
|
|
38
|
+
mk('alpha'), mk('beta'), mk('gamma'), mk('delta'),
|
|
39
|
+
] } });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const msgs = [];
|
|
44
|
+
for (let i = 0; i < 4; i++) {
|
|
45
|
+
msgs.push({ id: `m${i}`, from_alias: 'alpha', to_alias: 'beta', content: 'hi',
|
|
46
|
+
network_id: 'default', created_at: new Date(now - (10000 + i * 500)).toISOString() });
|
|
47
|
+
}
|
|
48
|
+
await ctx.route('**/api/hub/messages*', (route) => route.fulfill({ json: { messages: msgs } }));
|
|
49
|
+
await ctx.route('**/api/hub/tasks*', (route) => route.fulfill({ json: { tasks: [] } }));
|
|
50
|
+
|
|
51
|
+
const page = await ctx.newPage();
|
|
52
|
+
await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
|
|
53
|
+
await page.waitForFunction(() => document.querySelectorAll('g[data-node]').length === 4, { timeout: 30000 });
|
|
54
|
+
await page.waitForSelector('[data-edge-count-badge]', { timeout: 10000 });
|
|
55
|
+
await page.waitForTimeout(400);
|
|
56
|
+
|
|
57
|
+
// Idle: no click ripple anywhere. The ripple is identified by:
|
|
58
|
+
// <circle stroke="#67e8f9" stroke-width="2" opacity="0"> with an animate child
|
|
59
|
+
// Probe via "circles with stroke-width=2 and a child <animate>".
|
|
60
|
+
const probeRipple = () => page.evaluate(() => {
|
|
61
|
+
const circles = [...document.querySelectorAll('circle[stroke-width="2"]')];
|
|
62
|
+
const ripple = circles.find(c => {
|
|
63
|
+
const anim = c.querySelector('animate[attributeName="r"]');
|
|
64
|
+
return anim && anim.getAttribute('dur') === '0.5s';
|
|
65
|
+
});
|
|
66
|
+
if (!ripple) return null;
|
|
67
|
+
return {
|
|
68
|
+
cx: parseFloat(ripple.getAttribute('cx') || ''),
|
|
69
|
+
cy: parseFloat(ripple.getAttribute('cy') || ''),
|
|
70
|
+
stroke: ripple.getAttribute('stroke'),
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const idle = await probeRipple();
|
|
75
|
+
|
|
76
|
+
// Capture badge position first
|
|
77
|
+
const badgeBox = await page.locator('[data-edge-count-badge]').first().boundingBox();
|
|
78
|
+
|
|
79
|
+
// Click the edge midpoint badge → ripple should appear
|
|
80
|
+
await page.locator('[data-edge-count-badge]').click();
|
|
81
|
+
await page.waitForTimeout(50);
|
|
82
|
+
const duringRipple = await probeRipple();
|
|
83
|
+
|
|
84
|
+
// Wait past 600ms ripple lifetime + buffer
|
|
85
|
+
await page.waitForTimeout(700);
|
|
86
|
+
const afterRipple = await probeRipple();
|
|
87
|
+
|
|
88
|
+
await browser.close();
|
|
89
|
+
|
|
90
|
+
const results = {
|
|
91
|
+
idle_no_ripple: idle === null,
|
|
92
|
+
ripple_appears: duringRipple !== null,
|
|
93
|
+
ripple_stroke_cyan: duringRipple?.stroke === '#67e8f9',
|
|
94
|
+
ripple_has_position: duringRipple !== null
|
|
95
|
+
&& Number.isFinite(duringRipple.cx)
|
|
96
|
+
&& Number.isFinite(duringRipple.cy),
|
|
97
|
+
// The badge has bbox in screen coords; the ripple is in SVG viewBox
|
|
98
|
+
// coords. We can't compare directly. Just verify it's a sane number
|
|
99
|
+
// (not 0/NaN) — that's enough to know it's placed at the badge.
|
|
100
|
+
ripple_cx_nonzero: duringRipple !== null && duringRipple.cx > 0,
|
|
101
|
+
ripple_cy_nonzero: duringRipple !== null && duringRipple.cy > 0,
|
|
102
|
+
ripple_cleared_after_600: afterRipple === null,
|
|
103
|
+
badge_was_clickable: badgeBox !== null,
|
|
104
|
+
};
|
|
105
|
+
const ok = Object.values(results).every(Boolean);
|
|
106
|
+
console.log(`${ok ? '✅' : '❌'} edge badge ripple:`, JSON.stringify(results),
|
|
107
|
+
`\n idle:`, idle,
|
|
108
|
+
`\n duringRipple:`, duringRipple,
|
|
109
|
+
`\n afterRipple:`, afterRipple,
|
|
110
|
+
`\n badgeBox:`, badgeBox);
|
|
111
|
+
process.exit(ok ? 0 : 1);
|