Files
bowenliang123 e67fd3e9a6 feat(client): lead the team-member graph cards with the roster name and a 2-line duty
The head keeps a teammate's roster name in bold with their duty in the light
secondary tone (the member cards' desc idiom); the self and lead cards keep
their title. The label box grows to a fixed 3 lines so the duty wraps to 2 —
the card grows uniformly with AGENT_CARD_H, keeping every card's metric row
aligned and the link anchors on the card's edges. The geometry pins derive
from literals so a layout bug cannot mirror its own arithmetic.
2026-10-14 23:31:04 +08:00

125 lines
5.3 KiB
TypeScript

// fleetTeam.ts — the `agentTeam` projection's sanitizing parse and its roster/task helpers.
import assert from 'node:assert/strict'
import { describe, test } from 'vitest'
import {
memberIdByName,
teammateNameOf,
tasksOfOwner,
teamMemberOf,
teamOf,
type FleetTeam,
} from '../../src/client/fleetTeam'
function teamValue(over: Record<string, unknown> = {}): Record<string, unknown> {
return {
members: [
{ id: 'root', name: 'lead', role: 'lead', phase: 'active' },
{ id: 'w1', name: 'worker-bee', role: 'teammate', phase: 'active' },
{ id: 'w2', name: 'flaky-one', role: 'teammate', phase: 'failed', error: 'provider blew up' },
],
tasks: [
{ id: 'task-1', subject: 'schema parsing', description: 'parse the spec', status: 'completed', ownerName: 'worker-bee', blockedBy: [], writeScopes: ['src/api/'], ready: false, writeScopeWarnings: [] },
{ id: 'task-2', subject: 'wire the view', description: '', status: 'in_progress', ownerName: 'worker-bee', blockedBy: ['task-1'], writeScopes: [], ready: false, writeScopeWarnings: ['overlap with task-3'] },
{ id: 'task-3', subject: 'polish', description: '', status: 'pending', blockedBy: ['task-2'], writeScopes: [], ready: false },
{ id: 'task-4', subject: 'docs', description: '', status: 'pending', blockedBy: [], writeScopes: [], ready: true },
],
...over,
}
}
describe('teamOf — the sanitizing parse', () => {
test('a well-formed projection parses whole', () => {
const team = teamOf(teamValue({ failure: 'bad record' }))
assert.ok(team !== null)
assert.equal(team.members.length, 3)
assert.equal(team.tasks.length, 4)
assert.equal(team.failure, 'bad record')
assert.deepEqual(team.tasks[1].warnings, ['overlap with task-3'])
})
test('absent and shapeless values degrade to null', () => {
assert.equal(teamOf(undefined), null)
assert.equal(teamOf(null), null)
assert.equal(teamOf('team'), null)
assert.equal(teamOf({}), null)
assert.equal(teamOf({ members: 'nope' }), null)
})
test('malformed members and tasks drop alone', () => {
const team = teamOf({
members: [
null,
42,
{ id: '', name: 'x', role: 'teammate', phase: 'active' },
{ id: 'x', name: '', role: 'teammate', phase: 'active' },
{ id: 'x', name: 'x', role: 'captain', phase: 'active' },
{ id: 'x', name: 'x', role: 'teammate', phase: 'zombie' },
{ id: 'w9', name: 'nine', role: 'teammate', phase: 'provisioning', error: '' },
],
tasks: [
null,
'task',
{ id: '', subject: 's', status: 'pending' },
{ id: 'task-9', subject: '', status: 'pending' },
{ id: 'task-9', subject: 's', status: 'deleted' },
{ id: 'task-10', subject: 'kept', description: 7, status: 'pending', blockedBy: ['a', 1, ''], writeScopes: 'nope', ready: 'yes', revision: 'r' },
],
failure: 42,
})
assert.ok(team !== null)
assert.deepEqual(team.members, [{ id: 'w9', name: 'nine', role: 'teammate', phase: 'provisioning' }])
assert.equal(team.tasks.length, 1)
assert.deepEqual(team.tasks[0], {
id: 'task-10', subject: 'kept', description: '', status: 'pending',
blockedBy: ['a'], writeScopes: [], ready: false, warnings: [],
})
assert.equal(team.failure, undefined)
})
test('optional fields stay absent rather than materializing empty', () => {
const team = teamOf({ members: [{ id: 'r', name: 'lead', role: 'lead', phase: 'active' }] })
assert.ok(team !== null)
assert.equal(team.members[0].error, undefined)
assert.equal(team.failure, undefined)
// A missing tasks array serves an empty board.
assert.deepEqual(team.tasks, [])
})
})
describe('fleetTeam helpers', () => {
const team = teamOf(teamValue()) as FleetTeam
test('teamMemberOf joins by session id', () => {
assert.equal(teamMemberOf(team, 'w1')?.name, 'worker-bee')
assert.equal(teamMemberOf(team, 'nobody'), null)
assert.equal(teamMemberOf(null, 'w1'), null)
})
test('teammateNameOf names teammates only: the lead, the self card, and outsiders stay plain', () => {
const current = { isCurrent: true, id: 'w1' } as never
const teammate = { isCurrent: false, id: 'w1' } as never
const lead = { isCurrent: false, id: 'root' } as never
const outsider = { isCurrent: false, id: 's9' } as never
assert.equal(teammateNameOf(team, teammate), 'worker-bee')
assert.equal(teammateNameOf(team, current), undefined)
assert.equal(teammateNameOf(team, lead), undefined, 'the lead card keeps the session title')
assert.equal(teammateNameOf(team, outsider), undefined)
assert.equal(teammateNameOf(null, teammate), undefined)
})
test('memberIdByName joins the task board onto roster names', () => {
assert.equal(memberIdByName(team, 'worker-bee'), 'w1')
assert.equal(memberIdByName(team, 'ghost'), undefined)
assert.equal(memberIdByName(team, undefined), undefined)
assert.equal(memberIdByName(null, 'worker-bee'), undefined)
})
test('tasksOfOwner keeps board order', () => {
assert.deepEqual(tasksOfOwner(team, 'worker-bee').map(task => task.id), ['task-1', 'task-2'])
assert.deepEqual(tasksOfOwner(team, 'lead'), [])
assert.deepEqual(tasksOfOwner(team, undefined), [])
assert.deepEqual(tasksOfOwner(null, 'worker-bee'), [])
})
})