Skip to content

Commit be8a5c0

Browse files
committed
fix(showcase/dashboard): render Starter row-group in the live FeatureGrid (was in dead CellMatrix)
PR CopilotKit#5226 added the "Starter" pseudo-category row-group (StarterSection, using resolveStarterRow/buildStarterBadge) only to CellMatrix in cell-matrix.tsx. But the live DashboardPage renders the matrix tab via FeatureGrid (feature-grid.tsx); CellMatrix is reached only through cells-view.tsx, which no live route imports — it is dead/legacy. So the Starter row shipped in code but could never render (the served prod bundle had zero StarterSection/starter-row-/"no starter" strings). Port the StarterSection into FeatureGrid: render the four fixed sub-rows (health/agent/chat/interaction) after the feature categories, resolving each cell via the existing S3 helpers (resolveStarterRow + buildStarterBadge, 5-state vocab) and structurally excluded from the feature rollup/column tally (never calls renderCell/buildCellModel). Aligned to FeatureGrid's table structure (Feature col + optional parity ref-depth spacer + categoryColSpan). Remove the duplicate from the dead CellMatrix and move its dedicated tests to feature-grid.test.tsx, which exercises the live registry-backed grid. Render proof: production next build now bakes starter-row-/starter-cell-/ "no starter for this integration" into the served page.js (client + server) bundles. Full dashboard suite green (815 passed, 1 skipped).
1 parent de6a533 commit be8a5c0

4 files changed

Lines changed: 253 additions & 214 deletions

File tree

showcase/shell-dashboard/src/components/__tests__/cell-matrix.test.tsx

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CellMatrix } from "../cell-matrix";
88
import type { CatalogCell } from "../depth-utils";
99
import type { LiveStatusMap, StatusRow } from "@/lib/live-status";
1010
import type { FeatureCategory } from "@/lib/registry";
11-
import { E2E_STALE_AFTER_MS, STARTER_STALE_AFTER_MS } from "@/lib/staleness";
11+
import { E2E_STALE_AFTER_MS } from "@/lib/staleness";
1212

1313
// Mock localStorage
1414
const storageMap = new Map<string, string>();
@@ -712,88 +712,3 @@ describe("CellMatrix", () => {
712712
}
713713
});
714714
});
715-
716-
/* ------------------------------------------------------------------ */
717-
/* Starter row-group (spec §d) */
718-
/* ------------------------------------------------------------------ */
719-
720-
describe("CellMatrix — Starter row-group", () => {
721-
// agno is a MAPPED starter column; ag2 is one of the 7 NOT-supported columns.
722-
const starterIntegrations: IntegrationInfo[] = [
723-
{ slug: "agno", name: "Agno", tier: "at_parity" },
724-
{ slug: "ag2", name: "AG2", tier: "partial" },
725-
];
726-
727-
const renderMatrix = (live: LiveStatusMap) =>
728-
render(
729-
<CellMatrix
730-
cells={[]}
731-
categories={[]}
732-
features={[]}
733-
integrations={starterIntegrations}
734-
liveStatus={live}
735-
defaultOpenCategories={new Set()}
736-
filter="all"
737-
referenceSlug="agno"
738-
/>,
739-
);
740-
741-
it("renders the Starter header and four fixed sub-rows", () => {
742-
const { getByText, getByTestId } = renderMatrix(new Map());
743-
expect(getByText("Starter")).toBeDefined();
744-
for (const level of ["health", "agent", "chat", "interaction"]) {
745-
expect(getByTestId(`starter-row-${level}`)).toBeDefined();
746-
}
747-
});
748-
749-
it("✓ green for a passing mapped starter cell", () => {
750-
const live = mapOf([row("starter:agno/health", "starter", "green")]);
751-
const { getByTestId } = renderMatrix(live);
752-
const cell = getByTestId("starter-cell-agno-health");
753-
expect(cell.textContent).toContain("✓");
754-
});
755-
756-
it("red ✗ for a failed mapped starter cell", () => {
757-
const live = mapOf([row("starter:agno/chat", "starter", "red")]);
758-
const { getByTestId } = renderMatrix(live);
759-
const cell = getByTestId("starter-cell-agno-chat");
760-
expect(cell.textContent).toContain("✗");
761-
});
762-
763-
it("gray ? for a mapped column with no row yet (not-yet-run)", () => {
764-
const { getByTestId } = renderMatrix(new Map());
765-
const cell = getByTestId("starter-cell-agno-agent");
766-
expect(cell.textContent).toContain("?");
767-
});
768-
769-
it("not-supported ✗ for an unmapped column, tooltip 'no starter for this integration'", () => {
770-
const { getByTestId } = renderMatrix(new Map());
771-
const cell = getByTestId("starter-cell-ag2-health");
772-
// grey ✗ — the chip carries the tooltip title.
773-
expect(cell.textContent).toContain("✗");
774-
const chip = cell.querySelector("[title]");
775-
expect(chip?.getAttribute("title")).toBe("no starter for this integration");
776-
});
777-
778-
it("~ stale: a frozen-green starter row downgrades to amber ~", () => {
779-
const staleAt = new Date(
780-
Date.now() - STARTER_STALE_AFTER_MS - 1,
781-
).toISOString();
782-
const live = mapOf([
783-
{
784-
id: "id-stale",
785-
key: "starter:agno/interaction",
786-
dimension: "starter",
787-
state: "green" as const,
788-
signal: {},
789-
observed_at: staleAt,
790-
transitioned_at: staleAt,
791-
fail_count: 0,
792-
first_failure_at: null,
793-
},
794-
]);
795-
const { getByTestId } = renderMatrix(live);
796-
const cell = getByTestId("starter-cell-agno-interaction");
797-
expect(cell.textContent).toContain("~");
798-
});
799-
});

showcase/shell-dashboard/src/components/cell-matrix.tsx

Lines changed: 2 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,12 @@ import { DepthChip } from "./depth-chip";
1515
import { CellDrilldown } from "./cell-drilldown";
1616
import { IntegrationHeader } from "./integration-header";
1717
import { useCollapsible, CategoryHeaderRow } from "./collapsible-category";
18-
import { ToneChip } from "./badges";
1918
import { buildCellModel } from "@/lib/cell-model";
2019
import type { CatalogCell } from "./depth-utils";
2120
import type { ParityTier } from "./parity-badge";
2221
import type { FilterMode } from "./filter-chips";
23-
import {
24-
resolveCell,
25-
resolveStarterRow,
26-
buildStarterBadge,
27-
starterIsSupported,
28-
STARTER_LEVELS,
29-
} from "@/lib/live-status";
30-
import type {
31-
LiveStatusMap,
32-
ConnectionStatus,
33-
StarterLevel,
34-
} from "@/lib/live-status";
22+
import { resolveCell } from "@/lib/live-status";
23+
import type { LiveStatusMap, ConnectionStatus } from "@/lib/live-status";
3524
import type { FeatureCategory } from "@/lib/registry";
3625

3726
/** Identifies a selected cell for drilldown. */
@@ -228,105 +217,6 @@ function CategorySection({
228217
);
229218
}
230219

231-
/* ------------------------------------------------------------------ */
232-
/* StarterSection — the "Starter" pseudo-category row-group (spec §d) */
233-
/* ------------------------------------------------------------------ */
234-
235-
/** Human-readable label per starter sub-row, in STARTER_LEVELS order. */
236-
const STARTER_LEVEL_LABEL: Record<StarterLevel, string> = {
237-
health: "Health",
238-
agent: "Agent",
239-
chat: "Chat",
240-
interaction: "Interaction",
241-
};
242-
243-
interface StarterSectionProps {
244-
visibleIntegrations: IntegrationInfo[];
245-
liveStatus: LiveStatusMap;
246-
defaultOpen: boolean;
247-
connection: ConnectionStatus;
248-
now: number;
249-
}
250-
251-
/**
252-
* The "Starter" row-group: four fixed sub-rows (health/agent/chat/interaction)
253-
* keyed to the integration columns. Rendered like a `CategorySection`, but the
254-
* cells resolve via `resolveStarterRow` + `buildStarterBadge` (the full 5-state
255-
* §d vocabulary) instead of the depth model.
256-
*
257-
* INFORMATIONAL ONLY: this group never calls `resolveCell`, so starter rows
258-
* cannot contribute to the feature-cell `rollup` (spec §d) — the exclusion is
259-
* structural, not a filter.
260-
*/
261-
function StarterSection({
262-
visibleIntegrations,
263-
liveStatus,
264-
defaultOpen,
265-
connection,
266-
now,
267-
}: StarterSectionProps) {
268-
const { isOpen, toggle } = useCollapsible({
269-
name: "Starter",
270-
defaultOpen,
271-
});
272-
273-
const supportedCount = visibleIntegrations.filter((int) =>
274-
starterIsSupported(int.slug),
275-
).length;
276-
277-
return (
278-
<>
279-
<CategoryHeaderRow
280-
name="Starter"
281-
count={`${supportedCount}/${visibleIntegrations.length}`}
282-
colSpan={visibleIntegrations.length + 1}
283-
isOpen={isOpen}
284-
onToggle={toggle}
285-
/>
286-
{isOpen &&
287-
STARTER_LEVELS.map((level) => (
288-
<tr
289-
key={level}
290-
data-testid={`starter-row-${level}`}
291-
className="border-t border-[var(--border)] hover:bg-[var(--bg-hover)]"
292-
>
293-
<td className="sticky left-0 z-10 bg-[var(--bg-surface)] px-4 py-1.5 border-r border-[var(--border)] align-middle min-w-[200px]">
294-
<span className="text-xs text-[var(--text)]">
295-
{STARTER_LEVEL_LABEL[level]}
296-
</span>
297-
</td>
298-
{visibleIntegrations.map((int) => {
299-
const isSupported = starterIsSupported(int.slug);
300-
const row = isSupported
301-
? resolveStarterRow(liveStatus, int.slug, level)
302-
: null;
303-
const badge = buildStarterBadge(
304-
level,
305-
isSupported,
306-
row,
307-
now,
308-
connection,
309-
);
310-
return (
311-
<td
312-
key={int.slug}
313-
data-testid={`starter-cell-${int.slug}-${level}`}
314-
className="border-l border-[var(--border)] px-3 py-1.5 align-middle text-center"
315-
>
316-
<ToneChip
317-
tone={badge.tone}
318-
label={badge.label}
319-
title={badge.tooltip}
320-
/>
321-
</td>
322-
);
323-
})}
324-
</tr>
325-
))}
326-
</>
327-
);
328-
}
329-
330220
/* ------------------------------------------------------------------ */
331221
/* CellMatrix */
332222
/* ------------------------------------------------------------------ */
@@ -515,20 +405,6 @@ export function CellMatrix({
515405
/>
516406
);
517407
})}
518-
{/*
519-
* "Starter" pseudo-category row-group (spec §d). Informational
520-
* smoke-health for the deployed starter services — rendered after
521-
* the feature categories, never contributing to any feature cell's
522-
* rollup (it does not call resolveCell). Shown across all filter
523-
* modes since it is a health surface, not a feature-coverage row.
524-
*/}
525-
<StarterSection
526-
visibleIntegrations={visibleIntegrations}
527-
liveStatus={liveStatus}
528-
defaultOpen
529-
connection={connection}
530-
now={now}
531-
/>
532408
</tbody>
533409
</table>
534410
</div>

showcase/shell-dashboard/src/components/feature-grid.test.tsx

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@ import { render } from "@testing-library/react";
1111
import { LiveIndicator, computeColumnTally, FeatureGrid } from "./feature-grid";
1212
import type { CellContext, CellRenderer } from "./feature-grid";
1313
import { urlsFor } from "./cell-pieces";
14+
import { getIntegrations } from "@/lib/registry";
15+
import { starterIsSupported, STARTER_LEVELS } from "@/lib/live-status";
1416
import type { Integration, Feature } from "@/lib/registry";
15-
import type { LiveStatusMap, StatusRow } from "@/lib/live-status";
17+
import type {
18+
LiveStatusMap,
19+
StatusRow,
20+
ConnectionStatus,
21+
} from "@/lib/live-status";
22+
import { STARTER_STALE_AFTER_MS } from "@/lib/staleness";
1623

1724
describe("LiveIndicator", () => {
1825
it("renders live → green solid dot", () => {
@@ -232,3 +239,101 @@ describe("computeColumnTally", () => {
232239
expect(t).toEqual({ green: 0, amber: 1, red: 0, unknown: false });
233240
});
234241
});
242+
243+
/* ------------------------------------------------------------------ */
244+
/* Starter row-group (spec §d) — must render in the LIVE FeatureGrid */
245+
/* (it was ported here from the dead CellMatrix, where it could never */
246+
/* reach the served dashboard). */
247+
/* ------------------------------------------------------------------ */
248+
249+
describe("FeatureGrid — Starter row-group", () => {
250+
// FeatureGrid pulls from the REAL registry (getIntegrations), so the Starter
251+
// section renders against live integration slugs. Resolve a mapped and an
252+
// unmapped column from the registry itself rather than hardcoding slugs.
253+
const integrations = getIntegrations();
254+
const mapped = integrations.find((i) => starterIsSupported(i.slug));
255+
const unmapped = integrations.find((i) => !starterIsSupported(i.slug));
256+
257+
const renderGrid = (
258+
live: LiveStatusMap,
259+
connection: ConnectionStatus = "live",
260+
) =>
261+
render(
262+
<FeatureGrid
263+
title="Feature Matrix"
264+
renderCell={() => null}
265+
liveStatus={live}
266+
connection={connection}
267+
shellUrl="https://showcase.staging.copilotkit.ai"
268+
/>,
269+
);
270+
271+
it("renders the Starter header and all four fixed sub-rows", () => {
272+
const { getByText, getByTestId } = renderGrid(new Map());
273+
expect(getByText("Starter")).toBeDefined();
274+
for (const level of STARTER_LEVELS) {
275+
expect(getByTestId(`starter-row-${level}`)).toBeDefined();
276+
}
277+
});
278+
279+
it("a mapped column with no row yet renders the gray ? no-data cell", () => {
280+
expect(mapped, "registry must have ≥1 mapped starter column").toBeDefined();
281+
const { getByTestId } = renderGrid(new Map());
282+
const cell = getByTestId(`starter-cell-${mapped!.slug}-health`);
283+
expect(cell.textContent).toContain("?");
284+
});
285+
286+
it("an unmapped column renders the not-supported ✗ with the 'no starter' tooltip", () => {
287+
expect(unmapped, "registry must have ≥1 unmapped column").toBeDefined();
288+
const { getByTestId } = renderGrid(new Map());
289+
const cell = getByTestId(`starter-cell-${unmapped!.slug}-health`);
290+
expect(cell.textContent).toContain("✗");
291+
const chip = cell.querySelector("[title]");
292+
expect(chip?.getAttribute("title")).toBe("no starter for this integration");
293+
});
294+
295+
it("✓ green for a passing mapped starter cell", () => {
296+
expect(mapped).toBeDefined();
297+
const key = `starter:${mapped!.slug}/health`;
298+
const live: LiveStatusMap = new Map([[key, row(key, "starter", "green")]]);
299+
const { getByTestId } = renderGrid(live);
300+
const cell = getByTestId(`starter-cell-${mapped!.slug}-health`);
301+
expect(cell.textContent).toContain("✓");
302+
});
303+
304+
it("red ✗ for a failed mapped starter cell", () => {
305+
expect(mapped).toBeDefined();
306+
const key = `starter:${mapped!.slug}/chat`;
307+
const live: LiveStatusMap = new Map([[key, row(key, "starter", "red")]]);
308+
const { getByTestId } = renderGrid(live);
309+
const cell = getByTestId(`starter-cell-${mapped!.slug}-chat`);
310+
expect(cell.textContent).toContain("✗");
311+
});
312+
313+
it("~ amber for a frozen-green starter row past the staleness window", () => {
314+
expect(mapped).toBeDefined();
315+
const key = `starter:${mapped!.slug}/interaction`;
316+
const staleAt = new Date(
317+
Date.now() - STARTER_STALE_AFTER_MS - 1,
318+
).toISOString();
319+
const live: LiveStatusMap = new Map([
320+
[
321+
key,
322+
{
323+
id: "id-stale",
324+
key,
325+
dimension: "starter",
326+
state: "green" as const,
327+
signal: {},
328+
observed_at: staleAt,
329+
transitioned_at: staleAt,
330+
fail_count: 0,
331+
first_failure_at: null,
332+
},
333+
],
334+
]);
335+
const { getByTestId } = renderGrid(live);
336+
const cell = getByTestId(`starter-cell-${mapped!.slug}-interaction`);
337+
expect(cell.textContent).toContain("~");
338+
});
339+
});

0 commit comments

Comments
 (0)