Skip to content

Create an API Reference component - #1588

Draft
Alistair Matthews (alistairmatthews) wants to merge 6 commits into
microsoft:mainfrom
alistairmatthews:create-api-reference-component
Draft

Create an API Reference component#1588
Alistair Matthews (alistairmatthews) wants to merge 6 commits into
microsoft:mainfrom
alistairmatthews:create-api-reference-component

Conversation

@alistairmatthews

Copy link
Copy Markdown
Contributor

Summary

The PR adds a new Astro component named to render the correct API names, depending on the user's preference for C#/TypeScript. The component:

  • Accepts the FQN of an API.
  • Checks the browser's current aspire-language setting.
  • Renders a tag with the correct name.
  • Render a link to the API reference documentation.

This PR also includes unit tests for the new component and applies the new component, initially to all pages in the get-started folder.

Third-party links and affiliations

None

Validation

  • Ran the full test suite, which includes unit tests for the new component.
  • Ran pnpm run dev and manually observed API names in get-started pages.

@IEvangelist

Copy link
Copy Markdown
Member

Feel free to continue to work on this, but do not merge until I review please.

@IEvangelist David Pine (IEvangelist) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review summary

Source verification: microsoft/aspire main at 4a8b541c973baeb271f244603b0289c3f47933cf; generated API inputs in this PR point to microsoft/aspire@69db530a4816698cf1d5fa4557933e0ac4f127c6.

Claims: 29 normalized claims - 24 verified, 1 verified with nuance, 0 unverifiable, 4 contradicted.

Doc test: 8 changed routes exercised from PR head e24dac84f3c54cadeb88c0224b381da578d32e2f - 3 critical issues and 1 warning.

This needs another pass before merge. The component currently rewrites a string rather than resolving an API identity, emits no links, breaks inline HTML in real pages, and falls out of sync with supported aspire-lang interactions.

Blocking acceptance criteria

  1. Accept a canonical fully qualified API identifier and resolve its C# and TypeScript labels plus canonical reference routes from the generated API metadata, including explicit AspireExport identities and renames.
  2. Render valid inline link markup. For a missing, ambiguous, or one-language-only symbol, render unlinked code with a precise warning rather than inventing an API.
  3. Add a separate authoring validator that fails tests/CI for unresolved or ambiguous usages and reports the file, line, supplied FQN, and useful candidates.
  4. Consume the existing canonical AppHost language state instead of maintaining a second attribute/listener implementation.
  5. Cover resolver edge cases, parsed browser markup, pointer and keyboard tabs, PivotSelector, persisted/query initialization, link destinations, and validator diagnostics.

Editor autocomplete or a generated FQN union would be valuable, but it can remain a non-blocking follow-up once deterministic validation exists.

Phase A - Claim verification

Full claim catalog and source evidence # Phase A - Claims verification

PR: microsoft/aspire.dev #1588
PR head: e24dac84f3c54cadeb88c0224b381da578d32e2f
Source of truth: microsoft/aspire, main, 4a8b541c973baeb271f244603b0289c3f47933cf
Generated reference input represented by the PR: microsoft/aspire@69db530a4816698cf1d5fa4557933e0ac4f127c6

Counts

Verdict Count
Verified 24
Verified with nuance 1
Unverifiable 0
Contradicted 4
Total 29

Catalog

ID Location Normalized claim Verdict Evidence
COMP-01 ApiReference.astro:47-54, PR body The component renders an API-reference link. Contradicted The template contains no a, href, or route resolution.
COMP-02 ApiReference.astro:15-44, PR body The prop is a fully qualified API identity that can be looked up. Contradicted The prop is an arbitrary string; the examples are simple names, and apiNames performs only a last-segment casing rewrite with no metadata lookup or validation.
COMP-03 ApiReference.astro:25-44 The last dotted segment pivots between PascalCase and camelCase. Verified lastIndexOf('.') preserves the prefix and rewrites only the first leaf character.
COMP-04 ApiReference.astro:84-88 C# is the fallback language when no component storage value exists. Verified apply(readStored()) sets data-aspire-lang to csharp when no value is found.
COMP-05 ApiReference.astro:92-101 Pointer-clicking a Starlight aspire-lang tab updates the component language. Verified The delegated click handler reads the Starlight storage key after the click.
COMP-06 ApiReference.astro:103-106 A language change in another window updates the component. Verified The storage listener handles the Starlight storage key.
COMP-07 ApiReference.astro:56-107 The component shares a guard and storage key with ContainerImages. Verified Both components use window.__aspireLangPivot and starlight-synced-tabs__aspire-lang.
API-01 add-aspire-existing-app.mdx:68, glossary.mdx, troubleshooting.mdx WithReference exists in C# and TypeScript. Verified Aspire.Hosting.ResourceBuilderExtensions.WithReference; TS capability Aspire.Hosting/withReference.
API-02 add-aspire-existing-app.mdx:68, glossary.mdx, troubleshooting.mdx WaitFor exists in C# and TypeScript. Verified Aspire.Hosting.ResourceBuilderExtensions.WaitFor; TS capability Aspire.Hosting/waitFor.
API-03 glossary.mdx WaitForCompletion exists in C# and TypeScript. Verified C# WaitForCompletion; TS capability Aspire.Hosting/waitForResourceCompletion with display name waitForCompletion.
API-04 glossary.mdx WaitForStart exists in C# and TypeScript. Verified C# WaitForStart; TS capability Aspire.Hosting/waitForStart.
API-05 app-host.mdx WithPersistentLifetime exists in C# and TypeScript. Verified C# API surface and TS capability Aspire.Hosting/withPersistentLifetime.
API-06 add-aspire-existing-app.mdx AddExecutable exists in C# and TypeScript. Verified C# API surface and TS capability Aspire.Hosting/addExecutable.
API-07 aspire-mcp-server.mdx ExcludeFromMcp exists in C# and TypeScript. Verified C# API surface and TS capability Aspire.Hosting/excludeFromMcp.
API-08 resource-mcp-servers.mdx The intended core WithMcpServer exists in C# and TypeScript. Verified Core C# method and TS capability Aspire.Hosting/withMcpServer. A same-named Community Toolkit C# method exports as withInspectedMcpServer, demonstrating why FQN resolution is required.
API-09 resource-mcp-servers.mdx The intended PostgreSQL WithPostgresMcp exists in C# and TypeScript. Verified Aspire.Hosting.PostgresBuilderExtensions.WithPostgresMcp; TS capability Aspire.Hosting.PostgreSQL/withPostgresMcp. An Azure PostgreSQL API shares the simple name.
API-10 app-host.mdx, faq.mdx AddUvicornApp exists in C# and TypeScript. Verified PythonAppResourceBuilderExtensions.AddUvicornApp; TS capability Aspire.Hosting.Python/addUvicornApp.
API-11 app-host.mdx WithUv exists in C# and TypeScript. Verified C# [AspireExport]; TS capability Aspire.Hosting.Python/withUv.
API-12 app-host.mdx, faq.mdx AddNodeApp exists in C# and TypeScript. Verified C# JavaScript hosting API; TS capability Aspire.Hosting.JavaScript/addNodeApp.
API-13 app-host.mdx WithNpm exists in C# and TypeScript. Verified C# JavaScript hosting API; TS capability Aspire.Hosting.JavaScript/withNpm. Other packages also expose same-named functions, so package identity matters.
API-14 deploy-first-app.mdx, faq.mdx AddViteApp exists in C# and TypeScript. Verified C# JavaScript hosting API; TS capability Aspire.Hosting.JavaScript/addViteApp.
API-15 deploy-first-app.mdx, faq.mdx AddJavaScriptApp exists in C# and TypeScript. Verified C# JavaScript hosting API; TS capability Aspire.Hosting.JavaScript/addJavaScriptApp.
API-16 faq.mdx:102 AddPythonApp exists as a current API. Verified with nuance Both language APIs exist, but the C# overloads are [Obsolete] in PythonAppResourceBuilderExtensions.cs:179,221, recommending AddPythonScript, AddPythonModule, or AddPythonExecutable.
API-17 faq.mdx:102 AddPythonModule exists in C# and TypeScript. Verified C# method and TS capability Aspire.Hosting.Python/addPythonModule.
API-18 glossary.mdx WithEnvironment exists in C# and TypeScript. Verified Core C# overloads and TS capability Aspire.Hosting/withEnvironment.
API-19 glossary.mdx AddPostgres is provided by Aspire.Hosting.PostgreSQL in both languages. Verified Aspire.Hosting.PostgresBuilderExtensions.AddPostgres; TS capability Aspire.Hosting.PostgreSQL/addPostgres.
API-20 glossary.mdx:403 Core IResourceBuilder<T>.WithAnnotation has a TypeScript counterpart named withAnnotation. Contradicted No Aspire.Hosting/withAnnotation exists. The only generated TS withAnnotation is Aspire.Hosting.Kubernetes/withAnnotation, an incompatible KubernetesManifestResource method taking key and value.
API-21 resource-mcp-servers.mdx:131 Calling WithMcpServer() uses the root path. Contradicted McpServerResourceBuilderExtensions.WithMcpServer defaults path to /mcp.
API-22 resource-mcp-servers.mdx WithMcpServer and WithPostgresMcp use diagnostics ASPIREMCP001 and ASPIREPOSTGRES001. Verified Attributes in McpServerResourceBuilderExtensions.cs and PostgresBuilderExtensions.cs.

Blocking source findings

  1. The component does not implement the promised lookup or link.
  2. Simple names cannot identify package/type/member identity and cannot honor explicit AspireExport renames.
  3. The component invents a core TypeScript withAnnotation API that does not exist.
  4. The changed MCP page states the wrong default path.
  5. The changed FAQ foregrounds an obsolete C# API without noting its replacements.

Phase B - Doc-tester results

Documentation Test Report

Focus Area: PR #1588 ApiReference behavior on the eight changed get-started routes
Date: 2026-09-01
Tester: doc-tester agent
Served ref: e24dac84f3c54cadeb88c0224b381da578d32e2f
Local URL: http://localhost:4321/

Summary

Category Passed Failed Warnings
Changed routes load 8 0 0
API-reference rendering 23 20 0
API-reference links 0 43 0
Language synchronization paths 2 2 0
Code examples / CLI commands N/A N/A 0

Critical Issues

Issue 1: API references do not link to API documentation

Location: All eight changed routes
Type: Link
Severity: Critical

What the pull request promises:

Render a link to the API reference documentation.

What actually happens:

Browser inspection found 43 .api-reference instances and zero descendant links. None of the rendered API names can be followed to either the C# or TypeScript API reference.

Evidence:
The changed routes contained 43 component instances in total; querySelectorAll('.api-reference a') returned zero links on every route.

Recommended Action:

  • Resolve each fully qualified API identifier against the generated reference data.
  • Render a canonical C# or TypeScript API-reference link for the active language.

Issue 2: Inline references break paragraph markup and disappear

Location: Seven of the eight changed routes
Type: Component rendering / accessibility
Severity: Critical

What the documentation intends:

API names should read as inline code within prose.

What actually happens:

The component places Starlight's block-level Code output inside <span> elements. The browser repairs that invalid inline structure by moving the generated <div class="expressive-code"> and <figure> outside the component. Twenty of 43 .api-reference elements are empty at their intended inline position, and the same 20 API names become detached code blocks with visible copy controls.

Evidence:
On /get-started/app-host/, the sentence "To also keep the container running ... add" ends before the API name. WithPersistentLifetime() appears as a separate figure with a "Copied WithPersistentLifetime() to clipboard" button, followed by the remainder of the sentence. The same failure occurs 20 times across the changed routes.

Recommended Action:

  • Render valid phrasing content, such as an <a><code>...</code></a> structure, instead of embedding a block code component inside <span>.
  • Add browser coverage that asserts each reference remains inside its containing paragraph or list item and does not expose a copy button.

Issue 3: Keyboard and PivotSelector language changes leave API names stale

Location: /get-started/resource-mcp-servers/ and /get-started/deploy-first-app/
Type: Component behavior / accessibility
Severity: Critical

What the component claims:

The API name pivots with the page's C#/TypeScript language selection.

What actually happens:

A pointer click on a Starlight TypeScript tab updates the API name, but two other supported language-change paths do not. Clicking the page's PivotSelector changes the URL and both local-storage keys to TypeScript while data-aspire-lang and the visible API name remain C#. Keyboard-selecting the TypeScript Starlight tab changes the selected tab, URL, local storage, and canonical data-apphost-lang value, but data-aspire-lang remains C# and the API names remain PascalCase.

Evidence:
After clicking the TypeScript PivotSelector, the URL was ?aspire-lang=typescript and storage contained aspire-lang=typescript plus starlight-synced-tabs__aspire-lang=TypeScript, while both rendered attributes remained C# and AddJavaScriptApp stayed visible. After pressing ArrowRight on the C# Starlight tab, the selected tab and data-apphost-lang became TypeScript while data-aspire-lang and WithMcpServer() stayed C#.

Recommended Action:

  • Consume the site's canonical data-apphost-lang state instead of registering a second global state attribute and partial event listener.
  • Cover pointer, keyboard, PivotSelector, query-string initialization, and persisted initialization.

Warnings

Warning 1: Simple names provide no authoring or reader disambiguation

Location: All changed <ApiReference name="..."> usages
Issue: The rendered text provides no package, declaring type, overload, or destination. A new user cannot determine which WithReference, WithMcpServer, or similarly named API is intended.
Suggestion: Require a fully qualified identifier and validate it against the generated API data, while displaying the short language-appropriate label.

Passed Checks

  • All eight affected routes loaded successfully from the PR head.
  • A full navigation with ?aspire-lang=csharp or ?aspire-lang=typescript initialized non-empty references to the requested language.
  • Clicking a Starlight TypeScript tab with a pointer updated non-empty API references.
  • Reloading after a PivotSelector change restored the TypeScript API labels from persisted state.
  • No component-specific browser warnings were emitted.

Knowledge Gap: API identity

What I needed to know: Which package, declaring type, overload family, and language-specific API page each short name refers to.
Source of my knowledge: The rendered documentation did not provide this information.
User impact: New users cannot use the reference as a path to learn the API or distinguish same-named APIs.
Recommendation:

  • Make the component's link and fully qualified authoring identifier the source of this context.

Recommendations

  1. Priority fixes: Correct the invalid inline markup, render real API links from fully qualified identifiers, and use the canonical language state.
  2. Documentation gaps: Migrate every changed usage to an exact FQN and keep the displayed label concise.
  3. Product issues: Add resolver, component, validator, and browser tests; a static string-inclusion test cannot detect any of the critical failures above.


interface Props {
/**
* Fully qualified Aspire API name, e.g. `AddPostgres`, `addPostgres`,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These examples are not fully qualified API identities, and the prop is still an unvalidated free-form string. Please make this accept a canonical FQN (for example, Aspire.Hosting.PostgresBuilderExtensions.AddPostgres) and resolve both language labels and canonical hrefs from the generated C# and TypeScript metadata. Simple names cannot do that reliably: this data set contains 29 distinct declaring-type/package candidates for WithReference, and AspireExport can explicitly rename the TypeScript capability. For unresolved or ambiguous input, the component can render unlinked code plus a warning, but a separate authoring validator should fail with the source file, line, FQN, and candidate matches.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


<span class="api-reference not-content">
<span class="ar-lang" data-lang="csharp">
<Code code={csharp} lang="csharp" frame="none" showLineNumbers={false} />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Starlight Code emits block markup (div > figure > pre) here, so placing it inside these spans is not valid inline prose. The browser reparents that markup: across the eight changed routes, 20 of 43 .api-reference elements were empty at their intended position, and the same 20 names became detached code blocks with visible copy controls. Please render valid phrasing content such as <a><code>...</code></a> and add a browser assertion that the reference remains inside its containing paragraph/list item.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

}

function apply(lang) {
document.documentElement.dataset.aspireLang = lang || 'csharp';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This creates a second language-state attribute instead of consuming the existing data-apphost-lang state. It only stays current for pointer clicks that match this component selector. In the rendered PR, clicking the TypeScript PivotSelector updated the URL and both storage keys while data-aspire-lang and the API label stayed C#; keyboard-selecting the TypeScript Starlight tab updated data-apphost-lang but left data-aspire-lang and the API label in C#. Please bind to the canonical state and remove this duplicate listener path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

name: 'ApiReference renders both C# and TypeScript casings and the language pivot script',
Component: ApiReference,
props: { name: 'AddPostgres()' },
includes: [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These substring checks all pass while the rendered component has no links, 20 usages disappear from inline prose, detached copy controls are exposed, and keyboard/PivotSelector changes leave labels stale. Please split the lookup into a pure resolver with tests for exact FQNs, overload families, export renames, ambiguity, missing symbols, and missing counterparts; then add parsed-DOM/browser coverage for inline structure, hrefs, and every supported language-change path. The authoring validator also needs focused typo/ambiguity diagnostics tests.

|------|-------------|
| `IResourceAnnotation` | Typed metadata object attached to resources. |
| `WithAnnotation()`/`withAnnotation()` | Fluent method to attach typed annotations. |
| <ApiReference name="WithAnnotation()" /> | Fluent method to attach typed annotations. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The casing heuristic invents a core TypeScript withAnnotation() here. There is no Aspire.Hosting/withAnnotation entry in the generated TypeScript API data. The only exported TypeScript namesake is Aspire.Hosting.Kubernetes/withAnnotation, an incompatible KubernetesManifestResource.withAnnotation(key, value) API. A metadata-backed resolver/validator should reject this usage rather than presenting a different API to TypeScript readers.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


- `WithMcpServer()` — uses the default HTTP endpoint at the root path
- `WithMcpServer("/mcp")` — uses the default HTTP endpoint at `/mcp`
- <ApiReference name="WithMcpServer()" /> — uses the default HTTP endpoint at the root path

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The no-argument default is /mcp, not the root path. McpServerResourceBuilderExtensions.WithMcpServer declares string? path = "/mcp" on microsoft/aspire main. Please correct this bullet (and avoid presenting the explicit "/mcp" call as behavior distinct from the default). Source: https://github.com/microsoft/aspire/blob/4a8b541c973baeb271f244603b0289c3f47933cf/src/Aspire.Hosting/McpServerResourceBuilderExtensions.cs#L43-L46

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

- **C# / .NET** — First-class project references, service defaults, and integrations
- **Python** — `AddPythonApp`, `AddUvicornApp`, `AddPythonModule` with uv/pip/venv package management and automatic Dockerfile generation
- **JavaScript / TypeScript** — `AddJavaScriptApp`, `AddViteApp`, `AddNodeApp` with npm/yarn/pnpm auto-detection
- **Python** — <ApiReference name="AddPythonApp" />, <ApiReference name="AddUvicornApp" />, <ApiReference name="AddPythonModule" /> with uv/pip/venv package management and automatic Dockerfile generation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AddPythonApp exists, but both C# overloads are obsolete and direct users to AddPythonScript, AddPythonModule, or AddPythonExecutable with .WithArgs(...). This FAQ now foregrounds it as a current API without that caveat. Please replace it with the recommended APIs or clearly surface the deprecation. Source: https://github.com/microsoft/aspire/blob/4a8b541c973baeb271f244603b0289c3f47933cf/src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs#L179-L181

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

According to my reading of the referred code, the first overload of AddPythonApp() is not obsolete. Two overloads are obsolete, but AddPythonApp(IDistributedApplicationBuilder, string, string, string) remains.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants