feat(ux): consolidate Launch Swarm + telemetry UX with minimized strip

- Removed broken LaunchSwarmDialog (formula-based) from TopBar/LeftPanel
- All Rocket buttons (TopBar, LeftPanel, DAG nodes, social cards) now open
  AssignmentPanel (archetype-based) which actually works
- Every Rocket clears taskId first so assignMode && !taskId condition passes
- Conversation button priority: taskId always shows conversation, not assign panel
- Added TelemetryStrip: minimized right sidebar with status dots when non-telemetry
  panel (conversation/assignment) is active
- Live feed has minimize button → restores last taskId or assignMode
- DAG nodes: Signal icon → restores telemetry feed
- Social button on DAG nodes: single router.push to avoid race (setView + setTaskId)
- Fixed social card message button: opens right panel with drawer:closed (no popup)

Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
zenchantlive 2026-03-01 18:17:58 -08:00
parent 65d69ecbbc
commit c246ceaf21
165 changed files with 13730 additions and 1132 deletions

View file

@ -0,0 +1,33 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { GET as healthGet } from '../../src/app/api/bd/health/route';
test('bd health route returns setup guidance when bd is missing from PATH', async () => {
const previousPath = process.env.PATH;
const previousPathAlt = process.env.Path;
process.env.PATH = '';
process.env.Path = '';
try {
const response = await healthGet(new Request('http://localhost/api/bd/health?projectRoot=C:/repo/test'));
const body = await response.json();
assert.equal(response.status, 503);
assert.equal(body.ok, false);
assert.equal(body.error.classification, 'not_found');
assert.equal(typeof body.error.message, 'string');
assert.equal(String(body.error.message).includes('bd command not found in PATH'), true);
} finally {
if (previousPath === undefined) {
delete process.env.PATH;
} else {
process.env.PATH = previousPath;
}
if (previousPathAlt === undefined) {
delete process.env.Path;
} else {
process.env.Path = previousPathAlt;
}
}
});

View file

@ -0,0 +1,67 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { handleCoordEventsPost } from '../../src/app/api/coord/events/route';
test('handleCoordEventsPost returns 400 for invalid body', async () => {
const request = new Request('http://localhost/api/coord/events', {
method: 'POST',
body: 'not-json',
headers: { 'content-type': 'application/json' },
});
const response = await handleCoordEventsPost(request);
const body = await response.json();
assert.equal(response.status, 400);
assert.equal(body.ok, false);
});
test('handleCoordEventsPost writes event and returns success', async () => {
const request = new Request('http://localhost/api/coord/events', {
method: 'POST',
body: JSON.stringify({
projectRoot: process.cwd(),
event: {
version: 'coord.v1',
kind: 'coord_event',
issue_id: 'bb-123',
actor: 'amber-otter',
timestamp: '2026-02-28T18:00:00.000Z',
data: {
event_type: 'SEND',
event_id: 'evt_01',
project_root: process.cwd(),
to_agent: 'cobalt-harbor',
state: 'unread',
payload: { subject: 's', body: 'b' },
},
},
}),
headers: { 'content-type': 'application/json' },
});
const response = await handleCoordEventsPost(request, {
writeCoordEvent: async () => ({
ok: true,
eventId: 'evt_01',
commandResult: {
success: true,
classification: null,
command: 'bd',
args: [],
cwd: process.cwd(),
stdout: '',
stderr: '',
code: 0,
durationMs: 1,
error: null,
},
}),
});
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(body.ok, true);
assert.equal(body.eventId, 'evt_01');
});

View file

@ -0,0 +1,72 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { buildCommentMutationBody, buildCoordMessageActionEvent } from '../../../src/components/sessions/conversation-drawer';
describe('ConversationDrawer coord action payloads', () => {
const message = {
message_id: 'evt_send_1',
thread_id: 'bead:bb-123',
bead_id: 'bb-123',
from_agent: 'amber-otter',
to_agent: 'cobalt-harbor',
category: 'HANDOFF',
subject: 'subject',
body: 'body',
state: 'unread',
requires_ack: true,
created_at: '2026-02-28T10:00:00.000Z',
read_at: null,
acked_at: null,
} as const;
it('builds READ event with event_ref to message id', () => {
const payload = buildCoordMessageActionEvent({
action: 'read',
message: message as any,
beadId: 'bb-123',
projectRoot: '/tmp/repo',
nowIso: '2026-02-28T11:00:00.000Z',
}) as any;
assert.equal(payload.kind, 'coord_event');
assert.equal(payload.data.event_type, 'READ');
assert.equal(payload.data.event_ref, 'evt_send_1');
});
it('builds ACK event with recipient as actor', () => {
const payload = buildCoordMessageActionEvent({
action: 'ack',
message: message as any,
beadId: 'bb-123',
projectRoot: '/tmp/repo',
nowIso: '2026-02-28T11:00:00.000Z',
}) as any;
assert.equal(payload.actor, 'cobalt-harbor');
assert.equal(payload.data.event_type, 'ACK');
assert.equal(payload.issue_id, 'bb-123');
});
});
describe('ConversationDrawer comment payload', () => {
it('includes actor when provided', () => {
const payload = buildCommentMutationBody({
projectRoot: '/tmp/repo',
text: 'hello',
actor: 'zenchant',
}) as any;
assert.equal(payload.actor, 'zenchant');
});
it('omits actor when blank', () => {
const payload = buildCommentMutationBody({
projectRoot: '/tmp/repo',
text: 'hello',
actor: ' ',
}) as any;
assert.equal('actor' in payload, false);
});
});

View file

@ -40,6 +40,12 @@ test('UnifiedShell - imports AssignmentPanel', async () => {
assert.ok(fileContent.includes('AssignmentPanel'), 'Should import AssignmentPanel');
});
test('UnifiedShell - checks bd health and renders setup warning', async () => {
const fileContent = await fs.readFile(path.join(process.cwd(), 'src/components/shared/unified-shell.tsx'), 'utf-8');
assert.ok(fileContent.includes('useBdHealth'), 'Should use bd health hook');
assert.ok(fileContent.includes('BD setup issue:'), 'Should show bd setup warning text');
});
// Test that AssignmentPanel is rendered conditionally based on view and assignMode
test('UnifiedShell - renders AssignmentPanel conditionally', async () => {
const fileContent = await fs.readFile(path.join(process.cwd(), 'src/components/shared/unified-shell.tsx'), 'utf-8');

View file

@ -1,43 +0,0 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { BdExecutableNotFoundError, resolveBdExecutable } from '../../src/lib/bd-path';
test('resolveBdExecutable prefers explicit configured path when provided', async () => {
const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-bd-path-'));
const explicit = path.join(temp, 'tools', 'bd.exe');
await fs.mkdir(path.dirname(explicit), { recursive: true });
await fs.writeFile(explicit, '');
const resolved = await resolveBdExecutable({ explicitPath: explicit, env: { Path: '', NODE_ENV: 'test' } });
assert.equal(resolved.executable, explicit);
assert.equal(resolved.source, 'config');
});
test('resolveBdExecutable finds bd.exe on PATH when explicit path is not set', async () => {
const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-bd-path-env-'));
const candidate = path.join(temp, 'bd.exe');
await fs.writeFile(candidate, '');
const resolved = await resolveBdExecutable({ env: { Path: temp, NODE_ENV: 'test' } });
assert.equal(resolved.executable, candidate);
assert.equal(resolved.source, 'path');
});
test('resolveBdExecutable throws actionable setup guidance when executable is missing', async () => {
await assert.rejects(
() => resolveBdExecutable({ env: { Path: '', NODE_ENV: 'test' } }),
(error: unknown) => {
assert.equal(error instanceof BdExecutableNotFoundError, true);
const message = String((error as Error).message).toLowerCase();
assert.equal(message.includes('npm install -g @beads/bd'), true);
assert.equal(message.includes('bd.exe'), true);
return true;
},
);
});

View file

@ -2,6 +2,7 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import { runBdCommand } from '../../src/lib/bridge';
import { normalizeProjectRootForRuntime } from '../../src/lib/project-root';
test('runBdCommand returns structured success payload from exec output', async () => {
const result = await runBdCommand(
@ -12,12 +13,11 @@ test('runBdCommand returns structured success payload from exec output', async (
explicitBdPath: 'C:/tools/bd.exe',
},
{
resolveBdExecutable: async () => ({ executable: 'C:/tools/bd.exe', source: 'config' }),
exec: async (command: string, options: any) => {
assert.ok(command.includes('bd'));
assert.ok(command.startsWith('bd '));
assert.ok(command.includes('list'));
assert.ok(command.includes('--json'));
assert.equal(options.cwd, 'C:/repo/project');
assert.equal(options.cwd, normalizeProjectRootForRuntime('C:/repo/project'));
return { stdout: '[{"id":"bb-1"}]\r\n', stderr: '' };
},
},
@ -32,7 +32,6 @@ test('runBdCommand classifies missing executable as not_found', async () => {
const result = await runBdCommand(
{ projectRoot: 'C:/repo/project', args: ['list'] },
{
resolveBdExecutable: async () => ({ executable: 'C:/tools/bd.exe', source: 'config' }),
exec: async () => {
const error = new Error('spawn ENOENT') as NodeJS.ErrnoException;
error.code = 'ENOENT';
@ -49,7 +48,6 @@ test('runBdCommand classifies timeout failures', async () => {
const result = await runBdCommand(
{ projectRoot: 'C:/repo/project', args: ['list'], timeoutMs: 5 },
{
resolveBdExecutable: async () => ({ executable: 'C:/tools/bd.exe', source: 'config' }),
exec: async () => {
const error = new Error('timed out') as NodeJS.ErrnoException & { killed?: boolean; signal?: string };
error.code = 'ETIMEDOUT';
@ -68,7 +66,6 @@ test('runBdCommand classifies non-zero bad-argument exits', async () => {
const result = await runBdCommand(
{ projectRoot: 'C:/repo/project', args: ['update', '--bad-flag'] },
{
resolveBdExecutable: async () => ({ executable: 'C:/tools/bd.exe', source: 'config' }),
exec: async () => {
const error = new Error('exit code 1') as NodeJS.ErrnoException & {
stdout?: string;
@ -85,3 +82,26 @@ test('runBdCommand classifies non-zero bad-argument exits', async () => {
assert.equal(result.success, false);
assert.equal(result.classification, 'bad_args');
});
test('runBdCommand treats shell "not recognized" stderr as not_found', async () => {
const result = await runBdCommand(
{ projectRoot: 'C:/repo/project', args: ['list'] },
{
exec: async () => {
const error = new Error('exit code 1') as NodeJS.ErrnoException & {
stdout?: string;
stderr?: string;
exitCode?: number;
};
error.code = 'BD_EXIT';
error.stderr = `'bd' is not recognized as an internal or external command`;
error.exitCode = 1;
throw error;
},
},
);
assert.equal(result.success, false);
assert.equal(result.classification, 'not_found');
assert.equal(result.error?.includes('bd command not found in PATH'), true);
});

View file

@ -0,0 +1,68 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { writeCoordEvent } from '../../src/lib/coord-events';
function validEnvelope() {
return {
version: 'coord.v1',
kind: 'coord_event',
issue_id: 'bb-123',
actor: 'amber-otter',
timestamp: '2026-02-28T18:00:00.000Z',
data: {
event_type: 'SEND',
event_id: 'evt_01',
project_root: '/tmp/repo',
to_agent: 'cobalt-harbor',
state: 'unread',
payload: {
subject: 'Need review',
body: 'Please check API changes',
},
},
};
}
test('writeCoordEvent rejects invalid payload', async () => {
const result = await writeCoordEvent(
{ version: 'coord.v1' },
{ projectRoot: '/tmp/repo' },
);
assert.equal(result.ok, false);
assert.equal(result.error?.classification, 'bad_args');
});
test('writeCoordEvent invokes bd audit record with --stdin payload', async () => {
let capturedArgs: string[] | null = null;
let capturedStdin = '';
const result = await writeCoordEvent(
validEnvelope(),
{ projectRoot: '/tmp/repo' },
{
runBdCommand: async (options) => {
capturedArgs = options.args;
capturedStdin = options.stdinText ?? '';
return {
success: true,
classification: null,
command: 'bd',
args: options.args,
cwd: options.projectRoot,
stdout: '{"ok":true}',
stderr: '',
code: 0,
durationMs: 1,
error: null,
};
},
},
);
assert.equal(result.ok, true);
assert.deepEqual(capturedArgs, ['audit', 'record', '--stdin', '--json']);
assert.match(capturedStdin, /"coord_event"/);
assert.match(capturedStdin, /"SEND"/);
});

View file

@ -0,0 +1,90 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { projectInbox, projectMessageState, type CoordProtocolEvent } from '../../src/lib/coord-projections';
const baseSend: CoordProtocolEvent = {
version: 'coord.v1',
kind: 'coord_event',
issue_id: 'bb-123',
actor: 'amber-otter',
timestamp: '2026-02-28T10:00:00.000Z',
data: {
event_type: 'SEND',
event_id: 'evt_send_1',
project_root: '/tmp/repo',
to_agent: 'cobalt-harbor',
state: 'unread',
payload: {
subject: 'handoff',
body: 'please review',
},
},
};
test('projectMessageState derives unread -> read -> acked', () => {
const events: CoordProtocolEvent[] = [
{
...baseSend,
timestamp: '2026-02-28T10:00:00.000Z',
},
{
...baseSend,
timestamp: '2026-02-28T10:01:00.000Z',
data: {
event_type: 'READ',
event_id: 'evt_read_1',
event_ref: 'evt_send_1',
project_root: '/tmp/repo',
payload: {},
},
},
{
...baseSend,
timestamp: '2026-02-28T10:02:00.000Z',
data: {
event_type: 'ACK',
event_id: 'evt_ack_1',
event_ref: 'evt_send_1',
project_root: '/tmp/repo',
payload: {},
},
},
];
const state = projectMessageState(events);
assert.equal(state.get('evt_send_1'), 'acked');
});
test('projectInbox tolerates out-of-order and unknown refs', () => {
const events: CoordProtocolEvent[] = [
{
...baseSend,
timestamp: '2026-02-28T10:02:00.000Z',
data: {
event_type: 'ACK',
event_id: 'evt_ack_unknown',
event_ref: 'evt_missing',
project_root: '/tmp/repo',
payload: {},
},
},
{
...baseSend,
timestamp: '2026-02-28T10:01:00.000Z',
data: {
event_type: 'READ',
event_id: 'evt_read_1',
event_ref: 'evt_send_1',
project_root: '/tmp/repo',
payload: {},
},
},
baseSend,
];
const inbox = projectInbox(events, 'bb-123', 'cobalt-harbor');
assert.equal(inbox.length, 1);
assert.equal(inbox[0].state, 'read');
assert.equal(inbox[0].to_agent, 'cobalt-harbor');
});

View file

@ -0,0 +1,89 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
calculateReservationIncursions,
isTakeoverAllowed,
projectReservations,
type CoordProtocolEvent,
} from '../../src/lib/coord-projections';
function reserveEvent(input: {
actor: string;
scope: string;
bead: string;
at: string;
type?: 'RESERVE' | 'RELEASE' | 'TAKEOVER';
takeoverMode?: 'stale' | 'evicted';
}): CoordProtocolEvent {
return {
version: 'coord.v1',
kind: 'coord_event',
issue_id: input.bead,
actor: input.actor,
timestamp: input.at,
data: {
event_type: input.type ?? 'RESERVE',
event_id: `${input.type ?? 'RESERVE'}-${input.actor}-${Date.parse(input.at)}`,
project_root: '/tmp/repo',
scope: input.scope,
takeover_mode: input.takeoverMode,
payload: {},
},
};
}
test('isTakeoverAllowed enforces active/stale/evicted policy', () => {
assert.equal(isTakeoverAllowed('active', 'stale'), false);
assert.equal(isTakeoverAllowed('stale', 'stale'), true);
assert.equal(isTakeoverAllowed('stale', 'evicted'), false);
assert.equal(isTakeoverAllowed('evicted', 'stale'), true);
assert.equal(isTakeoverAllowed('evicted', 'evicted'), true);
});
test('projectReservations applies reserve/release transitions', () => {
const events: CoordProtocolEvent[] = [
reserveEvent({ actor: 'agent-a', scope: 'src/lib/*', bead: 'bb-1', at: '2026-02-28T10:00:00.000Z' }),
reserveEvent({ actor: 'agent-a', scope: 'src/lib/*', bead: 'bb-1', at: '2026-02-28T10:01:00.000Z', type: 'RELEASE' }),
];
const reservations = projectReservations(events, { 'agent-a': 'active' });
assert.equal(reservations.length, 0);
});
test('projectReservations rejects stale takeover when owner active', () => {
const events: CoordProtocolEvent[] = [
reserveEvent({ actor: 'agent-a', scope: 'src/lib/*', bead: 'bb-1', at: '2026-02-28T10:00:00.000Z' }),
reserveEvent({ actor: 'agent-b', scope: 'src/lib/*', bead: 'bb-2', at: '2026-02-28T10:01:00.000Z', type: 'TAKEOVER', takeoverMode: 'stale' }),
];
const reservations = projectReservations(events, { 'agent-a': 'active' });
assert.equal(reservations.length, 1);
assert.equal(reservations[0].agent_id, 'agent-a');
});
test('projectReservations allows stale takeover when owner stale', () => {
const events: CoordProtocolEvent[] = [
reserveEvent({ actor: 'agent-a', scope: 'src/lib/*', bead: 'bb-1', at: '2026-02-28T10:00:00.000Z' }),
reserveEvent({ actor: 'agent-b', scope: 'src/lib/*', bead: 'bb-2', at: '2026-02-28T10:01:00.000Z', type: 'TAKEOVER', takeoverMode: 'stale' }),
];
const reservations = projectReservations(events, { 'agent-a': 'stale' });
assert.equal(reservations.length, 1);
assert.equal(reservations[0].agent_id, 'agent-b');
assert.equal(reservations[0].takeover_mode, 'stale');
});
test('calculateReservationIncursions finds partial overlap', () => {
const reservations = projectReservations(
[
reserveEvent({ actor: 'agent-a', scope: 'src/lib/*', bead: 'bb-1', at: '2026-02-28T10:00:00.000Z' }),
reserveEvent({ actor: 'agent-b', scope: 'src/lib/parser.ts', bead: 'bb-2', at: '2026-02-28T10:01:00.000Z' }),
],
{},
);
const incursions = calculateReservationIncursions(reservations);
assert.equal(incursions.length, 1);
assert.equal(incursions[0].severity, 'partial');
});

View file

@ -0,0 +1,63 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { validateCoordEventEnvelope } from '../../src/lib/coord-schema';
function baseEnvelope(eventType: string): any {
return {
version: 'coord.v1',
kind: 'coord_event',
issue_id: 'bb-123',
actor: 'amber-otter',
timestamp: '2026-02-28T18:00:00.000Z',
data: {
event_type: eventType,
event_id: 'evt_01JN6Y1Q7R80E8P6K1Q5',
project_root: '/tmp/repo',
payload: {},
},
};
}
test('validateCoordEventEnvelope accepts valid SEND', () => {
const input = baseEnvelope('SEND');
input.data.to_agent = 'cobalt-harbor';
input.data.state = 'unread';
input.data.payload = { subject: 's', body: 'b' };
const result = validateCoordEventEnvelope(input);
assert.equal(result.ok, true);
});
test('validateCoordEventEnvelope rejects READ without event_ref', () => {
const input = baseEnvelope('READ');
const result = validateCoordEventEnvelope(input);
assert.equal(result.ok, false);
if (!result.ok) {
assert.match(result.error, /event_ref/i);
}
});
test('validateCoordEventEnvelope accepts TAKEOVER with stale mode', () => {
const input = baseEnvelope('TAKEOVER');
input.data.scope = 'src/lib/*';
input.data.takeover_mode = 'stale';
input.data.reason = 'owner stale';
const result = validateCoordEventEnvelope(input);
assert.equal(result.ok, true);
});
test('validateCoordEventEnvelope rejects TAKEOVER with invalid mode', () => {
const input = baseEnvelope('TAKEOVER');
input.data.scope = 'src/lib/*';
input.data.takeover_mode = 'none';
input.data.reason = 'owner stale';
const result = validateCoordEventEnvelope(input);
assert.equal(result.ok, false);
if (!result.ok) {
assert.match(result.error, /takeover_mode/i);
}
});

View file

@ -63,7 +63,7 @@ test('executeMutation surfaces bridge failures in normalized response', async ()
return {
success: false,
classification: 'non_zero_exit',
command: 'bd.exe',
command: 'bd',
args,
cwd: root,
stdout: '',
@ -93,7 +93,7 @@ test('executeMutation returns successful normalized response', async () => {
return {
success: true,
classification: null,
command: 'bd.exe',
command: 'bd',
args,
cwd: root,
stdout: '{"id":"bb-123"}',
@ -109,3 +109,62 @@ test('executeMutation returns successful normalized response', async () => {
assert.equal(result.operation, 'update');
assert.equal(result.command.success, true);
});
test('executeMutation includes --actor when provided in payload', async () => {
const payload = validateMutationPayload('comment', {
projectRoot: root,
id: 'bb-123',
text: 'Operator note',
actor: 'zenchant',
});
const result = await executeMutation('comment', payload, {
runBdCommand: async ({ args }) => {
assert.deepEqual(args, ['--actor', 'zenchant', 'comments', 'add', 'bb-123', 'Operator note', '--json']);
return {
success: true,
classification: null,
command: 'bd',
args,
cwd: root,
stdout: '{"ok":true}',
stderr: '',
code: 0,
durationMs: 2,
error: null,
};
},
});
assert.equal(result.ok, true);
});
test('executeMutation ignores bdPath and uses default runner contract', async () => {
const payload = validateMutationPayload('update', {
projectRoot: root,
id: 'bb-123',
status: 'in_progress',
bdPath: 'C:/Tools/beads/bd.exe',
});
const result = await executeMutation('update', payload, {
runBdCommand: async (options) => {
assert.equal(options.explicitBdPath, undefined);
assert.deepEqual(options.args, ['update', 'bb-123', '-s', 'in_progress', '--json']);
return {
success: true,
classification: null,
command: 'bd',
args: options.args,
cwd: root,
stdout: '{"ok":true}',
stderr: '',
code: 0,
durationMs: 2,
error: null,
};
},
});
assert.equal(result.ok, true);
});