@sleep2agi/agent-network-dashboard 0.5.1-preview.27 → 0.5.1-preview.29

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.
Files changed (141) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +4 -4
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/admin.html +1 -1
  21. package/.next/server/app/admin.rsc +1 -1
  22. package/.next/server/app/admin.segments/_full.segment.rsc +1 -1
  23. package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
  24. package/.next/server/app/admin.segments/_index.segment.rsc +1 -1
  25. package/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
  27. package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
  28. package/.next/server/app/index.html +2 -2
  29. package/.next/server/app/index.rsc +2 -2
  30. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  31. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  32. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  33. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  34. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  35. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/login.html +2 -2
  37. package/.next/server/app/login.rsc +2 -2
  38. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  39. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  40. package/.next/server/app/login.segments/_index.segment.rsc +1 -1
  41. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  42. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  43. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  44. package/.next/server/app/logs.html +1 -1
  45. package/.next/server/app/logs.rsc +1 -1
  46. package/.next/server/app/logs.segments/_full.segment.rsc +1 -1
  47. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  48. package/.next/server/app/logs.segments/_index.segment.rsc +1 -1
  49. package/.next/server/app/logs.segments/_tree.segment.rsc +1 -1
  50. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
  51. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  52. package/.next/server/app/messages.html +1 -1
  53. package/.next/server/app/messages.rsc +1 -1
  54. package/.next/server/app/messages.segments/_full.segment.rsc +1 -1
  55. package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
  56. package/.next/server/app/messages.segments/_index.segment.rsc +1 -1
  57. package/.next/server/app/messages.segments/_tree.segment.rsc +1 -1
  58. package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
  59. package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
  60. package/.next/server/app/node.html +1 -1
  61. package/.next/server/app/node.rsc +1 -1
  62. package/.next/server/app/node.segments/_full.segment.rsc +1 -1
  63. package/.next/server/app/node.segments/_head.segment.rsc +1 -1
  64. package/.next/server/app/node.segments/_index.segment.rsc +1 -1
  65. package/.next/server/app/node.segments/_tree.segment.rsc +1 -1
  66. package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
  67. package/.next/server/app/node.segments/node.segment.rsc +1 -1
  68. package/.next/server/app/nodes.html +1 -1
  69. package/.next/server/app/nodes.rsc +1 -1
  70. package/.next/server/app/nodes.segments/_full.segment.rsc +1 -1
  71. package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
  72. package/.next/server/app/nodes.segments/_index.segment.rsc +1 -1
  73. package/.next/server/app/nodes.segments/_tree.segment.rsc +1 -1
  74. package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
  75. package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
  76. package/.next/server/app/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app/server-logs.html +1 -1
  78. package/.next/server/app/server-logs.rsc +1 -1
  79. package/.next/server/app/server-logs.segments/_full.segment.rsc +1 -1
  80. package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
  81. package/.next/server/app/server-logs.segments/_index.segment.rsc +1 -1
  82. package/.next/server/app/server-logs.segments/_tree.segment.rsc +1 -1
  83. package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
  84. package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
  85. package/.next/server/app/settings/networks.html +1 -1
  86. package/.next/server/app/settings/networks.rsc +1 -1
  87. package/.next/server/app/settings/networks.segments/_full.segment.rsc +1 -1
  88. package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
  89. package/.next/server/app/settings/networks.segments/_index.segment.rsc +1 -1
  90. package/.next/server/app/settings/networks.segments/_tree.segment.rsc +1 -1
  91. package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
  92. package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
  93. package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
  94. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/settings/tokens.html +1 -1
  96. package/.next/server/app/settings/tokens.rsc +1 -1
  97. package/.next/server/app/settings/tokens.segments/_full.segment.rsc +1 -1
  98. package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
  99. package/.next/server/app/settings/tokens.segments/_index.segment.rsc +1 -1
  100. package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +1 -1
  101. package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
  102. package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
  103. package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
  104. package/.next/server/app/settings.html +2 -2
  105. package/.next/server/app/settings.rsc +2 -2
  106. package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
  107. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  108. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  109. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  110. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  111. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  112. package/.next/server/app/tasks.html +1 -1
  113. package/.next/server/app/tasks.rsc +1 -1
  114. package/.next/server/app/tasks.segments/_full.segment.rsc +1 -1
  115. package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
  116. package/.next/server/app/tasks.segments/_index.segment.rsc +1 -1
  117. package/.next/server/app/tasks.segments/_tree.segment.rsc +1 -1
  118. package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
  119. package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
  120. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +1 -1
  121. package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
  122. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
  123. package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
  124. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
  125. package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
  126. package/.next/server/middleware-build-manifest.js +3 -3
  127. package/.next/server/pages/404.html +1 -1
  128. package/.next/server/pages/500.html +1 -1
  129. package/.next/static/chunks/{0feqe3ryt~d-9.js → 00tcam64m5~rc.js} +1 -1
  130. package/.next/static/chunks/{11xaivtbslv7x.js → 0iwr.h1y0btzg.js} +1 -1
  131. package/.next/static/chunks/0u5t78m99~v0s.js +4 -0
  132. package/.next/trace +2 -2
  133. package/.next/trace-build +1 -1
  134. package/app/components/TopoGraph.tsx +43 -1
  135. package/package.json +1 -1
  136. package/scripts/topo-edge-badge-stroke-test.mjs +92 -0
  137. package/scripts/topo-more-flows-fontweight-test.mjs +103 -0
  138. package/.next/static/chunks/10p.1rpol0p3d.js +0 -4
  139. /package/.next/static/{GqeKDPqKv8MEUTK7SBLN3 → UoaOhm2QjMtDbdMc0tbP9}/_buildManifest.js +0 -0
  140. /package/.next/static/{GqeKDPqKv8MEUTK7SBLN3 → UoaOhm2QjMtDbdMc0tbP9}/_clientMiddlewareManifest.js +0 -0
  141. /package/.next/static/{GqeKDPqKv8MEUTK7SBLN3 → UoaOhm2QjMtDbdMc0tbP9}/_ssgManifest.js +0 -0
@@ -4776,14 +4776,37 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
4776
4776
  R251 closes the per-edge surface theme-toggle
4777
4777
  smoothness — every theme-driven property on
4778
4778
  every edge element now eases under cyber↔light. */}
4779
+ {/* Round 367 / Loop: edge midpoint badge rest
4780
+ stroke-width 1 → 1.25. Sibling visual-weight
4781
+ bump family (7th canvas anchor now):
4782
+ R287 minimap viewport stroke 1 → 1.5
4783
+ R295 legend swatch base radius 5.5 → 6
4784
+ R359 recent-row pip base radius 1.6 → 1.8
4785
+ R360 hub digit fontSize 11 → 12
4786
+ R361 edge-badge digit fontSize 10 → 11
4787
+ R365 hub-highlight base radius 5 → 5.5
4788
+ R367 edge-badge rest stroke 1 → 1.25 (this round)
4789
+ Cold edge badges gain ~25 % stroke presence
4790
+ (1.25/1.0 = 1.25). Stays clear of the R51
4791
+ overlap-test sentinel values (1.5 / 3 reserved
4792
+ for node strokes — the test selector is gated
4793
+ to g[data-node] ancestors so this edge-internal
4794
+ circle is invisible to that probe anyway, but
4795
+ 1.25 is a safe non-sentinel value regardless).
4796
+ R188 transition list 'stroke-width 300ms ease-
4797
+ out' still smoothes the hot/pin flip — now
4798
+ 1.25 → 2 instead of 1 → 2, same ease pace.
4799
+ data-edge-badge-stroke-width-rest exposes the
4800
+ new baseline for tests. */}
4779
4801
  <circle
4780
4802
  cx={badgeX} cy={badgeY}
4781
4803
  r={isHoveredEdge || isPinned ? 10.5 : 9}
4782
4804
  fill={pal.legendBox.fill}
4783
4805
  stroke={isPinned ? pal.legendHeadline : isHot ? hotStroke : pal.flowEdge}
4784
- strokeWidth={isPinned ? 2 : isHot ? 2 : 1}
4806
+ strokeWidth={isPinned ? 2 : isHot ? 2 : 1.25}
4785
4807
  opacity={isLight ? 0.95 : 0.82}
4786
4808
  data-edge-badge-lifted={(isHoveredEdge || isPinned) ? 'true' : 'false'}
4809
+ data-edge-badge-stroke-width-rest="1.25"
4787
4810
  style={{ transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out' }}
4788
4811
  />
4789
4812
  {/* Round 224 / Loop: edge badge text gains the 4th
@@ -7463,6 +7486,23 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7463
7486
  surface. transition list extends letter-spacing
7464
7487
  200ms ease-out alongside the existing opacity/
7465
7488
  fill easings. */}
7489
+ {/* Round 368 / Loop: `+N more flows` footer text gains
7490
+ fontWeight=500 (font-medium tier). Sibling small-
7491
+ text fw lift family with R363 recent-row alias
7492
+ + R364 legend-row label + R366 group-label count
7493
+ — all four lifts share the same theory: at small
7494
+ fontSize (9-11 px) against panel chrome, SVG-
7495
+ default fw 400 sits at the legibility floor;
7496
+ fw 500 brings the glyph into the deliberate-data
7497
+ band. fontStyle=italic + opacity 0.55 rest + R325
7498
+ letterSpacing 0.2 baseline + R344 hover-spread
7499
+ 0.2 → 0.3 + R195 cyan fill on hover all preserved
7500
+ — the fw bump just thickens the italic stroke.
7501
+ Hover-state punch (R195 fill + R325 opacity 0.55
7502
+ → 0.85 + R344 letter-spacing + R133 underline)
7503
+ stays as is, so the rest-vs-hover delta still
7504
+ reads clearly. data-recent-panel-more-font-weight
7505
+ attr exposes the value for tests. */}
7466
7506
  <text
7467
7507
  x="115" y="82"
7468
7508
  textAnchor="middle"
@@ -7470,11 +7510,13 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
7470
7510
  fontSize="9"
7471
7511
  fontFamily="monospace"
7472
7512
  fontStyle="italic"
7513
+ fontWeight="500"
7473
7514
  letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}
7474
7515
  opacity={hoveredRecentMore ? 0.85 : 0.55}
7475
7516
  textDecoration={hoveredRecentMore ? 'underline' : 'none'}
7476
7517
  data-recent-panel-more={moreCount}
7477
7518
  data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}
7519
+ data-recent-panel-more-font-weight="500"
7478
7520
  style={{ transition: 'opacity 150ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out' }}
7479
7521
  >
7480
7522
  {`+ ${moreCount}`}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.1-preview.27",
3
+ "version": "0.5.1-preview.29",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -0,0 +1,92 @@
1
+ /* Round 367 verification: edge midpoint badge rest stroke-width
2
+ * 1 → 1.25. Sibling visual-weight bump (7th canvas anchor in the
3
+ * R287/R295/R359/R360/R361/R365/R367 family).
4
+ *
5
+ * Cold edge badges gain ~25 % stroke presence. Stays clear of the
6
+ * R51 overlap-test sentinel values (1.5 / 3 reserved for node
7
+ * strokes — the test selector is gated to g[data-node] ancestors
8
+ * so this edge-internal circle is invisible to that probe anyway).
9
+ * R188 transition list 'stroke-width 300ms ease-out' still smoothes
10
+ * the hot/pin flip — now 1.25 → 2 instead of 1 → 2.
11
+ *
12
+ * Contract (cyber, fixture with cold flow):
13
+ * - Edge badge circle stroke-width attr === '1.25'.
14
+ * - data-edge-badge-stroke-width-rest === '1.25'.
15
+ * - data-edge-badge-lifted === 'false' (rest, not hovered/pinned).
16
+ * - data-edge-badge-text-pin === 'false' (cold flow, link.count<10).
17
+ * - Inline transition contains 'stroke-width' (R188 preserved).
18
+ * - Pre-R367 invariants: r=9 (cold, R164), fill=pal.legendBox.fill,
19
+ * stroke=pal.flowEdge (cold), opacity 0.82 (cyber).
20
+ */
21
+ import { chromium } from 'playwright';
22
+ import { readFileSync } from 'node:fs';
23
+
24
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
25
+ const browser = await chromium.launch({ headless: true });
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
32
+ await ctx.route('**/api/hub/status*', async (route) => {
33
+ const r = await route.fetch();
34
+ const b = await r.json();
35
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
36
+ const mk = (alias) => ({
37
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
38
+ network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ });
41
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
42
+ });
43
+ // 1 message → 1 cold flow (count=1, well below R129 isHot=10).
44
+ const now = Date.now();
45
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
46
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
47
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
48
+ ] } }));
49
+ await ctx.route('**/api/hub/tasks*', (r) => r.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.waitForSelector('[data-edge-badge-stroke-width-rest]', { timeout: 15000 });
54
+ await page.waitForTimeout(300);
55
+
56
+ const probe = await page.evaluate(() => {
57
+ // Probe the cold/rest edge badge circle (the parent of data-edge-badge-text
58
+ // is the <g> wrapping circle + text; we want the circle sibling).
59
+ const circle = document.querySelector('[data-edge-badge-stroke-width-rest]');
60
+ const cs = circle ? getComputedStyle(circle) : null;
61
+ // Find the text in the same group to check pin state.
62
+ const grp = circle?.parentElement;
63
+ const text = grp?.querySelector('[data-edge-badge-text]');
64
+ return {
65
+ strokeWidthAttr: circle?.getAttribute('stroke-width') ?? null,
66
+ strokeWidthData: circle?.getAttribute('data-edge-badge-stroke-width-rest') ?? null,
67
+ liftedAttr: circle?.getAttribute('data-edge-badge-lifted') ?? null,
68
+ rAttr: circle?.getAttribute('r') ?? null,
69
+ opacityAttr: circle?.getAttribute('opacity') ?? null,
70
+ transition: cs?.transition ?? null,
71
+ pinAttr: text?.getAttribute('data-edge-badge-text-pin') ?? null,
72
+ };
73
+ });
74
+
75
+ await browser.close();
76
+
77
+ const hasTrans = (s, prop) =>
78
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
79
+
80
+ const results = {
81
+ stroke_width_1_25: probe.strokeWidthAttr === '1.25',
82
+ data_stroke_1_25: probe.strokeWidthData === '1.25',
83
+ lifted_false_rest: probe.liftedAttr === 'false',
84
+ r_9_cold: probe.rAttr === '9', // R164 cold
85
+ opacity_0_82_cyber: probe.opacityAttr === '0.82', // R251 cyber theme
86
+ trans_has_strokew: hasTrans(probe.transition, 'stroke-width'), // R188
87
+ cold_flow_not_hot: probe.pinAttr === 'false', // R220 isHot/isPinned both false
88
+ };
89
+ const ok = Object.values(results).every(Boolean);
90
+ console.log(`${ok ? '✅' : '❌'} edge badge stroke 1 → 1.25:`, JSON.stringify(results),
91
+ '\n probe:', probe);
92
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,103 @@
1
+ /* Round 368 verification: recent-panel '+N more flows' footer text
2
+ * fontWeight 400 → 500. Sibling small-text fw lift family with R363
3
+ * recent-row alias + R364 legend-row label + R366 group-label count
4
+ * — all four lifts thicken small-fontSize text (9-11 px) against
5
+ * panel chrome from SVG-default 400 to the font-medium tier (500).
6
+ *
7
+ * The footer only renders when flowLinks.length > 3 (the panel shows
8
+ * the 3 most recent + a footer link to /messages for the overflow).
9
+ *
10
+ * Contract (cyber, 5+ flow links so the footer mounts):
11
+ * - data-recent-panel-more text element font-weight === '500'.
12
+ * - data-recent-panel-more-font-weight === '500'.
13
+ * - Pre-R368 invariants on the same element:
14
+ * * fontSize='9' + fontFamily='monospace' + fontStyle='italic'
15
+ * * letter-spacing='0.2' (R325 rest baseline)
16
+ * * opacity='0.55' (rest, R325 era)
17
+ * * inline transition lists opacity + letter-spacing + fill
18
+ * * data-recent-panel-more attr surfaces the count
19
+ * * data-recent-panel-more-unit tspan with opacity 0.7 (R340)
20
+ */
21
+ import { chromium } from 'playwright';
22
+ import { readFileSync } from 'node:fs';
23
+
24
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
25
+ const browser = await chromium.launch({ headless: true });
26
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
27
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
28
+ await ctx.addInitScript(() => {
29
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
30
+ });
31
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
32
+ await ctx.route('**/api/hub/status*', async (route) => {
33
+ const r = await route.fetch();
34
+ const b = await r.json();
35
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
36
+ const mk = (alias) => ({
37
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
38
+ network_id: nid, project_dir: null,
39
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
40
+ });
41
+ await route.fulfill({ response: r, json: { ...b, sessions: [
42
+ mk('a'), mk('b'), mk('c'), mk('d'), mk('e'),
43
+ ] } });
44
+ });
45
+ // 5 distinct flow links → panel shows 3 + footer "+ 2 more flows".
46
+ const now = Date.now();
47
+ const pairs = [['a','b'], ['c','d'], ['a','c'], ['b','d'], ['e','a']];
48
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages:
49
+ pairs.map(([from, to], idx) => ({
50
+ id: `m${idx}`, from_alias: from, to_alias: to, content: `ping ${idx}`,
51
+ network_id: 'default',
52
+ created_at: new Date(now - (5000 + idx * 1000)).toISOString(),
53
+ }))
54
+ } }));
55
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
56
+
57
+ const page = await ctx.newPage();
58
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
59
+ await page.waitForSelector('[data-recent-panel-more]', { timeout: 15000 });
60
+ await page.waitForTimeout(300);
61
+
62
+ const probe = await page.evaluate(() => {
63
+ const t = document.querySelector('[data-recent-panel-more]');
64
+ const unit = document.querySelector('[data-recent-panel-more-unit]');
65
+ const cs = t ? getComputedStyle(t) : null;
66
+ return {
67
+ fontWeightAttr: t?.getAttribute('font-weight') ?? null,
68
+ fontWeightData: t?.getAttribute('data-recent-panel-more-font-weight') ?? null,
69
+ fontSizeAttr: t?.getAttribute('font-size') ?? null,
70
+ fontFamily: t?.getAttribute('font-family') ?? null,
71
+ fontStyle: t?.getAttribute('font-style') ?? null,
72
+ letterSpacing: t?.getAttribute('letter-spacing') ?? null,
73
+ opacityAttr: t?.getAttribute('opacity') ?? null,
74
+ moreCount: t?.getAttribute('data-recent-panel-more') ?? null,
75
+ hoveredAttr: t?.getAttribute('data-recent-panel-more-hovered') ?? null,
76
+ transition: cs?.transition ?? null,
77
+ unitOpacity: unit?.getAttribute('opacity') ?? null,
78
+ };
79
+ });
80
+
81
+ await browser.close();
82
+
83
+ const hasTrans = (s, prop) =>
84
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
85
+
86
+ const results = {
87
+ font_weight_500: probe.fontWeightAttr === '500',
88
+ data_fw_500: probe.fontWeightData === '500',
89
+ font_size_9: probe.fontSizeAttr === '9',
90
+ font_family_mono: /monospace/i.test(probe.fontFamily || ''),
91
+ font_style_italic: probe.fontStyle === 'italic', // pre-R368
92
+ letter_spacing_0_2: probe.letterSpacing === '0.2', // R325 rest
93
+ opacity_0_55_rest: probe.opacityAttr === '0.55', // R325 era rest
94
+ more_count_2: probe.moreCount === '2', // 5 links - 3 shown
95
+ hovered_false_rest: probe.hoveredAttr === 'false',
96
+ trans_has_letterspacing: hasTrans(probe.transition, 'letter-spacing'),
97
+ trans_has_fill: hasTrans(probe.transition, 'fill'),
98
+ unit_opacity_0_7: probe.unitOpacity === '0.7', // R340 unit split
99
+ };
100
+ const ok = Object.values(results).every(Boolean);
101
+ console.log(`${ok ? '✅' : '❌'} more-flows footer fw 400 → 500:`, JSON.stringify(results),
102
+ '\n probe:', probe);
103
+ process.exit(ok ? 0 : 1);