diff --git a/src/main/nodes/cockpit-snapshot/connection-summary.test.ts b/src/main/nodes/cockpit-snapshot/connection-summary.test.ts index a6545f1..b4ca4cc 100644 --- a/src/main/nodes/cockpit-snapshot/connection-summary.test.ts +++ b/src/main/nodes/cockpit-snapshot/connection-summary.test.ts @@ -200,3 +200,23 @@ describe('nodesSummary rows', () => { expect([s.rows, s.more, s.footer]).toEqual([[], 0, 'healthy nodes hidden']) }) }) + +describe('nodesSummary with no board of your cockpit (#160)', () => { + const panelNew = (nodeId: number) => node(nodeId, { source: 'new', identified: 'panel' }) + + it('points to the new boards on the bus — the Overview notice’s count — and where to add them', () => { + const one = summary([panelNew(41)]) + expect([one.known, one.newCount]).toEqual([0, 1]) + expect(one.empty).toBe('1 new board on the bus — add it from the Cockpit.') + const two = summary([panelNew(41), pdu(42, 'Left', { source: 'new', identified: 'pdu' })]) + expect(two.newCount).toBe(2) + expect(two.empty).toBe('2 new boards on the bus — add them from the Cockpit.') + }) + + it('says the bus reports nothing only when it does: none on it, or none identified yet', () => { + expect(summary([]).empty).toBe('No nodes reported. Power on a PanelGroup.') + // Still identifying: not news yet (isReportableNew), so not counted. + const s = summary([node(41, { source: 'new' })]) + expect([s.newCount, s.empty]).toEqual([0, 'No nodes reported. Power on a PanelGroup.']) + }) +}) diff --git a/src/main/nodes/cockpit-snapshot/summaries.ts b/src/main/nodes/cockpit-snapshot/summaries.ts index c135e74..8db0ef7 100644 --- a/src/main/nodes/cockpit-snapshot/summaries.ts +++ b/src/main/nodes/cockpit-snapshot/summaries.ts @@ -7,11 +7,14 @@ // adopted (node-model.ts newState), each one a board up and a warning, and an Attention item. // What is on the bus is one rule (node-model.ts onBusNow, X2): while the link is down the tile says // "no live node status", and the banner, the notice and Connected nodes say nothing. +// With none of your cockpit's boards to count, Connected nodes points to the new ones on the bus +// (#160): the same count as the Overview's notice and the Cockpit's "New on the bus". // A Connected nodes row names its board's resolved power zone (D7, the same one the Cockpit reads): // `PDU_Right`, `PDU_Right (hidden)`, else "no power zone". import { hhmmss, lastSeenLabel, plural } from '@shared/format' import { COCKPIT_IDLE, + NO_NODES, type AttentionItem, type AttentionSummary, type CockpitTile, @@ -139,6 +142,16 @@ export function overviewSummary( // ── Connection › Connected nodes ──────────────────────────────────────────── +/** + * The card's line with no board of your cockpit to count: the new boards on the bus and where to + * add them (#160) — `1 new board on the bus — add it from the Cockpit.` — else that the bus + * reports nothing. + */ +function emptyLine(newCount: number): string { + if (newCount === 0) return NO_NODES + return `${plural(newCount, 'new board')} on the bus — add ${newCount === 1 ? 'it' : 'them'} from the Cockpit.` +} + /** * Connected nodes: the counts and the rows that need attention, the Cockpit's strays included (X3: * each one a board up and a warning, with its Attention row). Quiet while the link is down — it @@ -157,6 +170,8 @@ export function nodesSummary( const byId = new Map(views.map((v) => [v.entry.nodeId, v.entry])) const items = attentionItems(views, outages, strays) const healthy = band.online - band.warn + // The Overview's notice's count (the Cockpit's "New on the bus · N"): one rule, isReportableNew. + const newCount = newDevices(views, live)?.count ?? 0 const rows = items.slice(0, ATTENTION_SHOWN).map((i): SummaryRow => { const e = byId.get(i.nodeId)! @@ -202,6 +217,8 @@ export function nodesSummary( watts: band.watts, rows, more, - footer + footer, + newCount, + empty: emptyLine(newCount) } } diff --git a/src/renderer/src/nodes/NodesCard.tsx b/src/renderer/src/nodes/NodesCard.tsx index 0743de6..e3b777e 100644 --- a/src/renderer/src/nodes/NodesCard.tsx +++ b/src/renderer/src/nodes/NodesCard.tsx @@ -25,7 +25,7 @@ export function NodesCard({ autoConnect }: { autoConnect: boolean }) { {!linkUp ? (
{deviceView(deviceState, { autoConnect }).nodesHint}
) : sum.known === 0 ? ( -
No nodes reported. Power on a PanelGroup.
+
{sum.empty}
) : ( <>
diff --git a/src/renderer/test/t5-connection-nodes-card.test.tsx b/src/renderer/test/t5-connection-nodes-card.test.tsx index 80b7c5b..2efcdf3 100644 --- a/src/renderer/test/t5-connection-nodes-card.test.tsx +++ b/src/renderer/test/t5-connection-nodes-card.test.tsx @@ -114,6 +114,26 @@ describe('T5 Connection › nodes summary card (B63, B14, B37, X2)', () => { expect(rowButtons()).toEqual([]) }) + it('with none of your cockpit’s boards, it points to the new ones on the bus (#160)', async () => { + const T0 = Date.UTC(2026, 9, 16, 12, 0, 0) + const fresh = (n: number) => newBoard(n, T0, { identified: 'panel' }) + const { sky } = await renderScenario(, { device: connected(), model: model([]) }) + expect(screen.getByText('No nodes reported. Power on a PanelGroup.')).toBeInTheDocument() + sky.emitScenario({ model: model([fresh(1)]) }) + expect( + screen.getByText('1 new board on the bus — add it from the Cockpit.') + ).toBeInTheDocument() + expect(screen.queryByText('No nodes reported. Power on a PanelGroup.')).toBeNull() + sky.emitScenario({ model: model([fresh(1), fresh(2)]) }) + expect( + screen.getByText('2 new boards on the bus — add them from the Cockpit.') + ).toBeInTheDocument() + // Added to your cockpit: the normal card again. + sky.emitScenario({ model: model([panel(1, 'Center_Armament')]) }) + expect(count('healthy')).toBe('1healthy') + expect(screen.queryByText(/new boards? on the bus/)).toBeNull() + }) + it('"›" opens the Cockpit on that board', async () => { const { user } = await renderScenario(, { device: connected(), model: scene() }) await user.click(screen.getByRole('tab', { name: 'Connection' })) diff --git a/src/shared/nodes/ipc.ts b/src/shared/nodes/ipc.ts index 3ac0ab1..ea7e968 100644 --- a/src/shared/nodes/ipc.ts +++ b/src/shared/nodes/ipc.ts @@ -787,6 +787,16 @@ export interface NodesSummary { more: number /** `+5 more · healthy nodes hidden`; empty when there is nothing to say. */ footer: string + /** + * New boards on the bus that can be added: the Overview's new-devices count (the Cockpit's "New + * on the bus · N"), not part of `known`. + */ + newCount: number + /** + * The card's line when `known` is 0: where to add the new boards on the bus (#160), else that + * the bus reports nothing. + */ + empty: string } /** A registry row's "On the bus" column. */ @@ -848,6 +858,9 @@ export interface CockpitSnapshot { settings: SettingsNodes } +/** Connected nodes' line while the bus reports nothing at all (NodesSummary `empty`). */ +export const NO_NODES = 'No nodes reported. Power on a PanelGroup.' + /** What the Cockpit shows before main's first snapshot: no model, link down. */ export const COCKPIT_IDLE: CockpitSnapshot = { rev: 0, @@ -899,7 +912,9 @@ export const COCKPIT_IDLE: CockpitSnapshot = { watts: null, rows: [], more: 0, - footer: '' + footer: '', + newCount: 0, + empty: NO_NODES }, settings: { differing: 0, canReset: false, hidden: [], registry: { summary: '', rows: [] } } }