@cotal-ai/cli 0.9.0 → 0.9.1
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/dist/web/graph.js +22 -6
- package/package.json +3 -3
package/dist/web/graph.js
CHANGED
|
@@ -302,14 +302,21 @@
|
|
|
302
302
|
for (const e of edges.values()) { const h = hubs.get(e.chan); if (!filter.chat || !h || isHidden(h) || e.heat <= 0.02 || isHiddenMember(e)) continue; const lit = inFan(e, f); ctx.beginPath(); ctx.moveTo(e.a.x, e.a.y); ctx.lineTo(h.x, h.y); ctx.strokeStyle = rgba(MODE.chat, Math.min(0.55, e.heat * 0.55) * (lit ? 1 : 0.35)); ctx.lineWidth = 1 + e.heat * 1.6; ctx.stroke(); }
|
|
303
303
|
ctx.globalCompositeOperation = "source-over"; ctx.globalAlpha = 1;
|
|
304
304
|
}
|
|
305
|
+
// Perpendicular control point for a DM arc, computed in a direction-INDEPENDENT canonical order (by id)
|
|
306
|
+
// so the traveling comet rides the exact same dashed curve no matter which peer sent this message — a
|
|
307
|
+
// reply in the opposite direction otherwise flips the bulge to the wrong side and the comet peels off.
|
|
308
|
+
function dmControl(a, b) {
|
|
309
|
+
const [p, q] = a.id < b.id ? [a, b] : [b, a];
|
|
310
|
+
const mx = (p.x + q.x) / 2, my = (p.y + q.y) / 2, nx = -(q.y - p.y), ny = q.x - p.x, len = Math.hypot(nx, ny) || 1;
|
|
311
|
+
return { x: mx + (nx / len) * 24, y: my + (ny / len) * 24 };
|
|
312
|
+
}
|
|
305
313
|
function drawDmEdges() {
|
|
306
314
|
if (!filter.unicast) return; // the `direct` chip filters DM traffic — including the persistent DM curves
|
|
307
315
|
ctx.setLineDash([3, 4]);
|
|
308
316
|
for (const d of dms.values()) {
|
|
309
317
|
if (isHidden(d.a) || isHidden(d.b)) continue;
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
ctx.beginPath(); ctx.moveTo(d.a.x, d.a.y); ctx.quadraticCurveTo(cx, cy, d.b.x, d.b.y);
|
|
318
|
+
const c = dmControl(d.a, d.b);
|
|
319
|
+
ctx.beginPath(); ctx.moveTo(d.a.x, d.a.y); ctx.quadraticCurveTo(c.x, c.y, d.b.x, d.b.y);
|
|
313
320
|
ctx.strokeStyle = rgba(MODE.unicast, 0.5 + d.heat * 0.45); ctx.lineWidth = 1.7 + d.heat * 1.6; ctx.stroke();
|
|
314
321
|
}
|
|
315
322
|
ctx.setLineDash([]); ctx.globalAlpha = 1;
|
|
@@ -367,7 +374,7 @@
|
|
|
367
374
|
// a fan-out comet to a hidden offline member would fly to empty space — still tick + retire it, just don't draw
|
|
368
375
|
if (isHidden(p.a) || isHidden(p.b)) { if (p.t >= 1) { if (p.onArrive) p.onArrive(); particles.splice(i, 1); } continue; }
|
|
369
376
|
const t = ease(Math.min(1, p.t)); let x, y;
|
|
370
|
-
if (p.curve) { const
|
|
377
|
+
if (p.curve) { const c = dmControl(p.a, p.b), u = 1 - t; x = u * u * p.a.x + 2 * u * t * c.x + t * t * p.b.x; y = u * u * p.a.y + 2 * u * t * c.y + t * t * p.b.y; }
|
|
371
378
|
else { x = p.a.x + (p.b.x - p.a.x) * t; y = p.a.y + (p.b.y - p.a.y) * t; }
|
|
372
379
|
if (!filter.paused) { p.trail.push(x, y); if (p.trail.length > 12) p.trail.splice(0, p.trail.length - 12); }
|
|
373
380
|
const n = p.trail.length >> 1;
|
|
@@ -463,13 +470,22 @@
|
|
|
463
470
|
}
|
|
464
471
|
|
|
465
472
|
// ── events ──
|
|
466
|
-
|
|
473
|
+
// A <canvas> is a REPLACED element: `inset:0` does NOT stretch it the way it stretches a block. Without
|
|
474
|
+
// an explicit CSS size it renders at its intrinsic (= backing) size — W*DPR × H*DPR — so on a retina Mac
|
|
475
|
+
// (DPR 2) it overflows the viewport at 2× and pointer coords no longer map to the drawing's coordinate
|
|
476
|
+
// space (zoom/hover/click anchor low-and-right of the cursor). Pin the CSS size to the viewport so backing
|
|
477
|
+
// stays crisp at DPR while clientX/clientY line up 1:1 with the world transform.
|
|
478
|
+
function resize() { DPR = window.devicePixelRatio || 1; W = window.innerWidth; H = window.innerHeight; canvas.width = W * DPR; canvas.height = H * DPR; canvas.style.width = W + "px"; canvas.style.height = H + "px"; if (!cam.ready) { cam.x = W / 2; cam.y = H / 2; cam.ready = true; } }
|
|
467
479
|
window.addEventListener("resize", resize);
|
|
468
480
|
let drag = null;
|
|
469
481
|
canvas.addEventListener("mousemove", (e) => { if (drag) { cam.x = drag.cx + (e.clientX - drag.sx); cam.y = drag.cy + (e.clientY - drag.sy); drag.moved = drag.moved || Math.hypot(e.clientX - drag.sx, e.clientY - drag.sy) > 4; if (drag.moved) cam.user = true; return; } hover = pick(e.clientX, e.clientY); canvas.classList.toggle("hover", !!hover); });
|
|
470
482
|
canvas.addEventListener("mousedown", (e) => { drag = { sx: e.clientX, sy: e.clientY, cx: cam.x, cy: cam.y, moved: false }; });
|
|
471
483
|
window.addEventListener("mouseup", (e) => { if (drag && !drag.moved) { const n = pick(e.clientX, e.clientY); if (n) { sel = n; renderDetail(); $("hint").style.opacity = 0; } else closeDetail(); } drag = null; });
|
|
472
|
-
|
|
484
|
+
// Zoom-to-cursor. The factor is PROPORTIONAL to the wheel delta (exp), not a fixed step per event: a
|
|
485
|
+
// trackpad fires a burst of tiny wheel events per gesture, and a fixed 1.1×-each slammed the scale
|
|
486
|
+
// straight to the clamp — flinging the graph off-screen around the (correctly anchored) cursor. deltaMode
|
|
487
|
+
// is normalized to px so a mouse notch and a trackpad swipe both feel right.
|
|
488
|
+
canvas.addEventListener("wheel", (e) => { e.preventDefault(); cam.user = true; const px = e.deltaY * (e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? H : 1), f = Math.exp(-px * 0.0015), ns = Math.max(0.3, Math.min(3, cam.scale * f)), w = toWorld(e.clientX, e.clientY); cam.scale = ns; cam.x = e.clientX - w.x * ns; cam.y = e.clientY - w.y * ns; }, { passive: false });
|
|
473
489
|
window.addEventListener("keydown", (e) => { if (e.key === "Escape") closeDetail(); });
|
|
474
490
|
$("modes").onclick = (e) => { const c = e.target.closest(".chip"); if (!c) return; const m = c.dataset.mode; filter[m] = !filter[m]; c.classList.toggle("on", filter[m]); };
|
|
475
491
|
$("pause").onclick = () => { filter.paused = !filter.paused; $("pause").classList.toggle("on", filter.paused); $("pause").textContent = filter.paused ? "▶ resume" : "⏸ pause"; };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/cli",
|
|
3
3
|
"description": "Cotal mesh CLI: up, join, watch, console, web, spawn, mint, channels, history.",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.1",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"react": "^19.0.0",
|
|
24
24
|
"yaml": "^2.9.0",
|
|
25
25
|
"zod": "^4.4.3",
|
|
26
|
-
"@cotal-ai/core": "0.9.
|
|
27
|
-
"@cotal-ai/workspace": "0.9.
|
|
26
|
+
"@cotal-ai/core": "0.9.1",
|
|
27
|
+
"@cotal-ai/workspace": "0.9.1"
|
|
28
28
|
},
|
|
29
29
|
"optionalDependencies": {
|
|
30
30
|
"@eplightning/nats-server-darwin-arm64": "^2.14.0",
|