@sleep2agi/agent-network-dashboard 0.5.1-preview.40 → 0.5.1-preview.42

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 +2 -2
  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/{06_k1dgr3isa2.js → 0_2.t7lf33cgr.js} +1 -1
  130. package/.next/static/chunks/{05.asnoxq2t39.js → 0zklaue5r5j3i.js} +1 -1
  131. package/.next/static/chunks/0~ace06dgycy0.js +4 -0
  132. package/.next/trace +2 -2
  133. package/.next/trace-build +1 -1
  134. package/app/components/TopoGraph.tsx +46 -0
  135. package/package.json +1 -1
  136. package/scripts/topo-edge-visible-linecap-test.mjs +89 -0
  137. package/scripts/topo-group-box-stroke-test.mjs +105 -0
  138. package/.next/static/chunks/0fxhdt5-198se.js +0 -4
  139. /package/.next/static/{PtUnxSzwOvGhpWPUawz9k → IcF-8xU_qK-YW8IVus8yf}/_buildManifest.js +0 -0
  140. /package/.next/static/{PtUnxSzwOvGhpWPUawz9k → IcF-8xU_qK-YW8IVus8yf}/_clientMiddlewareManifest.js +0 -0
  141. /package/.next/static/{PtUnxSzwOvGhpWPUawz9k → IcF-8xU_qK-YW8IVus8yf}/_ssgManifest.js +0 -0
@@ -3973,7 +3973,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
3973
3973
  stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}
3974
3974
  strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}
3975
3975
  strokeDasharray={(isPinned || isHovered) ? 'none' : '6 6'}
3976
+ /* Round 380 / Loop: cluster box stroke gets round
3977
+ linecap + round linejoin. Sibling SVG stroke-
3978
+ softening polish to R378 flow-rail linecap + R379
3979
+ minimap viewport linejoin — extends the family to
3980
+ the group cluster boundary box (grid layout only):
3981
+ R288 chrome icons strokeLinecap='round'
3982
+ R378 flow-rail dashes strokeLinecap='round'
3983
+ R380 group box dashes strokeLinecap='round' (this round)
3984
+ R379 viewport rect strokeLinejoin='round'
3985
+ R380 group box corners strokeLinejoin='round' (this round)
3986
+ Linecap rounds the R85 '6 6' marching-ants dash
3987
+ pills at rest — each 6 px dash gains a ~0.75 px
3988
+ round cap (sw=1.5 idle), reading as soft pills
3989
+ instead of sharp 6 × 1.5 px rectangles. Linejoin
3990
+ rounds the 4 sharp 90° corners (any state — solid
3991
+ or dashed); at sw=1.5 the join arc is ~0.75 px,
3992
+ matching R379 viewport vocabulary. Geometry-safe:
3993
+ stroke-* properties only affect paint, not bbox.
3994
+ The R51 sentinel 1.5/3 strokeWidth values stay
3995
+ intact (the overlap probe is gated to g[data-
3996
+ node], so this cluster-internal rect is invisible
3997
+ to it anyway). data-group-box-linecap + -linejoin
3998
+ attrs expose the values for tests. */
3999
+ strokeLinecap="round"
4000
+ strokeLinejoin="round"
3976
4001
  data-group-box-pinned={isPinned ? 'true' : 'false'}
4002
+ data-group-box-linecap="round"
4003
+ data-group-box-linejoin="round"
3977
4004
  // R85: ambient "marching ants" drift on the perimeter
3978
4005
  // when this group has at least one working member, and
3979
4006
  // neither pin nor hover is active (those treatments
@@ -4488,15 +4515,34 @@ export function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProp
4488
4515
  online chips to splice in additional properties
4489
4516
  beside Tailwind's). data-edge-flow-rail attr
4490
4517
  surfaces the path for test introspection. */}
4518
+ {/* Round 381 / Loop: edge visible flow path picks up
4519
+ strokeLinecap='round'. Sibling polish to R378
4520
+ flow-rail dashed linecap — both flow-element paths
4521
+ (visible primary + dashed secondary rail) now share
4522
+ 'round' linecap vocabulary. The visible path runs
4523
+ source-node → dest-node as one continuous line, so
4524
+ the dest-end is covered by the markerEnd arrow and
4525
+ the source-end usually sits inside the source-node
4526
+ circle. At certain alignments (post-zoom, post-
4527
+ layout-switch transitions), the source-end may peek
4528
+ out by a fraction of a px past the node edge —
4529
+ round caps render that overshoot as a smooth half-
4530
+ disc instead of a sharp rectangle. Pure paint
4531
+ refinement, geometry-safe (bbox of the stroke
4532
+ unchanged at the join with the arrow marker).
4533
+ data-edge-visible-linecap attr exposes the value
4534
+ for tests. */}
4491
4535
  <path
4492
4536
  d={path}
4493
4537
  fill="none"
4494
4538
  stroke={pal.flowEdge}
4495
4539
  strokeWidth={renderWidth}
4540
+ strokeLinecap="round"
4496
4541
  opacity={Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)}
4497
4542
  filter={isLight ? undefined : 'url(#topo-glow)'}
4498
4543
  markerEnd={`url(#${arrowId})`}
4499
4544
  data-edge-visible={link.key}
4545
+ data-edge-visible-linecap="round"
4500
4546
  style={{
4501
4547
  pointerEvents: 'none',
4502
4548
  transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network-dashboard",
3
- "version": "0.5.1-preview.40",
3
+ "version": "0.5.1-preview.42",
4
4
  "description": "Agent Network Dashboard — Web UI for managing AI Agent networks",
5
5
  "scripts": {
6
6
  "dev": "next dev",
@@ -0,0 +1,89 @@
1
+ /* Round 381 verification: edge visible flow path strokeLinecap='round'.
2
+ * Sibling SVG stroke-softening polish to R378 flow-rail linecap — both
3
+ * flow-element paths now share 'round' linecap. R380 group box closed
4
+ * dash-cap + corner softening; R381 closes the flow-path family at the
5
+ * visible primary path.
6
+ *
7
+ * Stroke-softening family snapshot post-R381:
8
+ * R288 chrome icons strokeLinecap='round'
9
+ * R378 flow-rail dashes strokeLinecap='round'
10
+ * R380 group box dashes strokeLinecap='round'
11
+ * R381 flow visible path strokeLinecap='round' (this round)
12
+ * R379 viewport rect strokeLinejoin='round'
13
+ * R380 group box corners strokeLinejoin='round'
14
+ *
15
+ * Contract:
16
+ * - [data-edge-visible] element stroke-linecap attr === 'round'.
17
+ * - data-edge-visible-linecap === 'round'.
18
+ * - Pre-R381 invariants:
19
+ * * fill='none', stroke=pal.flowEdge
20
+ * * markerEnd referencing arrow id
21
+ * * opacity-based freshness preserved (any value in 0..1)
22
+ * * transition list contains opacity + stroke-width + stroke
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
+ const browser = await chromium.launch({ headless: true });
29
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
30
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
31
+ await ctx.addInitScript(() => {
32
+ try { localStorage.setItem('anet-theme', 'cyber'); sessionStorage.setItem('anet_v3_auth', '1'); } catch {}
33
+ });
34
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
35
+ await ctx.route('**/api/hub/status*', async (route) => {
36
+ const r = await route.fetch();
37
+ const b = await r.json();
38
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
39
+ const mk = (alias) => ({
40
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
41
+ network_id: nid, project_dir: null,
42
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
43
+ });
44
+ await route.fulfill({ response: r, json: { ...b, sessions: [ mk('a'), mk('b') ] } });
45
+ });
46
+ const now = Date.now();
47
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [
48
+ { id: 'm1', from_alias: 'a', to_alias: 'b', content: 'ping',
49
+ network_id: 'default', created_at: new Date(now - 5000).toISOString() },
50
+ ] } }));
51
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
52
+
53
+ const page = await ctx.newPage();
54
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
55
+ await page.waitForSelector('[data-edge-visible]', { timeout: 15000 });
56
+ await page.waitForTimeout(300);
57
+
58
+ const probe = await page.evaluate(() => {
59
+ const p = document.querySelector('[data-edge-visible]');
60
+ const cs = p ? getComputedStyle(p) : null;
61
+ return {
62
+ linecapAttr: p?.getAttribute('stroke-linecap') ?? null,
63
+ linecapData: p?.getAttribute('data-edge-visible-linecap') ?? null,
64
+ fillAttr: p?.getAttribute('fill') ?? null,
65
+ markerEndAttr: p?.getAttribute('marker-end') ?? null,
66
+ transition: cs?.transition ?? null,
67
+ linkKey: p?.getAttribute('data-edge-visible') ?? null,
68
+ };
69
+ });
70
+
71
+ await browser.close();
72
+
73
+ const hasTrans = (s, prop) =>
74
+ new RegExp(`${prop}\\s+\\d*\\.?\\d*s|${prop}\\s+\\d+ms`, 'i').test(s || '');
75
+
76
+ const results = {
77
+ linecap_round: probe.linecapAttr === 'round',
78
+ data_linecap_round: probe.linecapData === 'round',
79
+ fill_none: probe.fillAttr === 'none',
80
+ marker_end_arrow: /^url\(#topo-arrow-/.test(probe.markerEndAttr || ''),
81
+ trans_has_opacity: hasTrans(probe.transition, 'opacity'),
82
+ trans_has_stroke_w: hasTrans(probe.transition, 'stroke-width'),
83
+ trans_has_stroke: hasTrans(probe.transition, 'stroke'),
84
+ link_key_present: (probe.linkKey || '').length > 0,
85
+ };
86
+ const ok = Object.values(results).every(Boolean);
87
+ console.log(`${ok ? '✅' : '❌'} edge visible path strokeLinecap='round':`, JSON.stringify(results),
88
+ '\n probe:', probe);
89
+ process.exit(ok ? 0 : 1);
@@ -0,0 +1,105 @@
1
+ /* Round 380 verification: cluster box stroke gets round linecap +
2
+ * round linejoin. Sibling SVG stroke-softening polish to R378 flow-
3
+ * rail linecap + R379 minimap viewport linejoin — extends the family
4
+ * to the group cluster boundary box (grid layout only).
5
+ *
6
+ * Family snapshot post-R380:
7
+ * R288 chrome icons strokeLinecap='round'
8
+ * R378 flow-rail dashes strokeLinecap='round'
9
+ * R380 group box dashes strokeLinecap='round' (this round)
10
+ * R379 viewport rect strokeLinejoin='round'
11
+ * R380 group box corners strokeLinejoin='round' (this round)
12
+ *
13
+ * Contract (cyber, grid layout):
14
+ * - [data-group-box-pinned] elements have stroke-linecap='round'
15
+ * + stroke-linejoin='round'.
16
+ * - data-group-box-linecap + data-group-box-linejoin === 'round'.
17
+ * - Pre-R380 invariants:
18
+ * * strokeWidth=1.5 at rest (R68 ladder)
19
+ * * strokeDasharray='6 6' at rest (R85 marching ants)
20
+ * * data-group-box-pinned 'false' at rest
21
+ * * data-group-box-lifted 'false' at rest
22
+ */
23
+ import { chromium } from 'playwright';
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const TOKEN = JSON.parse(readFileSync('/home/vansin/.anet/config.json', 'utf8')).token;
27
+ const browser = await chromium.launch({ headless: true });
28
+ const ctx = await browser.newContext({ viewport: { width: 1500, height: 1500 } });
29
+ await ctx.addCookies([{ name: 'anet_dashboard_session', value: `v3:${TOKEN}`, domain: '127.0.0.1', path: '/' }]);
30
+ await ctx.addInitScript(() => {
31
+ try {
32
+ localStorage.setItem('anet-theme', 'cyber');
33
+ sessionStorage.setItem('anet_v3_auth', '1');
34
+ localStorage.setItem('anet-layout', JSON.stringify('grid'));
35
+ } catch {}
36
+ });
37
+ const fresh = new Date(Date.now() - 60 * 1000).toISOString();
38
+ await ctx.route('**/api/hub/status*', async (route) => {
39
+ const r = await route.fetch();
40
+ const b = await r.json();
41
+ const nid = (b.sessions || [])[0]?.network_id || 'default';
42
+ const mk = (alias) => ({
43
+ alias, status: 'working', model: 'claude-opus-4', runtime: 'claude-code-cli',
44
+ network_id: nid, project_dir: null,
45
+ created_at: fresh, updated_at: fresh, last_seen_at: fresh,
46
+ });
47
+ // Cluster groups via prefix alias → group boxes render.
48
+ await route.fulfill({ response: r, json: { ...b, sessions: [
49
+ mk('alpha-1'), mk('alpha-2'), mk('alpha-3'),
50
+ mk('beta-1'), mk('beta-2'),
51
+ ] } });
52
+ });
53
+ await ctx.route('**/api/hub/messages*', (r) => r.fulfill({ json: { messages: [] } }));
54
+ await ctx.route('**/api/hub/tasks*', (r) => r.fulfill({ json: { tasks: [] } }));
55
+
56
+ const page = await ctx.newPage();
57
+ await page.goto('http://127.0.0.1:3000/', { waitUntil: 'domcontentloaded' });
58
+ await page.waitForSelector('[data-topo-chrome-layout="grid"]', { timeout: 15000 });
59
+ const layoutActive = await page.getAttribute('[data-topo-chrome-layout="grid"]', 'data-topo-chrome-layout-active');
60
+ if (layoutActive !== 'true') await page.click('[data-topo-chrome-layout="grid"]');
61
+ await page.waitForSelector('[data-group-box-pinned]', { timeout: 10000 });
62
+ await page.waitForTimeout(400);
63
+
64
+ const probe = await page.evaluate(() => {
65
+ const boxes = Array.from(document.querySelectorAll('[data-group-box-pinned]'));
66
+ return boxes.map(el => ({
67
+ linecapAttr: el.getAttribute('stroke-linecap'),
68
+ linejoinAttr: el.getAttribute('stroke-linejoin'),
69
+ linecapData: el.getAttribute('data-group-box-linecap'),
70
+ linejoinData: el.getAttribute('data-group-box-linejoin'),
71
+ strokeWidth: el.getAttribute('stroke-width'),
72
+ strokeDasharray: el.getAttribute('stroke-dasharray'),
73
+ pinnedAttr: el.getAttribute('data-group-box-pinned'),
74
+ liftedAttr: el.getAttribute('data-group-box-lifted'),
75
+ }));
76
+ });
77
+
78
+ await browser.close();
79
+
80
+ const allOk = probe.length >= 1 && probe.every(b =>
81
+ b.linecapAttr === 'round'
82
+ && b.linejoinAttr === 'round'
83
+ && b.linecapData === 'round'
84
+ && b.linejoinData === 'round'
85
+ && b.strokeWidth === '1.5' // R68 rest
86
+ && b.strokeDasharray === '6 6' // R85 rest
87
+ && b.pinnedAttr === 'false'
88
+ && b.liftedAttr === 'false'
89
+ );
90
+
91
+ const results = {
92
+ boxes_rendered: probe.length >= 1,
93
+ all_linecap_round: probe.every(b => b.linecapAttr === 'round'),
94
+ all_linejoin_round: probe.every(b => b.linejoinAttr === 'round'),
95
+ all_data_linecap: probe.every(b => b.linecapData === 'round'),
96
+ all_data_linejoin: probe.every(b => b.linejoinData === 'round'),
97
+ all_strokew_1_5_rest: probe.every(b => b.strokeWidth === '1.5'),
98
+ all_dash_6_6_rest: probe.every(b => b.strokeDasharray === '6 6'),
99
+ all_pinned_false: probe.every(b => b.pinnedAttr === 'false'),
100
+ all_lifted_false: probe.every(b => b.liftedAttr === 'false'),
101
+ };
102
+ const ok = allOk && Object.values(results).every(Boolean);
103
+ console.log(`${ok ? '✅' : '❌'} group box strokeLinecap + Linejoin = round:`, JSON.stringify(results),
104
+ '\n boxes:', probe.map(b => ({ linecap: b.linecapAttr, linejoin: b.linejoinAttr, sw: b.strokeWidth, dash: b.strokeDasharray })));
105
+ process.exit(ok ? 0 : 1);