Skip to content

Kpi cards#379

Open
cainemerrick98 wants to merge 5 commits intogetnao:mainfrom
cainemerrick98:kpi_cards
Open

Kpi cards#379
cainemerrick98 wants to merge 5 commits intogetnao:mainfrom
cainemerrick98:kpi_cards

Conversation

@cainemerrick98
Copy link

Summary

  • Added a display kpi tool to the agent.
  • Implementation similar to dipslay chart (the agent provides a query id and a display name for the api). The agent may pass more than one kpi to the tool and these kpis will be rendered in one kpi banner.
  • Ensured stories also support kpis.

Examples

KPI Cards in Chat

image

KPI Cards in Story Side Panel

image

KPI Cards in Story Side Panel Editor

image

KPI Cards in Story Thumbnail

image

KPI Cards In Story Viewer

image

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 21 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/frontend/src/components/tool-calls/display-kpi.tsx">

<violation number="1" location="apps/frontend/src/components/tool-calls/display-kpi.tsx:36">
P0: **Rules of Hooks violation**: `useMemo` is called after two conditional early returns. Hooks must be called unconditionally and in the same order on every render. This will cause a React runtime crash when the component transitions between loading/error and resolved states.

Move the `useMemo` call above all early returns (and guard against `config` being undefined inside the memo).</violation>
</file>

<file name="apps/frontend/src/components/side-panel/story-kpi-embed.tsx">

<violation number="1" location="apps/frontend/src/components/side-panel/story-kpi-embed.tsx:25">
P2: Bug: `break` only exits the inner loop over `message.parts`, not the outer loop over `messages`. If the same `queryId` appears in a later message, the previously resolved value will be overwritten. The sibling component `story-chart-embed.tsx` avoids this by using `return` to exit both loops. Consider using a labeled break or refactoring to match the `story-chart-embed.tsx` pattern.</violation>
</file>

<file name="apps/frontend/src/routes/_sidebar-layout.stories.shared.$shareId.tsx">

<violation number="1" location="apps/frontend/src/routes/_sidebar-layout.stories.shared.$shareId.tsx:148">
P1: Runtime crash: `result` can be `undefined` (optional chaining + `as` cast doesn't provide runtime safety), and `result[0]` can be `undefined` for empty arrays. Accessing `.column` on either case throws a `TypeError`. Add null/empty checks similar to `SharedChartEmbed`.</violation>
</file>

<file name="apps/frontend/src/components/side-panel/story-editor.tsx">

<violation number="1" location="apps/frontend/src/components/side-panel/story-editor.tsx:156">
P2: Copy-paste bug: error fallback uses `data-type='chart-block'` instead of `data-type='kpi-block'`. The success branch correctly uses `'kpi-block'`.</violation>
</file>

<file name="apps/frontend/src/lib/stories-page.ts">

<violation number="1" location="apps/frontend/src/lib/stories-page.ts:220">
P2: `segment.type` is a string literal that always equals `'kpi'` — this is not meaningful text for search filtering. It means every story containing a KPI segment will match a search for "kpi", but no actual KPI content is searchable. Consider returning an empty string (since the `kpi` segment type has no text/title field), or a descriptive string if desired.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

);
}

const resolvedKpis = useMemo(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Rules of Hooks violation: useMemo is called after two conditional early returns. Hooks must be called unconditionally and in the same order on every render. This will cause a React runtime crash when the component transitions between loading/error and resolved states.

Move the useMemo call above all early returns (and guard against config being undefined inside the memo).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/components/tool-calls/display-kpi.tsx, line 36:

<comment>**Rules of Hooks violation**: `useMemo` is called after two conditional early returns. Hooks must be called unconditionally and in the same order on every render. This will cause a React runtime crash when the component transitions between loading/error and resolved states.

Move the `useMemo` call above all early returns (and guard against `config` being undefined inside the memo).</comment>

<file context>
@@ -0,0 +1,93 @@
+		);
+	}
+
+	const resolvedKpis = useMemo(
+		() =>
+			config.kpis.map((kpi) => {
</file context>
Fix with Cubic

const resolvedKpis = kpis.map((kpi) => {
console.log(queryData);
const result = queryData?.[kpi.queryId]?.data as Record<string, unknown>[];
const value = result[0][kpi.column] ?? null;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Runtime crash: result can be undefined (optional chaining + as cast doesn't provide runtime safety), and result[0] can be undefined for empty arrays. Accessing .column on either case throws a TypeError. Add null/empty checks similar to SharedChartEmbed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/routes/_sidebar-layout.stories.shared.$shareId.tsx, line 148:

<comment>Runtime crash: `result` can be `undefined` (optional chaining + `as` cast doesn't provide runtime safety), and `result[0]` can be `undefined` for empty arrays. Accessing `.column` on either case throws a `TypeError`. Add null/empty checks similar to `SharedChartEmbed`.</comment>

<file context>
@@ -124,6 +134,30 @@ function SharedChartEmbed({
+	const resolvedKpis = kpis.map((kpi) => {
+		console.log(queryData);
+		const result = queryData?.[kpi.queryId]?.data as Record<string, unknown>[];
+		const value = result[0][kpi.column] ?? null;
+		return { kpi, value };
+	});
</file context>
Suggested change
const value = result[0][kpi.column] ?? null;
const value = result?.[0]?.[kpi.column] ?? null;
Fix with Cubic

if (outputData.length === 1 && part.output.columns.includes(kpi.column)) {
value = outputData[0][kpi.column] ?? null;
}
break;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Bug: break only exits the inner loop over message.parts, not the outer loop over messages. If the same queryId appears in a later message, the previously resolved value will be overwritten. The sibling component story-chart-embed.tsx avoids this by using return to exit both loops. Consider using a labeled break or refactoring to match the story-chart-embed.tsx pattern.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/components/side-panel/story-kpi-embed.tsx, line 25:

<comment>Bug: `break` only exits the inner loop over `message.parts`, not the outer loop over `messages`. If the same `queryId` appears in a later message, the previously resolved value will be overwritten. The sibling component `story-chart-embed.tsx` avoids this by using `return` to exit both loops. Consider using a labeled break or refactoring to match the `story-chart-embed.tsx` pattern.</comment>

<file context>
@@ -0,0 +1,41 @@
+							if (outputData.length === 1 && part.output.columns.includes(kpi.column)) {
+								value = outputData[0][kpi.column] ?? null;
+							}
+							break;
+						}
+					}
</file context>
Fix with Cubic


if (kpis.length === 0) {
return (
<NodeViewWrapper draggable data-type='chart-block'>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Copy-paste bug: error fallback uses data-type='chart-block' instead of data-type='kpi-block'. The success branch correctly uses 'kpi-block'.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/components/side-panel/story-editor.tsx, line 156:

<comment>Copy-paste bug: error fallback uses `data-type='chart-block'` instead of `data-type='kpi-block'`. The success branch correctly uses `'kpi-block'`.</comment>

<file context>
@@ -133,6 +139,84 @@ const ChartBlock = Node.create({
+
+	if (kpis.length === 0) {
+		return (
+			<NodeViewWrapper draggable data-type='chart-block'>
+				<div className='my-2 rounded-lg border border-dashed p-4 text-center text-sm text-muted-foreground'>
+					Invalid KPI block
</file context>
Suggested change
<NodeViewWrapper draggable data-type='chart-block'>
<NodeViewWrapper draggable data-type='kpi-block'>
Fix with Cubic

case 'table':
return segment.title;
case 'kpi':
return segment.type;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: segment.type is a string literal that always equals 'kpi' — this is not meaningful text for search filtering. It means every story containing a KPI segment will match a search for "kpi", but no actual KPI content is searchable. Consider returning an empty string (since the kpi segment type has no text/title field), or a descriptive string if desired.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/frontend/src/lib/stories-page.ts, line 220:

<comment>`segment.type` is a string literal that always equals `'kpi'` — this is not meaningful text for search filtering. It means every story containing a KPI segment will match a search for "kpi", but no actual KPI content is searchable. Consider returning an empty string (since the `kpi` segment type has no text/title field), or a descriptive string if desired.</comment>

<file context>
@@ -216,6 +216,8 @@ function extractSegmentText(segment: SummarySegment): string {
 		case 'table':
 			return segment.title;
+		case 'kpi':
+			return segment.type;
 		case 'grid':
 			return segment.children.map(extractSegmentText).join(' ');
</file context>
Suggested change
return segment.type;
return '';
Fix with Cubic

@Bl3f
Copy link
Contributor

Bl3f commented Mar 1, 2026

Hello! Thank you for your first contribution!

KPI card should not be a tool but more a chart subtype of the current charts.

If we create tools for different kind of visualisation it gonna add so much noise to the context windows.

Second point is: I think the style of the card should be improved, card looks like way too much like the default vibe coded dashboards cards we have on every Lovable apps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants