diff --git a/workspaces/boost/plugins/boost/src/components/catalog/entity/SummaryCard.test.tsx b/workspaces/boost/plugins/boost/src/components/catalog/entity/SummaryCard.test.tsx
new file mode 100644
index 00000000000..489a03aafaa
--- /dev/null
+++ b/workspaces/boost/plugins/boost/src/components/catalog/entity/SummaryCard.test.tsx
@@ -0,0 +1,220 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { type Entity } from '@backstage/catalog-model';
+import { EntityProvider } from '@backstage/plugin-catalog-react';
+import { renderInTestApp } from '@backstage/test-utils';
+import { screen } from '@testing-library/react';
+
+import { SummaryCard } from './SummaryCard';
+
+const fullEntity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'AiResource',
+ metadata: {
+ name: 'code-review-skill',
+ namespace: 'default',
+ uid: 'uid-full',
+ description: 'Automates code reviews using AI',
+ annotations: {
+ 'rhdh.io/ai-asset-version': '1.2.0',
+ 'rhdh.io/ai-asset-source': 'github',
+ },
+ },
+ spec: {
+ type: 'skill',
+ lifecycle: 'production',
+ owner: 'team-ai-platform',
+ rationale: 'Reduce review turnaround time',
+ },
+};
+
+const modelEntity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Resource',
+ metadata: {
+ name: 'granite-3-code',
+ namespace: 'default',
+ uid: 'uid-model',
+ description: 'Code generation model',
+ annotations: {
+ 'rhdh.io/ai-asset-version': '3.0.1',
+ 'rhdh.io/ai-asset-source': 'model-registry',
+ },
+ },
+ spec: {
+ type: 'ai-model',
+ lifecycle: 'production',
+ owner: 'team-ml-ops',
+ },
+};
+
+const minimalEntity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'API',
+ metadata: {
+ name: 'github-mcp-server',
+ namespace: 'default',
+ uid: 'uid-mcp',
+ },
+ spec: {
+ type: 'mcp-server',
+ lifecycle: 'experimental',
+ owner: 'team-integrations',
+ },
+};
+
+const toolEntity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Resource',
+ metadata: {
+ name: 'web-search-tool',
+ namespace: 'default',
+ uid: 'uid-tool',
+ },
+ spec: {
+ type: 'ai-tool',
+ owner: 'team-integrations',
+ },
+};
+
+const emptyEntity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Resource',
+ metadata: {
+ name: 'empty-asset',
+ namespace: 'default',
+ uid: 'uid-empty',
+ },
+ spec: {},
+};
+
+const descriptionOnlyEntity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Resource',
+ metadata: {
+ name: 'described-asset',
+ namespace: 'default',
+ uid: 'uid-desc',
+ description: 'Has only a description',
+ },
+ spec: {},
+};
+
+function renderWithEntity(entity: Entity) {
+ return renderInTestApp(
+
+
+ ,
+ );
+}
+
+describe('SummaryCard', () => {
+ it('renders category badge, version, source, lifecycle, and description for a fully populated entity', async () => {
+ await renderWithEntity(fullEntity);
+
+ // Category badge
+ expect(screen.getByText('Skills')).toBeInTheDocument();
+ // Version
+ expect(screen.getByText('1.2.0')).toBeInTheDocument();
+ // Source
+ expect(screen.getByText(/github/)).toBeInTheDocument();
+ // Lifecycle
+ expect(screen.getByText('production')).toBeInTheDocument();
+ // Description
+ expect(
+ screen.getByText('Automates code reviews using AI'),
+ ).toBeInTheDocument();
+ // Rationale
+ expect(
+ screen.getByText('Reduce review turnaround time'),
+ ).toBeInTheDocument();
+ });
+
+ it('renders category badge and lifecycle, omits version and source when absent', async () => {
+ await renderWithEntity(minimalEntity);
+
+ // Category badge for mcp-server
+ expect(screen.getByText('MCP Servers')).toBeInTheDocument();
+ // Lifecycle
+ expect(screen.getByText('experimental')).toBeInTheDocument();
+ // No version or source
+ expect(screen.queryByText('1.2.0')).toBeNull();
+ expect(screen.queryByText(/Source/)).toBeNull();
+ });
+
+ it('renders category badge with label and color from getCategoryMeta', async () => {
+ await renderWithEntity(modelEntity);
+
+ const badge = screen.getByText('Models');
+ expect(badge).toBeInTheDocument();
+
+ // The badge parent should have the category color style
+ const badgeEl = badge.closest('[style]');
+ expect(badgeEl).toBeTruthy();
+ expect(badgeEl?.getAttribute('style')).toContain('var(--boost-color-model');
+ });
+
+ it('renders green lifecycle badge for production and amber for experimental', async () => {
+ const { unmount } = await renderWithEntity(fullEntity);
+ const productionBadge = screen.getByLabelText(/Lifecycle: production/);
+ const prodStyle = productionBadge.getAttribute('style') ?? '';
+ // Browser may render hex as rgb()
+ expect(
+ prodStyle.includes('#4ade80') || prodStyle.includes('rgb(74, 222, 128)'),
+ ).toBe(true);
+ unmount();
+
+ await renderWithEntity(minimalEntity);
+ const experimentalBadge = screen.getByLabelText(/Lifecycle: experimental/);
+ const expStyle = experimentalBadge.getAttribute('style') ?? '';
+ expect(
+ expStyle.includes('#fbbf24') || expStyle.includes('rgb(251, 191, 36)'),
+ ).toBe(true);
+ });
+
+ it('returns null when description, rationale, and all metadata fields are absent', async () => {
+ const { container } = await renderWithEntity(emptyEntity);
+
+ expect(container.innerHTML).toBe('');
+ });
+
+ it('preserves description and rationale rendering', async () => {
+ await renderWithEntity(fullEntity);
+
+ expect(
+ screen.getByText('Automates code reviews using AI'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText('Reduce review turnaround time'),
+ ).toBeInTheDocument();
+ });
+
+ it('renders when only description is present (no spec metadata)', async () => {
+ await renderWithEntity(descriptionOnlyEntity);
+
+ expect(screen.getByText('Has only a description')).toBeInTheDocument();
+ expect(screen.getByText('Summary')).toBeInTheDocument();
+ });
+
+ it('renders category badge without version or lifecycle when only type is present', async () => {
+ await renderWithEntity(toolEntity);
+
+ expect(screen.getByText('Tools')).toBeInTheDocument();
+ expect(screen.queryByText('production')).toBeNull();
+ expect(screen.queryByText('experimental')).toBeNull();
+ });
+});
diff --git a/workspaces/boost/plugins/boost/src/components/catalog/entity/SummaryCard.tsx b/workspaces/boost/plugins/boost/src/components/catalog/entity/SummaryCard.tsx
index 2e93d9ad444..79c7b98b5aa 100644
--- a/workspaces/boost/plugins/boost/src/components/catalog/entity/SummaryCard.tsx
+++ b/workspaces/boost/plugins/boost/src/components/catalog/entity/SummaryCard.tsx
@@ -15,19 +15,41 @@
*/
import { useEntity } from '@backstage/plugin-catalog-react';
-import { Card, CardBody, CardHeader, Flex, Text } from '@backstage/ui';
+import { Badge, Card, CardBody, CardHeader, Flex, Text } from '@backstage/ui';
import { useTranslation } from '../../../hooks/useTranslation';
+import { getCategoryMeta } from '../../../utils/categoryMeta';
import { getSpecField } from '../../../utils/entityHelpers';
+function getLifecycleColor(lifecycle: string): string {
+ switch (lifecycle.toLowerCase()) {
+ case 'production':
+ return '#4ade80';
+ case 'experimental':
+ return '#fbbf24';
+ default:
+ return '#9ca3af';
+ }
+}
+
export const SummaryCard = () => {
const { entity } = useEntity();
const { t } = useTranslation();
const description = entity.metadata.description ?? '';
const rationale = getSpecField(entity, 'rationale');
+ const specType = getSpecField(entity, 'type');
+ const lifecycle = getSpecField(entity, 'lifecycle');
+ const version =
+ entity.metadata.annotations?.['rhdh.io/ai-asset-version'] ?? '';
+ const source = entity.metadata.annotations?.['rhdh.io/ai-asset-source'] ?? '';
+
+ const categoryMeta = getCategoryMeta(specType);
+ const hasMetadata = !!(specType || version || source || lifecycle);
+
+ if (!description && !rationale && !hasMetadata) return null;
- if (!description && !rationale) return null;
+ const CategoryIcon = categoryMeta.icon;
return (
@@ -36,6 +58,36 @@ export const SummaryCard = () => {
+ {hasMetadata && (
+
+ {specType && (
+
+
+
+ {categoryMeta.label}
+
+
+ )}
+ {version && {version}}
+ {lifecycle && (
+
+ {lifecycle}
+
+ )}
+
+ )}
+ {source && (
+
+ {t('catalog.card.summarySource')}: {source}
+
+ )}
{description && {description}}
{rationale && (
diff --git a/workspaces/boost/plugins/boost/src/translations/ref.ts b/workspaces/boost/plugins/boost/src/translations/ref.ts
index bbf944b4cdc..c1a9872ab46 100644
--- a/workspaces/boost/plugins/boost/src/translations/ref.ts
+++ b/workspaces/boost/plugins/boost/src/translations/ref.ts
@@ -37,6 +37,12 @@ export const boostMessages = {
},
card: {
summaryTitle: 'Summary',
+ summaryCategory: 'Category',
+ summaryVersion: 'Version',
+ summarySource: 'Source',
+ summaryLifecycle: 'Lifecycle',
+ summaryLifecycleProduction: 'Production',
+ summaryLifecycleExperimental: 'Experimental',
adoptionTitle: 'Get Started',
versionTitle: 'Versions',
versionCurrent: 'current',