diff --git a/src/client/components/memberBoard.tsx b/src/client/components/memberBoard.tsx index 833bd2e..6292a7c 100644 --- a/src/client/components/memberBoard.tsx +++ b/src/client/components/memberBoard.tsx @@ -1,15 +1,17 @@ /** * The Fleet members board — every agent of the family in one panel, in two sections: the team's * roster as full cards (identity, duty, latest reply, live task count), then the family's other - * agents (plain subagents, ancestors, siblings) as compact rows. The toolbar's search, status - * filter, and sort steer both sections. A pick pins the agent (ringing its graph node); a member - * card's task chip filters the task board to that owner; the open button jumps to the session. + * agents (plain subagents, ancestors, siblings) as cards in the same form — their label in place + * of a roster name, and the run's tokens/duration/steps in place of the duty line. The toolbar's + * search, status filter, and sort steer both sections. A pick pins the agent (ringing its graph + * node); a member card's task chip filters the task board to that owner; the open button jumps + * to the session. */ import { useMemo, useState, type CSSProperties, type ReactElement } from 'react' import { IconUserOutlineRegular, IconUsersOutlineRegular, StateDot } from '@deepseek-ai/dsh-client-ui-primitives' import type { AgentForest, AgentNode } from '../agentTree' -import { familyHue, fmtDurationCompact } from '../agentTree' +import { familyHue, fmtDurationCompact, pressureColorOf } from '../agentTree' import { tasksOfOwner, teamMemberOf, type FleetTeam } from '../fleetTeam' import type { FleetDetail, FleetMemberInfo } from '../../shared/types' import type { ViewKit } from '../viewkit' @@ -33,12 +35,29 @@ export interface MemberBoardProps { fmt: ViewKit['fmt'] } -/** One member's card hue: the lead carries the brand color, teammates their golden-angled roster - * hue (the graph's family-hue idiom). */ -function memberStyle(index: number, lead: boolean): CSSProperties { - return { - '--lc-member-hue': lead ? 'var(--dsw-alias-brand-primary, var(--color-blue-500))' : familyHue(index), - } as CSSProperties +/** One card's hue: the lead carries the brand color, every other card a golden-angled hue — + * teammates by roster order, other agents by their level-1 family (the graph's link hue). */ +const BRAND_HUE = 'var(--dsw-alias-brand-primary, var(--color-blue-500))' + +function memberStyle(hue: string): CSSProperties { + return { '--lc-member-hue': hue } as CSSProperties +} + +/** The run's activity line above a card's foot: consumption, duration, then steps — the graph + * cards' own meta readout. */ +function memberMeta(node: AgentNode, t: ViewKit['t'], fmt: ViewKit['fmt']): ReactElement | null { + const bits: string[] = [] + if (node.head !== null && node.head.tokens > 0) bits.push(fmt(node.head.tokens)) + if (node.durationMs !== null) bits.push(fmtDurationCompact(node.durationMs)) + if (node.requests > 0) bits.push(t('agents.steps', { n: node.requests })) + const pct = node.head !== null ? node.head.pct : null + if (bits.length === 0 && pct === null) return null + return ( +
+ {bits.join(' · ')} + {pct !== null ? {pct}% : null} +
+ ) } type StatusFilter = 'all' | 'running' | 'done' | 'idle' @@ -183,7 +202,7 @@ export function MemberBoard(props: MemberBoardProps): ReactElement { + (props.pinnedId === member.id ? ' lc-member-pinned' : '') + (member.phase === 'failed' ? ' lc-member-failed' : '') } - style={memberStyle(index, member.role === 'lead')} + style={memberStyle(member.role === 'lead' ? BRAND_HUE : familyHue(index))} role="button" tabIndex={0} onClick={() => { props.onPin(props.pinnedId === member.id ? null : member.id) }} @@ -225,6 +244,7 @@ export function MemberBoard(props: MemberBoardProps): ReactElement { {t('fleet.members.reply')} {detail.lastReply.text} ) : null} + {node !== undefined ? memberMeta(node, t, fmt) : null}
{activeTasks.length > 0 ? (
- + ) : null} diff --git a/src/client/styles/fleet.css b/src/client/styles/fleet.css index 92125a4..3487dcb 100644 --- a/src/client/styles/fleet.css +++ b/src/client/styles/fleet.css @@ -29,9 +29,11 @@ .lc-members-note { margin: 2px 0 6px; font-size: 11px; color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary)); } /* ---- Members board ---- */ -.lc-members-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr)); gap: 8px; } +/* The 200px floor keeps the two-column read the common case: the unified cards are compact + enough that two of them fit the boards column down to a narrow pane. */ +.lc-members-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)); gap: 8px; } .lc-member-card { - display: flex; flex-direction: column; gap: 5px; padding: 10px 12px; border-radius: 10px; + display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; border-radius: 10px; background: var(--dsw-alias-bg-layer-2); /* The member's roster hue (golden-angled, shared with the graph's family links) tints the icon ring. */ border: 1px solid var(--dsw-alias-border-l2); @@ -53,13 +55,19 @@ border: 1px solid color-mix(in srgb, var(--lc-member-hue, var(--dsw-alias-border-l2)) 35%, transparent); } .lc-member-name { flex: 1; min-width: 0; font-size: 12.5px; color: var(--dsw-alias-label-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +/* Other agents wear their prompt title as the name: two clamped lines beat a harsher ellipsis. */ +.lc-member-name-wrap { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; white-space: normal; line-height: 1.25; } .lc-member-dot { flex: none; } .lc-member-status { display: flex; align-items: baseline; gap: 8px; font-size: 11px; color: var(--dsw-alias-label-secondary); } .lc-member-model { font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .lc-member-desc { font-size: 11.5px; line-height: 1.4; color: var(--dsw-alias-label-secondary); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; overflow-wrap: anywhere; } .lc-member-reply { font-size: 11px; line-height: 1.4; color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary)); font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .lc-member-reply::before { content: none; } -.lc-member-foot { display: flex; align-items: center; gap: 6px; margin-top: auto; padding-top: 3px; } +/* The run's activity line (the graph cards' meta readout): figures left, occupancy right. */ +.lc-member-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; font-size: 10.5px; line-height: 1.3; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; } +.lc-member-meta-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.lc-member-meta-pct { flex: none; font-weight: 600; } +.lc-member-foot { display: flex; align-items: center; gap: 6px; margin-top: auto; padding-top: 2px; } .lc-member-tasks { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-brand-primary, var(--color-blue-500)); font: inherit; font-size: 10.5px; padding: 1px 8px; border-radius: 999px; cursor: pointer; } .lc-member-tasks:hover { border-color: var(--dsw-alias-brand-primary, var(--color-blue-500)); } .lc-member-tasks-on { background: color-mix(in srgb, var(--dsw-alias-brand-primary, var(--color-blue-500)) 14%, transparent); border-color: var(--dsw-alias-brand-primary, var(--color-blue-500)); } @@ -98,18 +106,6 @@ .lc-team-task-warning { margin-top: 5px; font-size: 10.5px; color: var(--color-amber-500); } .lc-team-task-meta .lc-inspector-btn { margin-left: auto; } -.lc-fleet-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; } -.lc-fleet-row { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 8px; border: 1px solid transparent; cursor: pointer; font-size: 12px; } -.lc-fleet-row:hover { background: var(--dsw-alias-bg-layer-2); } -.lc-fleet-row:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, var(--color-blue-500)); outline-offset: 1px; } -.lc-fleet-row-pinned { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary, var(--color-blue-500)) 55%, transparent); background: var(--dsw-alias-bg-layer-2); } -.lc-fleet-dot-idle { flex: none; width: 10px; height: 10px; border-radius: 50%; background: color-mix(in srgb, var(--dsw-alias-label-secondary) 30%, transparent); } -.lc-fleet-row-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); } -.lc-fleet-row-team { color: var(--dsw-alias-brand-primary, var(--color-blue-500)); } -.lc-fleet-row-meta { flex: none; font-size: 11px; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; } -.lc-fleet-row-pct { flex: none; font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; } -.lc-fleet-row-open { flex: none; padding: 1px 7px; } - /* ---- Comms timeline ---- */ .lc-comms-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; } .lc-comms-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px; padding: 6px 2px; font-size: 11.5px; border-bottom: 1px dashed var(--dsw-alias-border-l2); border-left: 2px solid transparent; padding-left: 8px; } diff --git a/tests/client/components/fleetView.panels.spec.ts b/tests/client/components/fleetView.panels.spec.ts index 5671ce3..87c9e75 100644 --- a/tests/client/components/fleetView.panels.spec.ts +++ b/tests/client/components/fleetView.panels.spec.ts @@ -84,7 +84,7 @@ function makeView(ctx: TestClientCtx, useKit = kit) { } describe('FleetView — the panels over one family', () => { - test('the other-agents row open button jumps to the session', async () => { + test('the other-agents card open button jumps to the session', async () => { stubFleetRoute() const opened = new Array() const ctx = new TestClientCtx({ @@ -92,7 +92,8 @@ describe('FleetView — the panels over one family', () => { }) const View = makeView(ctx) const m = await mount(h(View, { sessionId: 'root', useProjection: projectionsFor(richTimeline()) })) - await click(query(query(m.container, '.lc-fleet-row'), '.lc-fleet-row-open')) + const cards = queryAll(m.container, '.lc-member-card') + await click(query(cards[cards.length - 1], '.lc-member-open')) assert.deepEqual([...opened], ['w2']) await m.unmount() ctx.dispose() @@ -112,11 +113,12 @@ describe('FleetView — the panels over one family', () => { assert.ok(text(summary).includes('0/1')) // The network card header carries the team chip. assert.ok(text(query(m.container, '.lc-agents')).includes('team · 3 members · 1 tasks')) - // The members board: team cards + the other-agents section (w2, the plain subagent). + // The members board: team cards (the ghost's roster row included) + the other-agents + // section (w2, the plain subagent) in the same card form. const board = query(m.container, '.lc-members') assert.ok(text(board).includes('worker-bee')) assert.ok(text(board).includes(DICT_EN['fleet.board.others'])) - assert.deepEqual(queryAll(board, '.lc-fleet-row').length, 1) + assert.equal(queryAll(board, '.lc-member-card').length, 4) assert.ok(text(query(m.container, '.lc-tasks')).includes('schema parsing')) // The task board's completion bar. assert.ok(text(query(m.container, '.lc-tasks-progress')).includes('0/1 done')) @@ -187,10 +189,6 @@ describe('FleetView — the panels over one family', () => { assert.ok(query(m.container, '[data-agent="w1"]').className.includes('lc-agent-pinned')) await click(query(m.container, '[data-agent="w1"]')) assert.equal(queryAll(m.container, '.lc-agent-pinned').length, 0) - - // The corner button still jumps. - await click(query(query(m.container, '[data-agent="w1"]'), '.lc-agent-open')) - assert.deepEqual([...opened], ['w1']) await m.unmount() ctx.dispose() }) @@ -362,7 +360,7 @@ describe('FleetView — the panels over one family', () => { assert.ok(text(progress).includes('1/2')) // No team: the members board lists the subagents and the task board points at the team mode. assert.ok(text(query(m.container, '.lc-members')).includes(DICT_EN['fleet.board.noTeam'])) - assert.deepEqual(queryAll(m.container, '.lc-fleet-row').length, 2) + assert.equal(queryAll(m.container, '.lc-member-card').length, 2) assert.ok(text(query(m.container, '.lc-tasks')).includes(DICT_EN['fleet.tasks.noTeam'])) await m.unmount() ctx.dispose() diff --git a/tests/client/components/memberBoard.spec.ts b/tests/client/components/memberBoard.spec.ts index 20fff00..9df71e9 100644 --- a/tests/client/components/memberBoard.spec.ts +++ b/tests/client/components/memberBoard.spec.ts @@ -1,6 +1,7 @@ // MemberBoard — every family agent in one panel: the team roster as full cards (identity, status, -// model, duty, latest reply, owner-filter interlock), the other agents as compact rows, and the -// toolbar (search / status / sort) steering both sections, down to the no-team and solo guidance. +// model, duty, latest reply, owner-filter interlock), the other agents as cards in the same form +// (label as the name, run meta in place of the duty line), and the toolbar (search / status / sort) +// steering both sections, down to the no-team and solo guidance. import { act, createElement as h, type ReactElement } from 'react' import assert from 'node:assert/strict' @@ -103,8 +104,9 @@ describe('MemberBoard — team cards', () => { detailOf: id => details.get(id), pinnedId: 'w1', }))) + // Four roster cards, then the three other-agents cards in the same form. const cards = queryAll(m.container, '.lc-member-card') - assert.equal(cards.length, 4) + assert.equal(cards.length, 7) const lead = cards[0] assert.ok(text(lead).includes('lead')) @@ -230,87 +232,101 @@ describe('MemberBoard — team cards', () => { }) }) -describe('MemberBoard — the other-agents rows', () => { - function rowLabels(container: HTMLElement): string[] { - return queryAll(container, '.lc-fleet-row-label').map(el => el.textContent ?? '') +describe('MemberBoard — the other-agents cards', () => { + /** The cards of the "other agents" section: the grid after that section's own head. */ + function otherCards(container: HTMLElement): HTMLElement[] { + const head = queryAll(container, '.lc-board-section-head') + .find(h => text(h).includes(DICT_EN['fleet.board.others'])) + return queryAll((head?.nextElementSibling ?? container) as HTMLElement, '.lc-member-card') + } + function otherNames(container: HTMLElement): string[] { + return otherCards(container).map(c => text(query(c, '.lc-member-name'))) } - test('non-member family agents list as compact rows with meta, pct, and the open jump', async () => { + test('non-member family agents render as member cards with status, meta, and the open jump', async () => { const pins = new Array() const opened: string[] = [] const hovers: (string | null)[] = [] + // A landed detail fills the card's reply line, like the roster cards'. + const details = new Map([ + ['s2', { detail: { lastReply: { time: 100, text: 'archive sealed' }, messages: [], truncated: false } }], + ]) const m = await mount(h(MemberBoard, propsOf({ + detailOf: id => details.get(id), onPin: id => { pins.push(id) }, onOpen: id => { opened.push(id) }, onHover: id => { hovers.push(id) }, }))) - // The section head, then one row per plain subagent — the current session and team members stay out. + // The section head, then one card per plain subagent — the current session and team members stay out. assert.ok(text(m.container).includes(DICT_EN['fleet.board.others'])) - assert.deepEqual(rowLabels(m.container), ['scout', 'archivist', 'lounger']) - const rows = queryAll(m.container, '.lc-fleet-row') - assert.ok(text(rows[0]).includes('800 · deepseek-flash · 42s · 5 steps')) - assert.ok(text(rows[0]).includes('80%')) - assert.equal(query(rows[2], '.lc-fleet-row-meta').textContent, '', 'a stat-less row carries no meta') - assert.equal(queryAll(rows[2], '.lc-fleet-dot-idle').length, 1, 'the idle row wears the placeholder dot') + assert.deepEqual(otherNames(m.container), ['scout', 'archivist', 'lounger']) + const cards = otherCards(m.container) + // The run meta rides the card: consumption, duration, steps — and the occupancy figure. + assert.ok(text(cards[0]).includes('800 · 42s · 5 steps')) + assert.ok(text(cards[0]).includes('80%')) + assert.ok(text(cards[0]).includes('deepseek-flash')) + assert.ok(text(cards[0]).includes('running')) + assert.ok(text(cards[1]).includes('archive sealed'), 'the landed latest reply fills the card') + // A stat-less agent carries no meta line and reads as inactive. + assert.equal(queryAll(cards[2], '.lc-member-meta').length, 0) + assert.ok(text(cards[2]).includes(DICT_EN['fleet.memberInactive'])) - await hover(rows[0]) - await click(rows[0]) - await unhover(rows[0]) + await hover(cards[0]) + await click(cards[0]) + await unhover(cards[0]) assert.deepEqual([...pins], ['s1']) assert.deepEqual(hovers, ['s1', null]) - await click(query(rows[0], '.lc-fleet-row-open')) + await click(query(cards[0], '.lc-member-open')) assert.deepEqual(opened, ['s1']) assert.deepEqual([...pins], ['s1'], 'open never pins') - // Keyboard: Enter pins, Space releases, an unrelated key is inert. - await act(async () => { rows[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true })) }) - await act(async () => { rows[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'x', bubbles: true, cancelable: true })) }) + // Keyboard: Enter pins, an unrelated key is inert. + await act(async () => { cards[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true })) }) + await act(async () => { cards[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'x', bubbles: true, cancelable: true })) }) assert.deepEqual([...pins], ['s1', 's1']) await m.unmount() - // The pinned row rings; a re-click releases, and so does the Space key. + // The pinned card rings; a re-click releases, and so does the Space key. const again = new Array() const m2 = await mount(h(MemberBoard, propsOf({ pinnedId: 's1', onPin: id => { again.push(id) } }))) - const pinnedRow = queryAll(m2.container, '.lc-fleet-row')[0] - assert.ok(pinnedRow.className.includes('lc-fleet-row-pinned')) - await click(pinnedRow) - await act(async () => { pinnedRow.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true, cancelable: true })) }) + const pinnedCard = otherCards(m2.container)[0] + assert.ok(pinnedCard.className.includes('lc-member-pinned')) + await click(pinnedCard) + await act(async () => { pinnedCard.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true, cancelable: true })) }) assert.deepEqual([...again], [null, null]) await m2.unmount() }) test('the toolbar searches, filters by status, and sorts — across both sections', async () => { const m = await mount(h(MemberBoard, propsOf())) - // Status filter: running keeps the running members' cards and the running subagent's row. + // Status filter: running keeps the running members' cards and the running subagent's card. const filters = queryAll(m.container, '.lc-fleet-filters .lc-gran-btn') await click(filters[1]) - assert.deepEqual(queryAll(m.container, '.lc-member-card').map(c => text(query(c, '.lc-member-name'))), ['lead', 'worker-bee']) - assert.deepEqual(rowLabels(m.container), ['scout']) + assert.deepEqual(queryAll(m.container, '.lc-member-card').map(c => text(query(c, '.lc-member-name'))), ['lead', 'worker-bee', 'scout']) await click(filters[2]) - assert.deepEqual(rowLabels(m.container), ['archivist']) - assert.equal(queryAll(m.container, '.lc-member-card').length, 0, 'no member node is done') + assert.deepEqual(queryAll(m.container, '.lc-member-card').map(c => text(query(c, '.lc-member-name'))), ['archivist']) await click(filters[3]) - assert.deepEqual(rowLabels(m.container), ['lounger']) + assert.deepEqual(queryAll(m.container, '.lc-member-card').map(c => text(query(c, '.lc-member-name'))), ['flaky-one', 'new-joiner', 'lounger']) await click(filters[0]) // Sort by context tokens: 800 ahead of 200 ahead of the headless idler. await selectIn(query(m.container, '.lc-fleet-sort'), 'tokens') - assert.deepEqual(rowLabels(m.container), ['scout', 'archivist', 'lounger']) + assert.deepEqual(otherNames(m.container), ['scout', 'archivist', 'lounger']) // (Spawn order coincides here; the reverse fixture order proves the sort.) await m.unmount() // A headless idler mid-list exercises the sorters' null-head arms on both sides. const flipped: AgentForest = { ...FOREST, nodes: [FOREST.nodes[0], FOREST.nodes[1], FOREST.nodes[2], FOREST.nodes[3], FOREST.nodes[5], FOREST.nodes[4]] } const m2 = await mount(h(MemberBoard, propsOf({ forest: flipped }))) - assert.deepEqual(rowLabels(m2.container), ['scout', 'lounger', 'archivist'], 'spawn order follows the forest') + assert.deepEqual(otherNames(m2.container), ['scout', 'lounger', 'archivist'], 'spawn order follows the forest') await selectIn(query(m2.container, '.lc-fleet-sort'), 'tokens') - assert.deepEqual(rowLabels(m2.container), ['scout', 'archivist', 'lounger']) + assert.deepEqual(otherNames(m2.container), ['scout', 'archivist', 'lounger']) await selectIn(query(m2.container, '.lc-fleet-sort'), 'duration') - assert.deepEqual(rowLabels(m2.container), ['scout', 'archivist', 'lounger']) + assert.deepEqual(otherNames(m2.container), ['scout', 'archivist', 'lounger']) await selectIn(query(m2.container, '.lc-fleet-sort'), 'steps') - assert.deepEqual(rowLabels(m2.container), ['scout', 'archivist', 'lounger']) + assert.deepEqual(otherNames(m2.container), ['scout', 'archivist', 'lounger']) await selectIn(query(m2.container, '.lc-fleet-sort'), 'recent') - assert.deepEqual(rowLabels(m2.container), ['scout', 'archivist', 'lounger']) + assert.deepEqual(otherNames(m2.container), ['scout', 'archivist', 'lounger']) await m2.unmount() }) @@ -318,7 +334,6 @@ describe('MemberBoard — the other-agents rows', () => { const m = await mount(h(MemberBoard, propsOf())) await typeIn(query(m.container, '.lc-fleet-search'), 'worker') assert.equal(queryAll(m.container, '.lc-member-card').length, 1) - assert.equal(queryAll(m.container, '.lc-fleet-row').length, 0) await typeIn(query(m.container, '.lc-fleet-search'), 'zzz-no-match') assert.equal(queryAll(m.container, '.lc-member-card').length, 0) assert.ok(text(m.container).includes(DICT_EN['fleet.list.empty'])) @@ -327,13 +342,12 @@ describe('MemberBoard — the other-agents rows', () => { }) describe('MemberBoard — guidance states', () => { - test('no team: the note names the mode and the family rows still list', async () => { + test('no team: the note names the mode and the family cards still list', async () => { const m = await mount(h(MemberBoard, propsOf({ team: null }))) assert.ok(text(m.container).includes(DICT_EN['fleet.board.noTeam'])) - assert.equal(queryAll(m.container, '.lc-member-card').length, 0) - // Without a roster every non-current node is an "other agent". + // Without a roster every non-current node is an "other agent" — in the same card form. assert.deepEqual( - queryAll(m.container, '.lc-fleet-row-label').map(el => el.textContent), + queryAll(m.container, '.lc-member-card .lc-member-name').map(el => el.textContent), ['w1', 'w2', 'scout', 'archivist', 'lounger'], ) await m.unmount() @@ -344,7 +358,7 @@ describe('MemberBoard — guidance states', () => { const m = await mount(h(MemberBoard, propsOf({ team: null, forest: solo }))) assert.ok(text(m.container).includes(DICT_EN['fleet.board.solo'])) assert.equal(queryAll(m.container, '.lc-fleet-toolbar').length, 0) - assert.equal(queryAll(m.container, '.lc-fleet-row').length, 0) + assert.equal(queryAll(m.container, '.lc-member-card').length, 0) await m.unmount() }) @@ -357,11 +371,11 @@ describe('MemberBoard — guidance states', () => { await m.unmount() }) - test('a team whose members all filtered out hides the section but keeps the rows', async () => { + test('a team whose members all filtered out hides the section but keeps the other cards', async () => { const m = await mount(h(MemberBoard, propsOf())) await typeIn(query(m.container, '.lc-fleet-search'), 'scout') - assert.equal(queryAll(m.container, '.lc-member-card').length, 0) - assert.equal(queryAll(m.container, '.lc-fleet-row').length, 1) + assert.ok(text(m.container).includes('scout')) + assert.equal(queryAll(m.container, '.lc-member-card').length, 1) await m.unmount() }) })