Skip to content

feat: Add Aspire sample detail pages#1089

Open
IEvangelist wants to merge 18 commits into
release/13.4from
dapine/update-samples-data
Open

feat: Add Aspire sample detail pages#1089
IEvangelist wants to merge 18 commits into
release/13.4from
dapine/update-samples-data

Conversation

@IEvangelist
Copy link
Copy Markdown
Member

@IEvangelist IEvangelist commented May 27, 2026

Summary

  • Refreshes src/frontend/src/data/samples.json from microsoft/aspire-samples@main, bringing the catalog from 15 to 26 samples.
  • Adds local thumbnail assets for new samples with screenshots.
  • Adds generated /reference/samples/{sample}/ detail pages with README content, tags, thumbnails, and GitHub CTAs.
  • Updates sample cards so selecting the card opens the local detail page while View on GitHub opens the upstream sample directly.
  • Improves the samples grid filter UX with tag counts, a nearby active-filter summary/clear action, clearer result counts, and an accessible empty state/reset button.
  • Renders sample README content with plain-text summaries, sentence-case heading anchors, Mermaid diagrams, Starlight-style steps for ordered lists, terminal-styled command output, zoomable Astro images, thumbnail-backed og:image, and WCAG AA-compliant CTAs.
  • Adds a subtle reduced-motion-safe hover animation for cards with screenshots.

Validation

  • pnpm exec vitest run --config vitest.config.ts tests/unit/custom-components.vitest.test.ts -t "Sample" --reporter=dot
  • pnpm lint

@IEvangelist IEvangelist changed the title chore: Update Aspire samples data feat: Add Aspire sample detail pages May 27, 2026
@IEvangelist IEvangelist changed the base branch from main to release/13.4 May 27, 2026 14:05
@IEvangelist IEvangelist force-pushed the dapine/update-samples-data branch from 493d571 to d5194c5 Compare May 27, 2026 14:05
IEvangelist and others added 18 commits May 29, 2026 09:45
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop the decorative circle in favor of a small connected-nodes SVG that
echoes Aspire's distributed-app story without copying TopicHero's grid
plus floating-icon language. Highlights:

- Animated dashed edges with a slow flow shader simulate data movement.
- Five nodes positioned in a balanced layout; one pulses per sample,
  picked by hashing the sample slug so every page feels distinct.
- Dual radial glows (primary at upper-right, secondary at lower-left)
  warm the hero without grid scaffolding.
- Kicker becomes an inline pill with a tag-driven Starlight icon
  (Docker, db, Azure, dashboard, C#, TypeScript, or rocket fallback)
  to give each hero a quick visual cue about the sample's stack.
- Existing CTAs, summary, tags, and accessibility hooks unchanged.
- Animations gated behind prefers-reduced-motion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fetch the sample directories' file tree once via the GitHub git-trees API
and classify each sample as TypeScript (apphost.ts), file-based C#
(apphost.cs), or csproj C# (*AppHost.csproj). Promote the detected
language to a sample tag so the TypeScript filter chip lights up in the
samples grid.

Render the AppHost flavor as a pill on the sample card and as a sibling
pill alongside the 'Aspire sample' kicker on the detail hero, with a
dedicated TypeScript color treatment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The starlight-page-actions plugin fetches '<currentPath>.md' for both
Copy Markdown and Open in Markdown, so a dynamic .md route at every
sample slug has to exist. Generate one by mirroring the C#/TypeScript
API reference pattern with a [sample].md.ts companion to the existing
[sample]/index.astro route, prerendered per static path.

Store the original upstream README (readmeRaw) in samples.json so the
markdown payload stays portable when pasted into an LLM or opened in a
browser. Relative image paths are rewritten to absolute
raw.githubusercontent.com URLs so screenshots render anywhere the
markdown is consumed.

Flip 'pageActions: true' in the StarlightPage frontmatter of the detail
page so the Copy / Open / Share buttons show up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Drop the leading paragraph from the rendered README when it matches the hero summary so the descriptive sentence only appears once on each sample detail page.
- Always use a neutral 'rocket' icon for the 'Aspire sample' kicker pill so the language signal lives entirely on the AppHost pill.
- Switch the .csproj AppHost icon to inline angle-brackets (XML-ish) so the C# logo glyph no longer collides with the 'C#' word in the label.
- Apply official brand colors to the AppHost pills: #512BD4 for C# (csproj and file-based) and #3178C6 for TypeScript, on both SampleCard and SampleDetail.
- Drop the redundant 'C#' word from the short label for file-based AppHosts since the seti C# glyph already carries the language signal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an AppHost code section that renders right after the hero on every
sample detail page so visitors immediately see the orchestration entry
point for the sample they are looking at.

- update-samples generator: extend the AppHost detection to return both
  the kind and the actual entry-point file path (prefers AppHost.cs over
  Program.cs adjacent to a *AppHost.csproj, picks apphost.ts for TS, and
  the standalone AppHost.cs for file-based AppHosts).
- Fetch the entry-point file contents during generation and persist
  appHostCode + appHostPath alongside the existing appHost kind. Trim
  trailing whitespace and normalize line endings so the JSON stays
  stable across runs.
- SampleDetail renders a new <section class='sample-apphost'> above the
  README with the section heading 'The <filename> entry point', a
  'View on GitHub' link that points at the blob URL of the source file,
  and the code itself in an expressive-code <Code> block using the
  language inferred from the file extension (typescript / csharp / xml).
- Add appHostCodeLang and appHostCodeTitle helpers so the lang and the
  rendered title can be derived without per-component duplication.
- Add a SampleDetail vitest assertion covering the new section, the
  blob-URL source link, and the language attribute on the code block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Starlight's <Steps> component is meant for top-level numbered procedures. When a README has a numbered sub-list inside an outer step, the recursive <Astro.self ...> call inside SampleReadmeBlocks would wrap the inner ordered list in another <Steps>, producing double indents, large duplicate bullet circles, and a broken visual rhythm.

Thread a
ested prop through the recursive renders. The top-level invocation from SampleDetail (default alse) still wraps ordered lists in <Steps>; recursive renders pass
ested={true}, which renders nested ordered lists as plain <ol> and unordered lists as plain <ul> without the Starlight step chrome. The blockquote branch propagates the current
ested value so quoted procedures keep their containing context.

Extends the SampleDetail vitest fixture with a nested ordered list and asserts the rendered HTML contains exactly one sl-steps class — one for the outer procedure, zero for the inner steps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously, .sample-hero had no max-width while .sample-readme and .sample-apphost were each capped at 56rem. The hero stretched to fill the Starlight content area while the code block and README rendered noticeably narrower, producing a stepped right edge between the hero card and everything below it.

Move the 56rem cap onto the .sample-detail wrapper so all three children inherit the same column. Removes the duplicate max-width from .sample-readme and .sample-apphost since they now derive their width from the shared parent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* SampleCard: AppHost flavor badge now sits in the bottom-left corner
  opposite the View on GitHub link; footer uses margin-left: auto
  instead of a spacer div. The appHostIconName helper moved to
  utils/samples.ts so the card and detail page share one source.
* SampleDetail: View on GitHub CTA now uses the GitHub mark on the
  leading edge instead of the trailing external-site arrow, matching
  the in-page footer link treatment.
* SampleReadmeBlocks: README image groups no longer leave dangling
  paragraphs orphaned underneath the gallery. Trailing text after a
  grouped image set is now folded back into the gallery caption
  block so sections like /reference/samples/aspire-with-javascript/
  render cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The middleware-level trailing-slash redirect for .md routes was
hard-coded to /reference/api/(csharp|typescript)/..., so navigating
to /reference/samples/<slug>.md without a trailing slash always
returned 404 instead of redirecting. Extend the regex to cover
samples as well.

While here, switch markdownResponse() to text/plain; charset=utf-8
so all .md endpoints (csharp, typescript, samples) render inline in
a new tab on Chrome, Edge, and Safari. The browsers treat
text/markdown as a binary download type, which made the "View as
Markdown" page action save the file instead of opening it. The
Copy Markdown action uses fetch().then(r => r.text()) so the
Content-Type change has no effect there.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Search box now has the standard X icon clear button embedded in the
  input (mirrors InpageSearch), hidden via the HTML hidden attribute
  until the user types. The UA-provided type="search" clear is
  suppressed so there is only one affordance.
* Tag chips are quiet by default (transparent background, 1px gray-5
  border, 0.78rem text, inline muted count). Active chips fill with a
  14% mix of the accent color so they are the only loud item in the
  row. Tighter sizing on viewports up to 480px.
* On viewports up to 768px the chip row collapses to ~4.5rem with a
  subtle bottom-fade mask and a chevron "Show all N tags" toggle. If
  a hidden chip is already active on URL hydrate, the row
  auto-expands so users can see what is filtering their view.
* Replaced the duplicate "Filtered by" bar with a single subtle
  "Clear all" underlined text link in the results header. The active
  chips themselves remain the source of truth for active filters.
* Search and tag state now syncs to ?q=<query>&tags=<slug,slug> via
  history.replaceState. Hydrating from the URL silently drops unknown
  tag slugs and ignores whitespace-only queries; updates use
  replaceState so the back/forward stack stays clean.

Test updates:
* custom-components.vitest.test.ts: assert the new data-clear-all
  link, the aria-label="Clear search" X icon, and that the
  readFiltersFromUrl/URLSearchParams/history.replaceState hooks ship
  in the rendered HTML.
* custom-components.spec.ts: new Playwright case that loads
  /reference/samples/?q=redis&tags=databases,not-a-real-tag, asserts
  the input prefill, the active chip state, that unknown slugs are
  dropped from the URL, that toggling another tag appends to ?tags=,
  and that Clear all resets every filter and removes ?q=/?tags=
  from location.search.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The shared `readMarkdown()` helper asserts the content-type from
`markdownResponse()`. We switched that response to
`text/plain; charset=utf-8` (so browsers display raw markdown inline
instead of triggering a download); the test was still asserting
`text/markdown` and failing on CI for the TypeScript function and
member route cases. Update the assertion to match the new behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@IEvangelist IEvangelist force-pushed the dapine/update-samples-data branch from 165e329 to dd1afe7 Compare May 29, 2026 14:46
@IEvangelist IEvangelist marked this pull request as ready for review May 29, 2026 14:50
Copilot AI review requested due to automatic review settings May 29, 2026 14:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds dedicated /reference/samples/{sample}/ detail pages rendered from upstream sample READMEs, refreshes the sample catalog (15 → 26 samples) with AppHost-aware metadata and thumbnails, and revamps the samples grid filter UX (tag counts, embedded clear icons, URL-synced filters, accessible empty state). Also switches the shared markdown API response helper from text/markdown to text/plain.

Changes:

  • New SampleDetail.astro + SampleReadmeBlocks.astro render samples with hero, AppHost code block, Steps, GitHub alerts → Asides, Mermaid, and a zoomable screenshot gallery.
  • New routes /reference/samples/[sample]/index.astro and /reference/samples/[sample].md.ts, with middleware updated to redirect trailing slashes for sample .md URLs.
  • Sample card + grid redesign with AppHost pills, clickable cards, URL-synced search/tag filters, and a collapsible tag wall on mobile.

Reviewed changes

Copilot reviewed 18 out of 27 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/frontend/src/components/SampleDetail.astro New detail component: hero, AppHost code, README rendering, screenshots gallery.
src/frontend/src/components/SampleReadmeBlocks.astro New recursive renderer for README blocks with Steps/Asides/Mermaid/Code support.
src/frontend/src/components/SampleCard.astro Plain-text descriptions, AppHost pill, clickable card → detail page.
src/frontend/src/components/SampleGrid.astro New filter UX: tag counts, clear-all link, URL sync, mobile collapse, embedded clear icon.
src/frontend/src/utils/samples.ts New shared types + helpers (AppHost labels/icons, slug, markdown builder, plain-text helper).
src/frontend/src/utils/sample-tags.ts Adds typescript tag label.
src/frontend/src/utils/api-markdown-shared.ts Switches markdown response Content-Type to text/plain.
src/frontend/src/pages/reference/samples/[sample]/index.astro Generates per-sample detail pages from samples.json.
src/frontend/src/pages/reference/samples/[sample].md.ts Generates per-sample raw markdown endpoint.
src/frontend/src/middleware.ts Extends nested markdown-pattern to cover samples.
src/frontend/scripts/update-samples.ts Adds AppHost detection + raw README capture for all samples.
src/frontend/config/sidebar/reference.topics.ts Adds id: 'reference' to topic config.
src/frontend/package.json + pnpm-lock.yaml Adds astro-expressive-code@0.42.0 dependency.
tests/unit/custom-components.vitest.test.ts Adds SampleDetail render coverage and updates SampleGrid/SampleCard assertions.
tests/unit/api-markdown.vitest.test.ts Updates content-type expectation to text/plain.
tests/typecheck/component-props.contracts.ts Adds SampleDetail prop contract checks.
tests/e2e/custom-components.spec.ts E2E for URL-synced sample filters.
tests/e2e/api-markdown-routes.spec.ts Adds sample .md route + new content-type expectation.
Files not reviewed (1)
  • src/frontend/pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +102 to +113
switch (node.type) {
case 'text':
case 'codespan':
case 'em':
case 'strong':
case 'del':
parts.push((node as marked.Tokens.Text | marked.Tokens.Codespan).text ?? '');
walk((node as any).tokens);
break;
case 'link':
walk((node as marked.Tokens.Link).tokens);
break;
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