trantor 0.17.17 → 0.17.19

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + context-handoff for independent AI coding agents (Claude, Codex, Gemini, …)",
9
- "version": "0.17.17"
9
+ "version": "0.17.19"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "trantor",
14
14
  "source": "./",
15
15
  "description": "The hub-world for AI agent crews. Say \"fire up the crew\" and Claude becomes the architect: a plan-aware Advisor routes the work (solo / cheap inline calls / live crew of Codex, Gemini, Kimi & DeepSeek in their own terminal windows), a Kanban/flow command center with a testing gate tracks it, and an economics brain (Scrooge) keeps the receipts. Includes the relay MCP, a SessionStart auto-discovery hook, and a PreCompact context-handoff so a fresh session can take over a full window instead of compacting.",
16
- "version": "0.17.17",
16
+ "version": "0.17.19",
17
17
  "author": {
18
18
  "name": "Sasha Bogojevic"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.17",
3
+ "version": "0.17.19",
4
4
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + crew orchestration for independent AI coding agents (Claude, Codex, Gemini, Kimi, DeepSeek)",
5
5
  "mcpServers": {
6
6
  "relay": {
@@ -27,6 +27,7 @@ const me = `${hostId()}:${project}`;
27
27
 
28
28
  const themeOf = (s) => {
29
29
  let m;
30
+ if ((m = s.match(/^release:\s*v?(\d+\.\d+\.\d+)/i))) return "v" + m[1]; // release: v0.17.15 → v0.17.15 (one card per version)
30
31
  if ((m = s.match(/^[a-z]+\(([^)]+)\)\s*:/i))) return m[1].trim(); // feat(engine): → engine
31
32
  if ((m = s.match(/^([A-Za-z][\w &+/.]*?)\s*:/))) return m[1].trim(); // "Landing: …" → Landing
32
33
  if ((m = s.match(/^([A-Za-z][\w.+-]*)/))) return m[1]; // first word
@@ -57,7 +58,11 @@ const ents = [...groups.entries()].sort((a, b) => a[1].latest - b[1].latest);
57
58
  let posted = 0, skipped = 0;
58
59
  for (const [theme, g] of ents) {
59
60
  const latest = g.commits.sort((a, b) => b.ts - a.ts)[0];
60
- const subj = latest.subject.replace(/^[a-z]+\([^)]*\)\s*:\s*/i, "").replace(/^[A-Za-z][\w &+/.]*?:\s*/, "").slice(0, 70);
61
+ const subj = latest.subject
62
+ .replace(/^[a-z]+\([^)]*\)\s*:\s*/i, "") // strip feat(scope):
63
+ .replace(/^[A-Za-z][\w &+/.]*?:\s*/, "") // strip "release:" / "Landing:"
64
+ .replace(/^v?\d+\.\d+\.\d+\s*[—–-]\s*/, "") // strip a leading "v0.17.15 — " (release version dup)
65
+ .slice(0, 70);
61
66
  const title = `${theme}: ${subj}${g.commits.length > 1 ? ` (+${g.commits.length - 1} more)` : ""}`.slice(0, 190);
62
67
  if (existing.has(title)) { skipped++; continue; }
63
68
  if (dry) { console.log(`+ [${new Date(g.latest).toISOString().slice(0, 10)}] ${theme.padEnd(20)} ${g.commits.length}c ${title.slice(0, 64)}`); posted++; continue; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.17.17",
3
+ "version": "0.17.19",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
package/ui.html CHANGED
@@ -470,25 +470,26 @@ function wireTimeline(el){
470
470
  view.style.left = (100 * s.scrollLeft / w) + '%';
471
471
  view.style.width = Math.min(100, 100 * s.clientWidth / w) + '%';
472
472
  };
473
- s.onscroll = () => { GSCROLL[proj] = s.scrollLeft; syncView(); };
473
+ s.onscroll = () => { GSCROLL[proj] = s.scrollLeft; flowBusy = Date.now(); syncView(); }; // flag busy so render() defers
474
474
  requestAnimationFrame(syncView);
475
475
  if (map) {
476
476
  const scrubTo = (clientX) => { // map a pointer x onto a scroll position (centered)
477
477
  const r = map.getBoundingClientRect();
478
478
  const ratio = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
479
+ flowBusy = Date.now(); // keep render() off the board WHILE scrubbing
479
480
  s.scrollLeft = ratio * s.scrollWidth - s.clientWidth / 2;
480
481
  };
481
- let dragging = false;
482
+ let scrubbing = false;
482
483
  map.addEventListener('pointerdown', e => {
483
- dragging = true;
484
+ scrubbing = true; flowBusy = Date.now();
484
485
  try { map.setPointerCapture?.(e.pointerId); } catch {}
485
486
  const seg = e.target.closest && e.target.closest('.gmapseg'); // click a phase segment → jump to that phase
486
487
  if (seg && seg.dataset.cx != null) s.scrollLeft = +seg.dataset.cx - 40; else scrubTo(e.clientX);
487
488
  e.preventDefault();
488
489
  });
489
- map.addEventListener('pointermove', e => { if (dragging) scrubTo(e.clientX); });
490
- const end = () => { dragging = false; };
491
- map.addEventListener('pointerup', end); map.addEventListener('pointercancel', end);
490
+ map.addEventListener('pointermove', e => { if (scrubbing) scrubTo(e.clientX); });
491
+ const end = () => { scrubbing = false; flowBusy = Date.now(); }; // freshen lock on release so the next render is deferred ~1s
492
+ map.addEventListener('pointerup', end); map.addEventListener('pointercancel', end); map.addEventListener('lostpointercapture', end);
492
493
  }
493
494
  });
494
495
  el.querySelectorAll('.gnode[data-id]').forEach(n => n.onclick = () => openCard(+n.dataset.id));
@@ -526,6 +527,8 @@ async function openCard(id){
526
527
  }
527
528
  function closeCard(){ const el=$('#cardmodal'); el.style.display='none'; el.innerHTML=''; if(cardEsc){ document.removeEventListener('keydown', cardEsc); cardEsc=null; } }
528
529
  let dragging = null; // suppresses re-render mid-gesture
530
+ let flowBusy = 0; // timestamp of the last FLOW scrub/scroll — render() defers while you interact
531
+ // (the 2.5s poll / SSE rebuild was replacing the scrubber DOM mid-drag → freeze)
529
532
  function saveCam(proj, cam){ const c = JSON.parse(localStorage.getItem("abFlowCam") || "{}"); c[proj] = cam; localStorage.setItem("abFlowCam", JSON.stringify(c)); }
530
533
  function wireFlow(el){
531
534
  el.querySelectorAll(".flowwrap").forEach(w => {
@@ -671,7 +674,8 @@ async function render(){
671
674
  return SEEN.indexOf(a.project)-SEEN.indexOf(b.project); // stable first-seen order
672
675
  });
673
676
  const el=$('#boards');
674
- if(dragging)return; // never rebuild mid-gesture
677
+ if(dragging)return; // never rebuild mid-gesture (project drag / pan)
678
+ if(Date.now()-flowBusy < 1000){ clearTimeout(render._t); render._t=setTimeout(render,1100); return; } // ...nor mid-scrub/scroll; catch up shortly after
675
679
  if(!projects.length){el.innerHTML='<div class="empty big">no projects yet — agents register a project on connect</div>';return;}
676
680
  const idleOpen=new Set(JSON.parse(localStorage.getItem("abIdleOpen")||"[]"));
677
681
  const projBlock=p=>{