diff --git a/src/frontend/src/assets/dashboard/ai-coding-agents/ai-agents-dialog-dark.png b/src/frontend/src/assets/dashboard/ai-coding-agents/ai-agents-dialog-dark.png new file mode 100644 index 000000000..f6ab03d15 Binary files /dev/null and b/src/frontend/src/assets/dashboard/ai-coding-agents/ai-agents-dialog-dark.png differ diff --git a/src/frontend/src/assets/dashboard/ai-coding-agents/ai-agents-dialog-light.png b/src/frontend/src/assets/dashboard/ai-coding-agents/ai-agents-dialog-light.png new file mode 100644 index 000000000..c8da28dd8 Binary files /dev/null and b/src/frontend/src/assets/dashboard/ai-coding-agents/ai-agents-dialog-light.png differ diff --git a/src/frontend/src/components/ImageShowcase.astro b/src/frontend/src/components/ImageShowcase.astro index f546b2891..163152fdc 100644 --- a/src/frontend/src/components/ImageShowcase.astro +++ b/src/frontend/src/components/ImageShowcase.astro @@ -6,17 +6,36 @@ import { Icon } from '@astrojs/starlight/components'; import { Image } from 'astro:assets'; import { Zoom } from 'starlight-image-zoom/components'; +import ThemeImage from '@components/ThemeImage.astro'; -interface Props { +type BaseProps = { title: string; description: string; - image: ImageMetadata; imageAlt: string; imagePosition?: 'left' | 'right'; cta?: { label: string; href: string }; -} - -const { title, description, image, imageAlt, imagePosition = 'right', cta } = Astro.props; +}; + +type Props = BaseProps & + ( + | { + image: ImageMetadata; + lightImage?: never; + darkImage?: never; + } + | { + image?: never; + lightImage: ImageMetadata; + darkImage: ImageMetadata; + } + ); + +const { title, description, imageAlt, imagePosition = 'right', cta } = Astro.props; +const themedImages = + 'lightImage' in Astro.props && 'darkImage' in Astro.props + ? { light: Astro.props.lightImage, dark: Astro.props.darkImage } + : undefined; +const singleImage = 'image' in Astro.props ? Astro.props.image : undefined; ---
@@ -33,9 +52,22 @@ const { title, description, image, imageAlt, imagePosition = 'right', cta } = As }
- - {imageAlt} - + { + themedImages ? ( + + ) : ( + singleImage && ( + + {imageAlt} + + ) + ) + }
diff --git a/src/frontend/src/content/docs/dashboard/index.mdx b/src/frontend/src/content/docs/dashboard/index.mdx index f40e68950..00b874679 100644 --- a/src/frontend/src/content/docs/dashboard/index.mdx +++ b/src/frontend/src/content/docs/dashboard/index.mdx @@ -23,7 +23,8 @@ import projectsImage from '@assets/dashboard/explore/resources-filtered-containe import tracesImage from '@assets/dashboard/explore/trace-span-details.png'; import metricsImage from '@assets/dashboard/explore/metrics-view.png'; import structuredLogsImage from '@assets/dashboard/explore/structured-logs-errors-view.png'; -import mcpDialogImage from '@assets/dashboard/mcp-server/mcp-dialog.png'; +import aiAgentsDialogDark from '@assets/dashboard/ai-coding-agents/ai-agents-dialog-dark.png'; +import aiAgentsDialogLight from '@assets/dashboard/ai-coding-agents/ai-agents-dialog-light.png'; ## Key capabilities @@ -170,11 +172,11 @@ Open the dashboard URL, then point your apps' OTLP exporter to `http://localhost features={[ { icon: 'puzzle', - title: 'MCP server', + title: 'AI coding agents', description: - 'Expose dashboard data to AI agents and coding assistants via the Model Context Protocol. Your AI tools get full observability context.', - href: '/get-started/aspire-mcp-server/', - label: 'Configure MCP', + 'Use the Aspire CLI and MCP server to give agents dashboard data for diagnosing and verifying app changes.', + href: '/dashboard/ai-coding-agents/', + label: 'Use agents', accent: 'purple', }, { diff --git a/src/frontend/tests/typecheck/component-props.contracts.ts b/src/frontend/tests/typecheck/component-props.contracts.ts index ae07cee25..65ae688e3 100644 --- a/src/frontend/tests/typecheck/component-props.contracts.ts +++ b/src/frontend/tests/typecheck/component-props.contracts.ts @@ -325,6 +325,13 @@ const validImageShowcaseProps = { imageAlt: 'Zoomed diagram', cta: { label: 'Read the guide', href: '/docs/' }, } satisfies PropsOf; +const validThemedImageShowcaseProps = { + title: 'Debug with agents', + description: 'Give agents dashboard context.', + lightImage: heroImage, + darkImage: heroImage, + imageAlt: 'Themed dashboard dialog', +} satisfies PropsOf; // @ts-expect-error ImageShowcase should reject unknown props. const invalidImageShowcaseProps: PropsOf = { title: 'Visualize your app', @@ -333,6 +340,15 @@ const invalidImageShowcaseProps: PropsOf = { imageAlt: 'Zoomed diagram', unexpected: true, }; +// @ts-expect-error ImageShowcase should not mix single-image and theme-image props. +const invalidMixedImageShowcaseProps: PropsOf = { + title: 'Visualize your app', + description: 'See resources, traces and endpoints together.', + image: heroImage, + lightImage: heroImage, + darkImage: heroImage, + imageAlt: 'Zoomed diagram', +}; const validIncludeProps = { relativePath: 'content/docs/get-started/install-cli.mdx', @@ -742,7 +758,9 @@ void [ validIconLinkCardProps, invalidIconLinkCardProps, validImageShowcaseProps, + validThemedImageShowcaseProps, invalidImageShowcaseProps, + invalidMixedImageShowcaseProps, validIncludeProps, invalidIncludeProps, validInstallCliModalProps, diff --git a/src/frontend/tests/unit/custom-components.vitest.test.ts b/src/frontend/tests/unit/custom-components.vitest.test.ts index 7252fb480..95f21249e 100644 --- a/src/frontend/tests/unit/custom-components.vitest.test.ts +++ b/src/frontend/tests/unit/custom-components.vitest.test.ts @@ -387,6 +387,24 @@ const basicRenderCases: BasicRenderCase[] = [ }, includes: ['Visualize your app', 'Zoomed diagram', 'Read the guide'], }, + { + name: 'ImageShowcase renders theme-aware images', + Component: ImageShowcase, + props: { + title: 'Debug with agents', + description: 'Give agents dashboard context.', + lightImage: heroImage, + darkImage: heroImage, + imageAlt: 'Themed dashboard dialog', + }, + includes: [ + 'Debug with agents', + 'theme-image', + 'data-light=', + 'data-dark=', + 'Themed dashboard dialog', + ], + }, { name: 'LoopingVideo renders sources and toggle button state', Component: LoopingVideo, @@ -968,7 +986,9 @@ describe('custom Astro component render coverage', () => { // time in the README body and the count rises to 2. const escaped = distinctiveSummarySentence.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const summaryMatches = html.match(new RegExp(escaped, 'g')) ?? []; - expect(summaryMatches.length, 'summary sentence should appear exactly once (hero only)').toBe(1); + expect(summaryMatches.length, 'summary sentence should appear exactly once (hero only)').toBe( + 1 + ); // 2. The long emphasized label is gone from the body. Its image still // surfaces in the gallery so the screenshot itself is preserved.