Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions prototype/gen-mockup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';
// Drives public/workflow-graph.js with a realistic synthetic Claude Code session
// (orchestrator spawns two parallel subagents, then fans in) and writes a static
// SVG + an HTML wrapper. No browser / deps needed.

const fs = require('fs');
const path = require('path');
const { buildWorkflowGraph, renderWorkflowSVG } = require('../public/workflow-graph.js');

const T0 = Date.parse('2026-06-17T10:00:00Z');
const s = ms => T0 + ms;

// Helper: an assistant message carrying tool_use blocks (so extractSpawnCalls works).
const asst = (...blocks) => ({ role: 'assistant', content: blocks });
const agentCall = (id, description) => ({ type: 'tool_use', id, name: 'Agent', input: { description } });

const entries = [
// ── orchestrator (explicit session) ──
{
id: 'main-1', sessionId: 'sess-main', receivedAt: s(0), elapsed: 2600,
agent: 'claude', displayNum: 1, title: 'Refactor auth + add tests',
toolCalls: { Read: 1, Grep: 2 },
req: { messages: [ asst({ type: 'tool_use', id: 't1', name: 'Read', input: {} }) ] },
},
{
id: 'main-2', sessionId: 'sess-main', receivedAt: s(3200), elapsed: 800,
agent: 'claude', displayNum: 2,
toolCalls: { Agent: 2 },
req: { messages: [ asst(
agentCall('a1', 'explore auth module structure'),
agentCall('a2', 'survey existing test coverage'),
) ] },
},
{
id: 'main-3', sessionId: 'sess-main', receivedAt: s(8400), elapsed: 4200,
agent: 'claude', displayNum: 3,
toolCalls: { Edit: 2, Write: 1 },
req: { messages: [ asst({ type: 'tool_use', id: 't9', name: 'Edit', input: {} }) ] },
},
{
id: 'main-4', sessionId: 'sess-main', receivedAt: s(13200), elapsed: 5100,
agent: 'claude', displayNum: 4,
toolCalls: { Bash: 1 },
req: { messages: [ asst({ type: 'tool_use', id: 't12', name: 'Bash', input: {} }) ] },
},

// ── subagent A: explore auth (inferred session) ──
{
id: 'subA-1', sessionId: 'sess-subA', receivedAt: s(4200), elapsed: 1500,
agent: 'claude', displayNum: 1, isSubagent: true, sessionInferred: true,
title: 'explore auth', toolCalls: { Glob: 1, Read: 2 },
req: { messages: [] },
},
{
id: 'subA-2', sessionId: 'sess-subA', receivedAt: s(6000), elapsed: 1400,
agent: 'claude', displayNum: 2, isSubagent: true, sessionInferred: true,
toolCalls: { Grep: 3 }, req: { messages: [] },
},

// ── subagent B: survey tests (inferred session, runs in parallel with A) ──
{
id: 'subB-1', sessionId: 'sess-subB', receivedAt: s(4500), elapsed: 2900,
agent: 'claude', displayNum: 1, isSubagent: true, sessionInferred: true,
title: 'survey tests', toolCalls: { Bash: 1, Read: 1 }, toolFail: true,
req: { messages: [] },
},
];

const graph = buildWorkflowGraph(entries);

// ── sanity: did inference recover the intended spawn structure? ──
const spawns = graph.edges.filter(e => e.type === 'spawn');
const fanins = graph.edges.filter(e => e.type === 'fanin');
console.log('lanes :', graph.lanes.map(l => `${l.sessionId}(${l.kind})`).join(', '));
console.log('spawn edges:', spawns.map(e => `${e.from}→${e.to} [${e.label}]`).join(' '));
console.log('fanin edges:', fanins.map(e => `${e.from}→${e.to}`).join(' '));
const ok = spawns.length === 2
&& spawns.every(e => e.from === 'main-2')
&& spawns.some(e => e.to === 'subA-1') && spawns.some(e => e.to === 'subB-1')
&& fanins.length === 2 && fanins.every(e => e.to === 'main-3');
console.log(ok ? '✓ spawn/fan-in inference reproduced the intended graph' : '✗ inference MISMATCH');

const svg = renderWorkflowSVG(graph, { pxPerSec: 40 });
const outDir = __dirname;
fs.writeFileSync(path.join(outDir, 'workflow-swimlane.svg'), svg);
fs.writeFileSync(path.join(outDir, 'workflow-swimlane.html'),
`<!doctype html><meta charset=utf-8><title>ccxray · Workflow Swimlane prototype</title>
<body style="margin:0;background:#0d1117;display:flex;justify-content:center;padding:24px">
${svg}
</body>`);
console.log('wrote', path.join(outDir, 'workflow-swimlane.svg'));
if (!ok) process.exit(1);
4 changes: 4 additions & 0 deletions prototype/workflow-swimlane.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!doctype html><meta charset=utf-8><title>ccxray · Workflow Swimlane prototype</title>
<body style="margin:0;background:#0d1117;display:flex;justify-content:center;padding:24px">
<svg xmlns="http://www.w3.org/2000/svg" width="922" height="362" viewBox="0 0 922 362" font-family="ui-monospace,SFMono-Regular,Menlo,monospace"><rect width="922" height="362" fill="#0d1117"/><text x="150" y="26" fill="#e6edf3" font-size="15" font-weight="700">Workflow · Swimlane Flow</text><text x="150" y="42" fill="#8b949e" font-size="11">orchestrator + 2 subagent lane(s) · X = time · → spawn · ⇠ fan-in</text><rect x="0" y="56" width="922" height="92" fill="#ffffff" opacity="0.015"/><line x1="150" y1="148" x2="882" y2="148" stroke="#30363d" stroke-width="1" opacity="0.5"/><circle cx="16" cy="102" r="4" fill="#58a6ff"/><text x="28" y="100" fill="#e6edf3" font-size="11" font-weight="600">orchestrator</text><text x="28" y="114" fill="#8b949e" font-size="9">main · 4 turn(s)</text><line x1="150" y1="240" x2="882" y2="240" stroke="#30363d" stroke-width="1" opacity="0.5"/><circle cx="16" cy="194" r="4" fill="#ff8a65"/><text x="28" y="192" fill="#e6edf3" font-size="11" font-weight="600">explore auth</text><text x="28" y="206" fill="#8b949e" font-size="9">subagent · 2 turn(s)</text><rect x="0" y="240" width="922" height="92" fill="#ffffff" opacity="0.015"/><line x1="150" y1="332" x2="882" y2="332" stroke="#30363d" stroke-width="1" opacity="0.5"/><circle cx="16" cy="286" r="4" fill="#ff8a65"/><text x="28" y="284" fill="#e6edf3" font-size="11" font-weight="600">survey tests</text><text x="28" y="298" fill="#8b949e" font-size="9">subagent · 1 turn(s)</text><line x1="150" y1="48" x2="150" y2="332" stroke="#30363d" stroke-width="1" opacity="0.25"/><text x="153" y="44" fill="#8b949e" font-size="9">0s</text><line x1="350" y1="48" x2="350" y2="332" stroke="#30363d" stroke-width="1" opacity="0.25"/><text x="353" y="44" fill="#8b949e" font-size="9">5s</text><line x1="550" y1="48" x2="550" y2="332" stroke="#30363d" stroke-width="1" opacity="0.25"/><text x="553" y="44" fill="#8b949e" font-size="9">10s</text><line x1="750" y1="48" x2="750" y2="332" stroke="#30363d" stroke-width="1" opacity="0.25"/><text x="753" y="44" fill="#8b949e" font-size="9">15s</text><line x1="285.5" y1="102" x2="278" y2="102" stroke="#8b949e" stroke-width="1.5" opacity="0.5"/><line x1="374" y1="102" x2="486" y2="102" stroke="#8b949e" stroke-width="1.5" opacity="0.5"/><line x1="628" y1="102" x2="678" y2="102" stroke="#8b949e" stroke-width="1.5" opacity="0.5"/><line x1="453.5" y1="194" x2="390" y2="194" stroke="#8b949e" stroke-width="1.5" opacity="0.5"/><path d="M 326 125 C 326 148, 318 148, 318 171" fill="none" stroke="#ff8a65" stroke-width="2" marker-end="url(#arrowSpawn)"/><text x="322" y="145" fill="#ff8a65" font-size="9" text-anchor="middle">spawn: explore auth module structur</text><path d="M 326 125 C 326 194, 330 194, 330 263" fill="none" stroke="#ff8a65" stroke-width="2" marker-end="url(#arrowSpawn)"/><text x="328" y="191" fill="#ff8a65" font-size="9" text-anchor="middle">spawn: survey existing test coverag</text><path d="M 493 171 C 523 148, 486 148, 486 125" fill="none" stroke="#4dd0e1" stroke-width="1.6" stroke-dasharray="4 3" marker-end="url(#arrowFanin)"/><path d="M 452.5 263 C 482.5 194, 486 194, 486 125" fill="none" stroke="#4dd0e1" stroke-width="1.6" stroke-dasharray="4 3" marker-end="url(#arrowFanin)"/><defs><marker id="arrowSpawn" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#ff8a65"/></marker><marker id="arrowFanin" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="#4dd0e1"/></marker></defs><rect x="150" y="79" width="135.5" height="46" rx="7" fill="#16243a" stroke="#58a6ff" stroke-width="1.5"/><text x="159" y="93" fill="#e6edf3" font-size="11" font-weight="700">#1 <tspan fill="#8b949e" font-weight="400">claude</tspan></text><text x="159" y="108" fill="#3fb950" font-size="9.5">Read Grep×2</text><text x="159" y="119" fill="#8b949e" font-size="8">2.6s</text><rect x="278" y="79" width="96" height="46" rx="7" fill="#16243a" stroke="#58a6ff" stroke-width="1.5"/><text x="287" y="93" fill="#e6edf3" font-size="11" font-weight="700">#2 <tspan fill="#8b949e" font-weight="400">claude</tspan></text><text x="287" y="108" fill="#3fb950" font-size="9.5">·</text><text x="287" y="119" fill="#8b949e" font-size="8">0.8s</text><text x="366" y="93" fill="#ff8a65" font-size="9" text-anchor="end">⑂2</text><rect x="486" y="79" width="142" height="46" rx="7" fill="#16243a" stroke="#58a6ff" stroke-width="1.5"/><text x="495" y="93" fill="#e6edf3" font-size="11" font-weight="700">#3 <tspan fill="#8b949e" font-weight="400">claude</tspan></text><text x="495" y="108" fill="#3fb950" font-size="9.5">Edit×2 Write</text><text x="495" y="119" fill="#8b949e" font-size="8">4.2s</text><rect x="678" y="79" width="96" height="46" rx="7" fill="#16243a" stroke="#58a6ff" stroke-width="1.5"/><text x="687" y="93" fill="#e6edf3" font-size="11" font-weight="700">#4 <tspan fill="#8b949e" font-weight="400">claude</tspan></text><text x="687" y="108" fill="#3fb950" font-size="9.5">Bash</text><text x="687" y="119" fill="#8b949e" font-size="8">5.1s</text><rect x="318" y="171" width="135.5" height="46" rx="7" fill="#2a1f1a" stroke="#ff8a65" stroke-width="1.5"/><text x="327" y="185" fill="#e6edf3" font-size="11" font-weight="700">#1 <tspan fill="#8b949e" font-weight="400">claude</tspan></text><text x="327" y="200" fill="#3fb950" font-size="9.5">Glob Read×2</text><text x="327" y="211" fill="#8b949e" font-size="8">1.5s</text><rect x="390" y="171" width="103" height="46" rx="7" fill="#2a1f1a" stroke="#ff8a65" stroke-width="1.5"/><text x="399" y="185" fill="#e6edf3" font-size="11" font-weight="700">#2 <tspan fill="#8b949e" font-weight="400">claude</tspan></text><text x="399" y="200" fill="#3fb950" font-size="9.5">Grep×3</text><text x="399" y="211" fill="#8b949e" font-size="8">1.4s</text><rect x="330" y="263" width="122.5" height="46" rx="7" fill="#2a1f1a" stroke="#f85149" stroke-width="1.5"/><text x="339" y="277" fill="#e6edf3" font-size="11" font-weight="700">#1 <tspan fill="#8b949e" font-weight="400">claude</tspan></text><text x="339" y="292" fill="#3fb950" font-size="9.5">Bash Read</text><text x="339" y="303" fill="#8b949e" font-size="8">2.9s</text></svg>
</body>
1 change: 1 addition & 0 deletions prototype/workflow-swimlane.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading