feat: Add Aspire sample detail pages#1089
Open
IEvangelist wants to merge 18 commits into
Open
Conversation
493d571 to
d5194c5
Compare
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>
165e329 to
dd1afe7
Compare
Contributor
There was a problem hiding this comment.
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.astrorender samples with hero, AppHost code block, Steps, GitHub alerts → Asides, Mermaid, and a zoomable screenshot gallery. - New routes
/reference/samples/[sample]/index.astroand/reference/samples/[sample].md.ts, with middleware updated to redirect trailing slashes for sample.mdURLs. - 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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/frontend/src/data/samples.jsonfrommicrosoft/aspire-samples@main, bringing the catalog from 15 to 26 samples./reference/samples/{sample}/detail pages with README content, tags, thumbnails, and GitHub CTAs.og:image, and WCAG AA-compliant CTAs.Validation
pnpm exec vitest run --config vitest.config.ts tests/unit/custom-components.vitest.test.ts -t "Sample" --reporter=dotpnpm lint