Skip to content

Live status header icon + redesigned videos page - #758

Merged
David Pine (IEvangelist) merged 44 commits into
mainfrom
dapine/live-status
Sep 15, 2026
Merged

David Pine (IEvangelist) merged 44 commits into
mainfrom
dapine/live-status

Conversation

@IEvangelist

@IEvangelist David Pine (IEvangelist) commented Apr 28, 2026 •

Copy link
Copy Markdown
Member

Live status header icon + redesigned videos page

Adds a real-time live indicator to the aspire.dev site header, immediately left of cookie preferences, and replaces the legacy /community/videos/ curated list with a focused two-tab live page for YouTube and Twitch.

When a stream is live, state is pushed to connected clients over Server-Sent Events from StaticHost. The header icon strobes everywhere except /community/videos/ and while native Picture-in-Picture is already open. Clicking the live header icon opens a native Document Picture-in-Picture window when supported, and falls back to the videos page otherwise. The PiP window is tracked as site-global state so it survives Astro client-side navigations without recreating the embed or interrupting playback; closing native PiP only closes PiP and does not redirect.

What's included

  • Frontend live icon, SSE client, header wiring, and videos page rewrite.
  • Native Document Picture-in-Picture controller for live embeds, with YouTube/Twitch embed selection from the server-provided primarySource and PiP-open state shared across Astro navigations.
  • YouTube/Twitch tab icons and offline channel embeds: YouTube loads the aspiredotdev channel live-stream embed and Twitch loads the aspiredotdev channel player even when no live event is active.
  • ASP.NET Core live-status backend with GET /api/live, GET /api/live/stream, Twitch EventSub webhook, YouTube WebSub webhook, and a dev-only override endpoint.
  • Twitch and YouTube background services with resilient named HttpClients, webhook signature validation, reconciling polls/subscription renewal, and safe idle behavior when provider credentials are missing.
  • AppHost dashboard URLs, Scalar API reference, and local dashboard commands for offline/online fake states and signed fake webhook invocations.
  • Server-side protection for dev/dashboard commands via a per-run secret AppHost parameter sent as X-Aspire-Live-Dev-Command-Key and validated by StaticHost.
  • wwwroot/.gitignore guard so local copies of the built frontend output cannot be accidentally added to source control.
  • Frontend scripts build:statichost and build:statichost:skip-search that build Astro directly into src/statichost/StaticHost/wwwroot while preserving the Scalar assets and ignore guard.

Local testing

Start the AppHost from the worktree root with aspire start --isolated --apphost .\src\apphost\Aspire.Dev.AppHost\Aspire.Dev.AppHost.csproj, open the Aspire dashboard, select the aspiredev resource, and run the live-status commands from the Actions context menu.

To refresh the local StaticHost static files without search indexing, run from src/frontend:

pnpm build:statichost:skip-search

In AppHost run mode, StaticHost gets Live__EnableDevEndpoint=true and per-run secret parameters for the dev command key and fake webhook signing keys. Without real provider API credentials, Twitch and YouTube workers stay idle, so the feature is effectively off until a dashboard command triggers it.

Useful endpoints on the aspiredev resource: /api/live, /api/live/stream, /api/live/twitch/webhook, /api/live/youtube/webhook, /api/live/_dev/set, and /scalar/v1. The Twitch webhook is POST-only for notifications and the YouTube webhook supports GET verification plus POST notifications; plain GETs now return descriptive text instead of an unhelpful 404.

Tests

  • tests/StaticHost.Tests covers webhook HMAC verification, YouTube Atom parsing, JSON shape, live-state aggregation, coalescing, subscribe/unsubscribe, and timing behavior.
  • src/frontend/tests/unit/live-status.vitest.test.ts covers the live-status client public API.
  • src/frontend/tests/e2e/live-status.spec.ts covers mocked /api/live + SSE behavior, header strobing, videos-page no-strobe behavior, PiP-open strobe suppression, native PiP click interception, PiP state surviving Astro client-side navigation, and idle channel embeds.

Configuration

"Live": {
  "EnableDevEndpoint": false,
  "DevCommandSecret": "",
  "Twitch": { "ClientId": "", "ClientSecret": "", "WebhookSecret": "", "ChannelLogin": "aspiredotdev" },
  "YouTube": { "ApiKey": "", "WebhookSecret": "", "ChannelHandle": "@aspiredotdev" }
}

User-secrets in dev, env vars / Key Vault in prod. Missing provider secrets degrade safely: the workers log and remain idle, while the API/SSE endpoints continue serving the non-live state.

@IEvangelist
David Pine (IEvangelist) marked this pull request as ready for review May 4, 2026 14:24
Copilot AI review requested due to automatic review settings May 4, 2026 14:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new end-to-end “live status” feature to aspire.dev: StaticHost exposes live state + SSE + Twitch/YouTube webhooks, and the frontend consumes it to drive a header live icon, a redesigned /community/videos/ live page, and a site-global Document Picture-in-Picture experience.

Changes:

  • Implemented StaticHost live-status backend (JSON + SSE, Twitch EventSub + YouTube WebSub, background reconciliation/polling, dev-only override endpoint, Scalar OpenAPI in dev).
  • Added frontend live-status client + header integration + PiP controller, and rewrote /community/videos/ into a two-tab live embeds page.
  • Added comprehensive unit/e2e tests plus a build script to emit Astro output directly into StaticHost wwwroot.

Reviewed changes

Copilot reviewed 50 out of 51 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/StaticHost.Tests/StaticHost.Tests.csproj Adds new StaticHost test project and dependencies.
tests/StaticHost.Tests/Live/YouTubeWebSubServiceTests.cs Unit tests for YouTube worker tick behavior.
tests/StaticHost.Tests/Live/YouTubeWebhookHandlerTests.cs Unit tests for YouTube signature + Atom parsing helpers.
tests/StaticHost.Tests/Live/YouTubeClientTests.cs Unit tests for YouTube Data API + PubSubHubbub client.
tests/StaticHost.Tests/Live/TwitchWebhookHandlerTests.cs Unit tests for Twitch signature verification and webhook handler behavior.
tests/StaticHost.Tests/Live/TwitchEventSubServiceTests.cs Unit tests for Twitch reconcile logic (create/delete subs).
tests/StaticHost.Tests/Live/TwitchClientTests.cs Unit tests for Twitch Helix client behavior.
tests/StaticHost.Tests/Live/TwitchAppTokenProviderTests.cs Unit tests for token acquisition + refresh behavior.
tests/StaticHost.Tests/Live/LiveTestHelpers.cs Shared test helpers (fake options monitor, HTTP handler recording, etc.).
tests/StaticHost.Tests/Live/LiveStatusJsonTests.cs Ensures YouTube property name serialization is correct.
tests/StaticHost.Tests/Live/LiveStatusBroadcasterTests.cs Unit tests for broadcaster aggregation + coalescing + subscriptions.
tests/StaticHost.Tests/GlobalUsings.cs Global usings for the new test project.
src/statichost/StaticHost/wwwroot/scalar/aspire-theme.css Adds Aspire-themed Scalar CSS for dev API reference.
src/statichost/StaticHost/wwwroot/.gitignore Prevents committing locally-built Astro output while preserving Scalar assets.
src/statichost/StaticHost/StaticHost.csproj Adds resilience/OpenAPI/Scalar packages and debug content trimming for wwwroot.
src/statichost/StaticHost/Program.cs Wires live-status + OpenAPI/Scalar in dev and adjusts static assets serving behavior.
src/statichost/StaticHost/Live/YouTube/YouTubeWebSubService.cs Implements YouTube background worker (subscribe + polling).
src/statichost/StaticHost/Live/YouTube/YouTubeWebhookHandler.cs Adds pure helper methods for YouTube webhook signature + payload parsing.
src/statichost/StaticHost/Live/YouTube/YouTubeClient.cs Implements YouTube API + PubSubHubbub client.
src/statichost/StaticHost/Live/YouTube/IYouTubeClient.cs Defines YouTube client abstraction + result types.
src/statichost/StaticHost/Live/Twitch/TwitchWebhookHandler.cs Adds pure helper methods for Twitch EventSub signature + state updates.
src/statichost/StaticHost/Live/Twitch/TwitchEventSubService.cs Implements Twitch background worker reconcile loop + state seeding.
src/statichost/StaticHost/Live/Twitch/TwitchClient.cs Implements Twitch Helix client for user/stream/subscription operations.
src/statichost/StaticHost/Live/Twitch/TwitchAppTokenProvider.cs Implements cached Twitch app token provider for Helix requests.
src/statichost/StaticHost/Live/Twitch/ITwitchClient.cs Defines Twitch client abstraction + record types.
src/statichost/StaticHost/Live/README.md Documents architecture, endpoints, configuration, and testing strategy.
src/statichost/StaticHost/Live/LiveStatusServiceCollectionExtensions.cs Adds DI registration for live-status feature + named resilient HttpClients.
src/statichost/StaticHost/Live/LiveStatusOptions.cs Adds strongly-typed configuration options for live-status feature.
src/statichost/StaticHost/Live/LiveStatusBroadcaster.cs Implements live snapshot aggregation + coalesced broadcasts + subscriptions.
src/statichost/StaticHost/Live/LiveStatus.cs Defines live snapshot schema + source-gen JSON context.
src/statichost/StaticHost/Live/LiveEndpoints.cs Maps /api/live JSON, SSE stream, webhooks, and dev endpoint.
src/statichost/StaticHost/GlobalUsings.cs Updates global usings to include live-status namespace.
src/statichost/StaticHost/appsettings.json Adds Live configuration section defaults.
src/frontend/tests/unit/live-status.vitest.test.ts Adds unit tests for the live-status client public API.
src/frontend/tests/e2e/ui-regressions.spec.ts Updates header action ordering assertions and adds navigation regression coverage.
src/frontend/tests/e2e/live-status.spec.ts Adds extensive e2e coverage for SSE behavior, header state, PiP, and videos page behavior.
src/frontend/src/content/docs/community/videos.mdx Replaces curated videos list with “Aspire Live” page embedding YouTube/Twitch.
src/frontend/src/content/docs/community/index.mdx Renames community CTA to “Watch live streams”.
src/frontend/src/components/YouTubeEmbed.astro Supports channel live-stream embed + adjusts iframe labeling.
src/frontend/src/components/TwitchEmbed.astro Adjusts iframe labeling and props typing.
src/frontend/src/components/starlight/Header.astro Adds header live icon + loads live-status client + adds PiP controller.
src/frontend/src/components/starlight/Head.astro Enables Astro ClientRouter for client-side transitions.
src/frontend/src/components/LiveVideosTabs.astro Adds two-tab live embeds component wired to live-status snapshots.
src/frontend/src/components/LivePip.astro Implements site-global Document Picture-in-Picture controller UI + logic.
src/frontend/src/components/live-status.ts Adds singleton SSE client, DOM wiring, dismissal logic, and snapshot pub/sub.
src/frontend/src/assets/icons/live.svg Adds the “live” header icon SVG.
src/frontend/scripts/build-static-host.mjs Adds script to build Astro directly into StaticHost wwwroot while preserving Scalar assets.
src/frontend/package.json Adds build:statichost and build:statichost:skip-search scripts.
src/frontend/config/sidebar/community.topics.ts Renames “Videos” navigation entry to “Live Streams” (with translations).
src/frontend/astro.config.mjs Supports configurable outDir via ASTRO_OUT_DIR.
src/apphost/Aspire.Dev.AppHost/AppHost.cs Adds local dev dashboard commands + per-run secrets for live-status simulation.

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

Comment thread src/statichost/StaticHost/Live/LiveEndpoints.cs Outdated
Comment thread src/statichost/StaticHost/Live/LiveEndpoints.cs Outdated
Comment thread src/frontend/src/components/LiveVideosTabs.astro
Comment thread src/statichost/StaticHost/Live/YouTube/YouTubeWebSubService.cs
Comment thread src/statichost/StaticHost/Live/Twitch/TwitchEventSubService.cs
Comment thread src/apphost/Aspire.Dev.AppHost/AppHost.cs Outdated
Comment thread src/apphost/Aspire.Dev.AppHost/AppHost.cs Outdated
Comment thread src/apphost/Aspire.Dev.AppHost/AppHost.cs Outdated
Comment thread src/statichost/StaticHost/StaticHost.csproj Outdated
David Pine (IEvangelist) added a commit that referenced this pull request May 11, 2026
Two follow-ups from @eerhardt's review on PR #807:

* Move InternalsVisibleTo into StaticHost.csproj using
  Include="$(AssemblyName).Tests" to match the convention established
  in PR #758 and the existing src/tools/*.csproj files. Delete the
  now-unnecessary Properties/AssemblyInfo.cs.

* Mirror MarkdownNegotiationMiddleware's positive ShouldHandle pattern
  in LinkHeaderMiddleware. The two were inverted: one returned
  "should I handle?" while the other returned "should I skip?".
  Both now use the same shape — guard with !ShouldHandle, then check
  MarkdownPathMapper.IsInfrastructurePath separately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
David Pine (IEvangelist) pushed a commit that referenced this pull request May 11, 2026
* PR feedback

* fixup
David Pine (IEvangelist) added a commit that referenced this pull request May 19, 2026
Two follow-ups from @eerhardt's review on PR #807:

* Move InternalsVisibleTo into StaticHost.csproj using
  Include="$(AssemblyName).Tests" to match the convention established
  in PR #758 and the existing src/tools/*.csproj files. Delete the
  now-unnecessary Properties/AssemblyInfo.cs.

* Mirror MarkdownNegotiationMiddleware's positive ShouldHandle pattern
  in LinkHeaderMiddleware. The two were inverted: one returned
  "should I handle?" while the other returned "should I skip?".
  Both now use the same shape — guard with !ShouldHandle, then check
  MarkdownPathMapper.IsInfrastructurePath separately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
David Pine (IEvangelist) added a commit that referenced this pull request May 20, 2026
… Content-Signal, agent-skills, WebMCP) (#807)

* Add agent-readiness improvements (Link headers, markdown negotiation, robots Content-Signal, agent-skills, WebMCP)

Bring aspire.dev up to spec for the checks at https://isitagentready.com:

* RFC 8288 Link headers on HTML responses
  - LinkHeaderMiddleware advertises </llms.txt>; rel="llms",
    </.well-known/agent-skills/index.json>; rel="agent-skills",
    </sitemap-index.xml>; rel="sitemap", and a per-page rel="alternate"
    type="text/markdown" link when a .md companion exists.
  - Header attached via Response.OnStarting on 2xx text/html responses only;
    redirects, JSON, static assets, and well-known JSON are skipped.

* Cloudflare-style "Markdown for Agents" content negotiation
  - MarkdownNegotiationMiddleware handles Accept: text/markdown by streaming
    the .md companion (emitted by starlight-page-actions) directly via
    IFileProvider.SendFileAsync. No path rewrite, so no interaction with
    UseRouting / MapStaticAssets endpoint selection.
  - Cache-Control: private, max-age=0, must-revalidate ensures Front Door
    does NOT cache, avoiding Vary: Accept cache-key explosion.
  - 406 when markdown preferred but no companion AND no HTML acceptable.
  - HEAD parity, Vary: Accept on negotiated responses, infrastructure paths
    (.well-known, _astro, healthz, install., pagefind) bypass negotiation.

* Both new middlewares run BEFORE UseDefaultFiles + UseRouting (UseDefaultFiles
  rewrites /foo/ -> /foo/index.html, breaking companion mapping; MapStaticAssets
  registers endpoints during UseRouting, so post-routing path rewrites do not
  re-trigger endpoint selection).

* robots.txt declares Content-Signal: ai-train=yes, search=yes, ai-input=yes
  inside the User-agent: * group (per draft-romm-aipref-contentsignals).

* /.well-known/agent-skills/index.json (Agent Skills Discovery RFC v0.2.0)
  with a getting-started-with-aspire SKILL.md and a digest field of the form
  sha256:<lowerhex>. compute-skill-digests.mjs recomputes / verifies on every
  build; pnpm lint runs verify-skill-digests in --check mode.
  .gitattributes pins LF for the agent-skills artifacts so digests are
  byte-stable across Windows / Linux checkouts.

* WebMCP integration on the Astro side
  - src/scripts/webmcp.ts feature-detects navigator.modelContext.registerTool
    and registers a single search-aspire-docs tool with a JSON Schema input.
  - Backed by src/scripts/search/* (SearchProvider abstraction with Pagefind
    today and a Typesense stub for the upcoming migration). The WebMCP tool
    surface is engine-agnostic so the Pagefind -> Typesense swap is a one-line
    change in src/scripts/search/index.ts.
  - Hooked into Head.astro via a single import line.

* New host-level tests in tests/StaticHost.Tests/
  - In-process TestServer with a temp wwwroot fixture (no frontend build
    required; PrivateAssets="all" on the frontend.esproj reference prevents
    the dist/ directory from leaking into test compilations).
  - 51 tests covering markdown negotiation (incl. HEAD, 406, fallback, Vary
    behavior, infrastructure-path skip), Link header content + skip rules,
    AcceptHeaderParser q-value handling, and the well-known artifacts.

* New Playwright spec tests/e2e/webmcp.spec.ts asserts that the homepage
  registers exactly one WebMCP tool (search-aspire-docs) when the runtime
  exposes navigator.modelContext, and that the absence of the API is
  non-fatal.

Out of scope (intentionally not advertised, would mislead agents):
* /.well-known/openid-configuration / oauth-authorization-server (no protected APIs).
* /.well-known/oauth-protected-resource (no protected resource).
* /.well-known/mcp/server-card.json (aspire.dev does not host an MCP server).
* /.well-known/api-catalog (RFC 9727 requires real API endpoints; aspire.dev
  exposes documentation, an LLM corpus, a sitemap, and an RSS feed - none of
  which are APIs in the RFC's sense).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Rewrite getting-started-with-aspire skill to match official Aspire skill conventions

The first cut was inaccurate — it told agents to `mkdir my-aspire-app && cd
my-aspire-app && aspire new` and then non-interactively pick a template. In
reality `aspire new` is fully interactive and creates its own project folder,
so the mkdir+cd pattern is wrong and the fabricated template flags would
mislead agents.

Rewrite the skill to align with the conventions used by the official skill
at github.com/microsoft/aspire/tree/main/.agents/skills/aspire while keeping
this one short and focused on getting started:

* Frontmatter description is now a long when-to-use / when-not-to-use
  sentence in the same shape as the official skill.
* Body: install, `aspire new` (interactive, no fabricated flags), `aspire
  start` (called out as the agent-friendly path vs. `aspire run` which
  blocks the terminal), and a concise list of authoritative references.
* Explicit pointer to the official `aspire` skill for the operate-an-existing-
  app workflow, so an agent that has both available picks the right one.
* Updated index.json description to match the new framing.
* compute-skill-digests.mjs refreshed the sha256 to reflect the new bytes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Correct Aspire framing in skill: polyglot, not .NET-only

Aspire is a polyglot stack — the AppHost can be authored in C# or TypeScript
today, with additional languages (Java, Go, Python, Rust, …) on the roadmap.
Calling it "the .NET cloud-native stack" was both inaccurate and misleading
to agents who would then assume C#-only tooling and dismiss the TypeScript
AppHost path.

Re-runs compute-skill-digests.mjs to update the index.json digest to match
the corrected SKILL.md bytes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Tighten markdown-companion mapping and centralize agent-readiness DRY

Concerns flagged by the user:
1. LinkHeaderMiddleware.ShouldSkip duplicated the infrastructure path list
   maintained on MarkdownPathMapper.IsInfrastructurePath. Fixed by routing the
   path-skip check through the helper so both middlewares stay in lock-step.
2. Not every page on aspire.dev produces a `.md` companion (DocFX-rendered
   /reference/api/**, the search route, Lunaria stats, redirects, the 404
   page). The previous mapper accepted any `.md` that happened to exist on
   disk, so a stray markdown file with no real HTML page would have been
   advertised via the `Link: rel="alternate"; type="text/markdown"` header
   and served by the negotiation middleware on `Accept: text/markdown`.
   Fixed by requiring BOTH the `.md` AND the corresponding HTML page to
   exist before declaring a companion. Adds new xUnit cases pinning the
   stray-md scenario for both middlewares.

Additional cleanup along the way:
* AcceptHeaderParser.PrefersMarkdown: removed a dead `htmlQ` assignment and
  hoisted `HighestExplicitQuality` to a private static method so markdown
  and html lookups go through the same helper.
* Extracted shared sample HTML/Markdown bodies and seed helpers used by
  LinkHeaderTests + MarkdownNegotiationTests into SamplePages so the
  on-disk Starlight layout is described in one place.

Tests: 57 passing (was 51), 0 warnings, 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR #807 review comments

* MarkdownNegotiationMiddleware: drop the stale reference to the
  `HasMarkdownCompanion` API (which never existed in this PR's final
  form); point readers at `MarkdownPathMapper.TryGetMarkdownCompanion`
  instead so the comment matches the live code.
* compute-skill-digests.mjs: harden `resolvePublicPath` against `..`
  traversal. Switches from `path.join` to `path.resolve` and asserts the
  result stays under `publicRoot` so a malicious or malformed `url` in
  index.json (e.g. `/.well-known/agent-skills/../../../../etc/passwd`)
  cannot read bytes outside the published public/ tree in dev/CI.
* WellKnownArtifactTests: rename `Agent_skills_files_are_LF_only` to
  `AgentSkills_files_are_LF_only` to match the surrounding
  `AgentSkills_*` PascalCase prefix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address review nits: InternalsVisibleTo in csproj, symmetrical helper

Two follow-ups from @eerhardt's review on PR #807:

* Move InternalsVisibleTo into StaticHost.csproj using
  Include="$(AssemblyName).Tests" to match the convention established
  in PR #758 and the existing src/tools/*.csproj files. Delete the
  now-unnecessary Properties/AssemblyInfo.cs.

* Mirror MarkdownNegotiationMiddleware's positive ShouldHandle pattern
  in LinkHeaderMiddleware. The two were inverted: one returned
  "should I handle?" while the other returned "should I skip?".
  Both now use the same shape — guard with !ShouldHandle, then check
  MarkdownPathMapper.IsInfrastructurePath separately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove Typesense stubs and flatten search abstraction

The original PR shipped a SearchProvider interface plus a Typesense stub
in src/frontend/src/scripts/search/ so the Pagefind -> Typesense swap
would be a one-line change. We're no longer doing that migration, so
the abstraction layer has no remaining justification.

* Delete src/frontend/src/scripts/search/typesense-provider.ts
* Delete src/frontend/src/scripts/search/SearchProvider.ts
* Delete src/frontend/src/scripts/search/index.ts
* Delete src/frontend/src/scripts/search/pagefind-provider.ts
* Inline the Pagefind logic and types into a single
  src/frontend/src/scripts/search.ts that exports
  searchAspireDocs(query, limit).
* webmcp.ts now imports searchAspireDocs directly; drop the stale
  "Pagefind today, Typesense later" comment and the provider-selector
  indirection.

No behavior change for the WebMCP search-aspire-docs tool. Pagefind is
the only backend now, and the unavailable-fallback contract is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
@IEvangelist
David Pine (IEvangelist) marked this pull request as draft July 21, 2026 18:26
@IEvangelist
David Pine (IEvangelist) marked this pull request as ready for review August 18, 2026 02:29
Comment thread src/frontend/src/components/live-status.ts Outdated
Comment thread src/statichost/StaticHost/Live/LiveEndpoints.cs Outdated
Comment thread src/statichost/StaticHost/Live/LiveEndpoints.cs
Comment thread src/statichost/StaticHost/Live/LiveEndpoints.cs Outdated
Comment thread src/statichost/StaticHost/Live/LiveEndpoints.cs Outdated
Comment thread src/statichost/StaticHost/Live/LiveStatusBroadcaster.cs Outdated
Comment thread src/statichost/StaticHost/Live/LiveStatusBroadcaster.cs Outdated
Comment thread src/statichost/StaticHost/Live/LiveStatusBroadcaster.cs Outdated
Comment thread src/frontend/src/components/LiveVideosTabs.astro Outdated
Comment thread src/frontend/src/components/live-status.ts
Comment thread src/frontend/tests/e2e/live-status.spec.ts Outdated
Comment thread src/frontend/scripts/build-static-host.mjs Outdated
Comment thread src/statichost/StaticHost/Live/Twitch/TwitchEventSubService.cs Outdated

@adamint Adam Ratzman (adamint) 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.

I left inline comments for the must-fix local-dev, webhook validation/caching, broadcaster ordering, and frontend state/test issues. These all look fixable in place, and CI is green at this head. I could not run the site or browser paths inside the isolated worker because the dependencies and local server were unavailable there, so this pass relies on CI plus direct source inspection for that proof.

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

Frontend HTML artifact ready

The latest frontend build uploaded the frontend-dist artifact for PR #758. Use the VS Code button below to open this PR with GitHub Artifacts Explorer and browse the built HTML locally.

VS Code: Open PR #758 artifacts

This comment updates automatically when a new frontend build artifact is uploaded.

@IEvangelist
David Pine (IEvangelist) marked this pull request as draft August 18, 2026 20:10
Comment thread src/apphost/Aspire.Dev.AppHost/LiveExtensions.cs Outdated
Keep the public StaticHost on Aspire's normal per-site worker ceiling and proxy live-status traffic to a dedicated single-worker coordinator.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@IEvangelist
David Pine (IEvangelist) marked this pull request as draft September 10, 2026 18:57
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@IEvangelist
David Pine (IEvangelist) marked this pull request as ready for review September 11, 2026 13:52

@eerhardt Eric Erhardt (eerhardt) 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 the usage of key vault and redis

Comment thread src/apphost/Aspire.Dev.AppHost/AppHost.cs Outdated
Comment thread src/statichost/StaticHost/Program.cs Outdated
Comment thread src/statichost/StaticHost/Program.cs Outdated
Comment thread src/statichost/StaticHost/Program.cs Outdated
Comment thread src/statichost/StaticHost/Live/LiveStatusStore.cs Outdated
Comment thread src/statichost/StaticHost/Live/RedisDistributedLock.cs Outdated
Comment thread src/statichost/StaticHost/wwwroot/scalar/aspire-theme.css Outdated
Comment thread src/statichost/StaticHost/Live/LiveStatusBroadcaster.cs Outdated
Use direct Redis optimistic concurrency for shared records, require distributed production services, clarify resource names and startup behavior, and share frontend brand tokens with Scalar.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mitchdenny

Copy link
Copy Markdown
Member

Review findings

Reviewed commit 0e3a7f540bb84987a32bf280f9467ef5bb298318. These issues should be addressed before merging.

High

  1. Client-side navigation removes the unanswered cookie-consent banner.
    Head.astro:43: the new global ClientRouter replaces the dynamically inserted WCP banner during body swaps, while window.__aspireWcpConsentInitialized survives and prevents initialization from restoring it. Visitors in consent-required regions who navigate before answering lose the banner. Preserve a persistent consent host and its required root state across navigation, and add a navigate-before-consenting regression test. This was reproduced with the actual consent initializer in a simulated body swap.

  2. A stale YouTube search can overwrite a newer live observation.
    LiveEndpoints.cs:417-420: webhook confirmation unconditionally publishes its search result. An offline search can start, the polling worker can discover and publish a newly started broadcast, and the older empty response can then overwrite it with (false, null). The worker no longer performs known-video checks and can wait until its next discovery deadline, up to 30 minutes away. The positive polling write has the corresponding stale-result race. Apply results conditionally against the provider state observed before the request, share offline-confirmation logic, and test these request interleavings.

  3. A lost YouTube verification response can leave the subscription inactive for days.
    YouTubeWebSubSubscriptionState.cs:131-144: confirmation clears Pending and schedules renewal before the challenge response reaches the hub. If that response is lost, matching verification retries return 404 because the pending token is gone. The hub cannot activate the subscription, but the application defers another request until renewal, four days later for a five-day lease. Retain recently confirmed topic/token data and accept bounded idempotent retries without repeatedly extending the lease. Cover a retry on another worker after response loss.

  4. Prematurely invalidated Twitch tokens remain cached.
    TwitchAppTokenProvider.cs:26-40: a token rejected before its advertised expiry continues to be returned on every reconciliation. Helix 401 responses never invalidate or reacquire it, so polling and subscription repair keep failing until expiry or process restart. On 401, conditionally invalidate the rejected token, obtain a fresh app token, and retry once. Add an early-invalidation regression test.

Medium

  1. C# and TypeScript API search controllers interfere after navigation.
    TypeScript module page:662: both languages retain astro:page-load controllers targeting the same pkg-* elements and shared globals. After C# package -> TypeScript module -> C# package navigation, the TypeScript renderer replaces C# search links with non-clickable elements because C# results lack its expected h URL field. Guard initialization with language-specific page/root ownership, including the type/member controllers. This was reproduced by executing the actual controller scripts; add a cross-language navigation regression test.

  2. The backend test suites are not executed by CI.
    Aspire.Dev.slnx:9: adding the AppHost test project to the solution does not make the current workflows run it. apphost-build.yml only builds the AppHost; tools-tests.yml only runs the two generator suites. Wire Aspire.Dev.AppHost.Tests and StaticHost.Tests into CI and include their test paths in change detection.

Test coverage

Local runs passed: 33 focused live-infrastructure tests and four AppHost tests after restore/build, plus 46 provider tests using the resulting binaries with --no-build --no-restore. Existing CI checks pass, including desktop/tablet/mobile e2e. The scenarios above lack regression coverage. Mapped live HTTP endpoints and production Redis coordination/synchronizer behavior are not covered by the current tests; coordination tests exercise the in-memory fake. Axe audits omit the videos page and open live picker.

The frontend reproductions were dependency-free simulations, not browser runs. Frontend dependencies were absent, so local Vitest, browser tests, and typechecking were not run. No local frontend build was run.

Preserve consent and API search ownership across navigation, fence YouTube observations, make WebSub retries idempotent, and refresh rejected Twitch tokens. Add mapped HTTP, Redis, and accessibility coverage and run backend suites in CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@IEvangelist

Copy link
Copy Markdown
Member Author

Mitch Denny (@mitchdenny) Addressed the agreed feedback in cc7b010.

  • Preserve the original WCP consent host, runtime, vendor styles, root consent state, and banner offset across client navigation, with navigate-before-consenting coverage.
  • Fence YouTube results atomically using provider-specific epoch/revision metadata in Redis. Polling and webhook checks now share offline evidence; stale positive/negative and overlapping same-state results cannot overwrite newer observations.
  • Persist recently confirmed WebSub tokens for idempotent cross-worker retries, bounded by the original lease or ten minutes. Retries never extend renewal, and replacement subscriptions invalidate old tokens.
  • Conditionally invalidate rejected Twitch tokens and retry every Helix operation once with a fresh token, without discarding concurrent replacements.
  • Give all six API search surfaces language/page-owned roots, local index data, and abortable controller/timer lifecycles.
  • Run both backend suites and mandatory real-Redis integration coverage in the existing AppHost CI job, with the test directories and solution included in change detection.

Also added mapped HTTP endpoint tests and live videos/open-picker axe and keyboard coverage. Redis tests use an explicit disposable instance and exact-key cleanup, never a shared/default database or database-wide flush.

Local results: 236 StaticHost tests (including 18 against Redis 8.6), 4 AppHost tests, and 107 focused frontend unit tests passed. Consent/live/accessibility coverage passed 62 browser cases across desktop/tablet/mobile, with 7 existing device-specific skips. All 12 API navigation cases passed using native transitions on desktop and the supported ClientRouter swap fallback on touch projects. Native touch emulation surfaced Chromium InvalidStateError transition-capture aborts; those were not hidden by filtering page errors, and the explicit fallback cases still assert preserved JavaScript context and clickable correct-language results.

The repository-wide TypeScript check has existing failures: baseline 187 diagnostics, current 185, with zero newly introduced diagnostics. No local frontend production build was run; the updated commit has triggered CI.

Comment thread .github/workflows/apphost-build.yml Outdated
Create the Redis-only application with DistributedApplicationTestingBuilder and the same Azure Managed Redis hosting integration as the AppHost. Remove the separate CI image pin, external connection-string contract, and shared-instance ownership guards.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Resolve Twitch parent from the browser hostname before loading, correct the shared Aspire YouTube channel, and switch between the non-autoplaying uploads playlist and exact live video. Cover source transitions, hidden tabs, and client navigation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@IEvangelist
David Pine (IEvangelist) merged commit 6dbf31b into main Sep 15, 2026
17 of 19 checks passed
@IEvangelist
David Pine (IEvangelist) deleted the dapine/live-status branch September 15, 2026 18:09
David Pine (IEvangelist) added a commit that referenced this pull request Sep 16, 2026
* Keep docs versions current and default to TypeScript AppHosts (#1600)

* Keep docs versions current and default to TypeScript

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address TypeScript-first review feedback

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore: Update integration data and GitHub stats (9/2/26) (#1612)

Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore: Update integration data and GitHub stats (9/3/26) (#1615)

Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore: Update integration data and GitHub stats (9/4/26) (#1622)

Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore: Update integration data and GitHub stats (9/7/26) (#1625)

Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore: Update integration data and GitHub stats (9/8/26) (#1629)

Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Merge Duplicate C#/.NET Sidebar Sections (#1632)

* Fix: merge duplicate C#/.NET sidebar sections under Frameworks & runtimes

* Clean:revert unrelated icon safelist regeneration

* fix: match sidebar label to page title for .NET get-started item

* Claude's accessibility fixes. (#1595)

* Claude's accessibility fixes.

* Addressed list margins feedback from @Copilot.

* chore: Update integration data and GitHub stats (9/9/26) (#1640)

* chore: Update integration data and GitHub stats (9/9/26)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: keep integration documentation mappings synchronized

Remove the stale Bun mapping, reconcile catalog-managed mappings during updates, and stage documentation-map changes in automated PRs. Validate structured data without Astro-generated asset side effects.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: David Pine <david.pine@microsoft.com>

* docs: restore Floci integration documentation (#1590)

* docs: restore Floci integration documentation

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs: address Floci review feedback

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* chore: Update integration data and GitHub stats (9/10/26) (#1647)

Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Improve search discoverability and agent-readable content (#1633)

* feat: improve search and agent discoverability

Restore page-specific descriptions, publish useful homepage Markdown, strengthen observability guidance, and defer inactive AppHost examples.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: keep mobile homepage within layout budget

Tighten spacing in the expanded observability section so the production mobile viewport remains within the existing compactness gate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: align agent docs and search indexing

Document authenticated standalone MCP with an explicit API key and keep deferred AppHost examples out of the homepage Pagefind index.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Bump OpenTelemetry.Exporter.OpenTelemetryProtocol and 4 others (#1602)

Bumps OpenTelemetry.Exporter.OpenTelemetryProtocol from 1.17.0 to 1.18.0
Bumps OpenTelemetry.Extensions.Hosting from 1.17.0 to 1.18.0
Bumps OpenTelemetry.Instrumentation.AspNetCore from 1.17.0 to 1.18.0
Bumps OpenTelemetry.Instrumentation.Http from 1.17.0 to 1.18.0
Bumps OpenTelemetry.Instrumentation.Runtime from 1.17.0 to 1.18.0

---
updated-dependencies:
- dependency-name: OpenTelemetry.Exporter.OpenTelemetryProtocol
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-selected
- dependency-name: OpenTelemetry.Extensions.Hosting
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-selected
- dependency-name: OpenTelemetry.Instrumentation.AspNetCore
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-selected
- dependency-name: OpenTelemetry.Instrumentation.Http
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-selected
- dependency-name: OpenTelemetry.Instrumentation.Runtime
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nuget-selected
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump the github-actions group across 1 directory with 2 updates (#1601)

Bumps the github-actions group with 2 updates in the / directory: [github/gh-aw-actions/setup](https://github.com/github/gh-aw-actions) and [github/gh-aw-actions/setup-cli](https://github.com/github/gh-aw-actions).


Updates `github/gh-aw-actions/setup` from 0.87.1 to 0.88.0
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](github/gh-aw-actions@423b3dc...afc709f)

Updates `github/gh-aw-actions/setup-cli` from 0.87.1 to 0.88.0
- [Release notes](https://github.com/github/gh-aw-actions/releases)
- [Changelog](https://github.com/github/gh-aw-actions/blob/main/CHANGELOG.md)
- [Commits](github/gh-aw-actions@423b3dc...afc709f)

---
updated-dependencies:
- dependency-name: github/gh-aw-actions/setup
  dependency-version: 0.87.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/gh-aw-actions/setup-cli
  dependency-version: 0.87.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Hide the right TOC when the mobile TOC is visible (#1655)

* Fix right TOC width at browser zoom levels

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Hide right TOC when mobile TOC is visible

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Docs: capture 13.2 Foundry rename breaking changes and remove legacy AIFoundry references (#1163)

* docs: clarify Foundry migration guidance

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs: update Foundry package versions

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b8ebe88c-337e-4d4a-aa80-e14c2c76289b

---------

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Maddy Montaquila <maddy@MadBook-Pro-20.local>
Copilot-Session: b8ebe88c-337e-4d4a-aa80-e14c2c76289b

* Update deployment docs to pipeline-step APIs and remove legacy callback annotation references (#1158)

* Update deployment docs to pipeline-step API

Refresh custom deployment examples for pipeline steps and clarify legacy callback references in deployment, diagnostics, and release notes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5b0c81a3-d822-462e-8cf5-8eb6debfe968

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Restore historical What's new content

Keep release-version articles unchanged while retaining the current pipeline API updates elsewhere.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address deployment docs review feedback

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b156fe61-0b1c-460c-9735-1eaeaa356b0a

* Align Aspire 9.4 deployment example with pipelines

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b156fe61-0b1c-460c-9735-1eaeaa356b0a

---------

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: David Pine <david.pine@microsoft.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Maddy Montaquila <maddy@MadBook-Pro-20.local>
Copilot-Session: 5b0c81a3-d822-462e-8cf5-8eb6debfe968
Copilot-Session: b156fe61-0b1c-460c-9735-1eaeaa356b0a

* Live status header icon + redesigned videos page (#758)

* Add live-status header icon, SSE backend, redesigned videos page

- Adds a strobing live-status indicator in the site header (left of the
  cookie-preferences button) wired to a new /api/live SSE endpoint.
- Adds a custom floating PiP player that follows visitors across the
  site while live and returns them to the videos page on close.
- Replaces the legacy curated /community/videos/ page with a focused
  YouTube + Twitch tabbed page whose embed lights up when live.
- Adds an in-StaticHost background-worker stack:
  * Twitch EventSub stream.online/offline subscription + reconcile,
  * YouTube WebSub subscribe/renew + confirming poll fallback,
  * a debounced LiveStatusBroadcaster with sticky primary-source
    mesh logic and Channel<T> SSE fan-out.
- Surfaces the new endpoints + Scalar API reference (with a custom
  Aspire-brand theme) on the Aspire dashboard for local dev.
- Includes a worktree cleanup helper script and PR_BODY.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add tests for live-status feature

- StaticHost.Tests xUnit project: TwitchWebhookHandler (HMAC-SHA256
  round-trip + tamper detection), YouTubeWebhookHandler (HMAC-SHA1 +
  Atom video-id extraction), LiveStatusBroadcaster (sticky-primary
  mesh, coalesce window via FakeTimeProvider, subscribe/unsubscribe).
- vitest spec for live-status.ts public API.
- Playwright spec mocking /api/live + a controllable SSE stream to
  drive the header icon strobe and the floating PiP open/close UX.

22 backend xUnit tests + 3 vitest assertions all green locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Unwrap draft PR body

Remove hard-wrapped prose from the draft PR description so GitHub renders paragraphs and list items without arbitrary line breaks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove draft PR body file

Do not keep the temporary GitHub PR description file in source control.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove worktree cleanup scripts

The cleanup helpers were workflow-only artifacts and should not be source controlled.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add local live-status dev controls

- Enable an explicit AppHost local dev mode for live-status testing.
- Add dashboard HTTP commands on the StaticHost resource to fake Twitch
  and YouTube live events, including signed calls to the real webhook
  endpoints.
- Keep provider workers idle when API credentials are absent so local
  state only changes when commanded.
- Fix the live-status JSON contract to emit youtube, matching the
  frontend client, and add coverage for that shape.
- Document dashboard-command and manual HTTP testing paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Secure live dev commands and native PiP

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Keep live PiP open across navigation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Build frontend directly into StaticHost

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Keep live PiP alive across site navigation

Enable Astro client routing so same-origin site links swap content without unloading the opener document that owns the native Document Picture-in-Picture window. Update the live-status e2e coverage to click a real internal Docs link and assert the PiP iframe remains intact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Let users choose live PiP source

When both Twitch and YouTube are live, show an explicit live-source menu from the header action before opening native Picture-in-Picture. Keep the selected source sticky while it remains live and allow switching an existing PiP window without recreating it.

Also replace noisy iframe title attributes on the live embeds with aria-labels so the videos tabs keep an accessible frame name without hover title noise.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Polish live stream chooser

Add YouTube and Twitch icons to the live-source chooser and remove the redundant aspiredotdev helper text. Rename the Community sidebar entry and related live destination copy from videos to Live Streams.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix live UX navigation regressions

Preserve the cookie consent root across Astro client-side body swaps so preferences continue to open after navigation. Restyle the live source chooser to align with the existing install modal treatment and cover the cookie regression with Playwright.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add live stream action dialog

Always show the live chooser while live so users can pick native PiP, the aspire.dev embeds, or the provider platform directly. Add a session-scoped dismiss action that silences the strobing live notification for the current live event without hiding the live entry point.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix live dialog mobile layout

Keep the live action dialog within narrow viewports and add mobile E2E coverage using mocked live status APIs. Also keep generated StaticHost output out of Debug build item discovery so local AppHost starts remain fast.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Polish live dialog responsive behavior

Make repeated live icon clicks toggle the live dialog, render the mobile dialog as a stable full-width sheet, restore a wider desktop menu with one-line labels, and harden mocked live-status E2E coverage for both layouts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Move mobile live dialog below header

Anchor the mobile live action dialog below the top navigation instead of using a bottom sheet, add a top-right dismiss button matching the Install CLI modal affordance, and update E2E coverage for placement and dismiss behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Polish live actions and notification sessions

Use regular Fluent icon names for local live command actions, reorganize the live action dialog into PiP, embedded-player, and platform groups, add external-link affordances, and introduce a stable liveSessionId so dismissing a live notification covers near-simultaneous provider joins.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix compact header regression expectation

Account for the live status header action in the compact header reachability test and map it by tour target so the assertion remains stable when live-state labels change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Add StaticHost live unit coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address live status PR feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* * PR feedback

* fixup

* Fix API reference search controllers under view transitions

The six API reference page templates initialized their search controllers
inside a `DOMContentLoaded` listener. After this branch adds the
`<ClientRouter />` to `Head.astro` (so the live PiP can persist
across navigation), the site swaps bodies via view transitions and
`DOMContentLoaded` does not refire on subsequent navigations. As a
result, clicking the C#/TypeScript API buttons from the global search
dialog landed on the API page with the URL `?q=` parameter in place but
the controller was never instantiated, so `#ts-api-search-input` (and
its C# counterpart) was never populated and search results were never
rendered.

Switch all six API reference page initializers to `astro:page-load`,
which fires on the initial load and after every view-transition swap.
This matches the convention already used by `Sidebar.astro`,
`PageTitle.astro`, `InstallCliModal.astro`, and the rest of the
post-ClientRouter components in this branch.

Resolves:
  tests/e2e/site-search.spec.ts:200 'typed query is forwarded to the
  C# and TypeScript API buttons' (desktop / tablet / mobile chromium).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review feedback for live-status

Backend (StaticHost):
- Broadcaster: seed+register subscribers atomically under the lock so a flush
  landing mid-subscribe can't strand a client on the stale seed; resolve sticky
  primary against the pending basis so the first source stays primary when a
  second joins within the coalesce window; ignore UpdatedAt when deciding whether
  anything substantive changed so an ever-advancing timestamp can't force a
  redundant broadcast.
- Twitch webhook: reject stale/unparseable Message-Timestamp (10-min window, 1-min
  future skew) to bound replay of genuinely-signed callbacks; skip message-id dedup
  for verification handshakes so retried challenges always echo; inject TimeProvider.
- YouTube webhook: replace the untracked per-notification Task.Run with a bounded,
  coalescing YouTubeLiveConfirmationQueue BackgroundService (capacity-1 DropWrite)
  tied to the host stopping token.
- Reconcile loops: only swallow OperationCanceledException when the stopping token
  actually fired, so genuine cancellations elsewhere still surface.
- Cache-Control: no-store now applied centrally to the whole /api/live group via an
  endpoint filter instead of per-handler.

Frontend:
- Seed fetch no longer clobbers fresher SSE state (apply only when not older).
- Tabs: only trusted (real user) clicks make a tab sticky, so auto-switch keeps
  working; synthetic activateTab() clicks are ignored.
- build-static-host: restore scalar/.gitignore in finally before the temp dir is
  removed, so a failed astro build can't lose the only preserved copies.
- Dev proxy: AppHost injects StaticHost's origin (ASPIRE_STATICHOST_URL) and Vite
  proxies /api/live[/stream] to it under `aspire run`; omitted in CI/prod.
- Playwright: fulfil the SSE route with a complete string body via a shared helper
  instead of a ReadableStream that serialised to garbage.

Tests:
- Broadcaster: coalesce-window primary stickiness and no-op suppression regressions.
- TwitchWebhookHandler.IsFresh: fresh/stale/skew/unparseable.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix botched merge in Header.astro cookie consent

The merge left Header.astro importing the removed `vanilla-cookieconsent`
package, breaking the frontend build. upstream/main replaced that package
with a global `[data-cookie-manage-consent]` handler in Head.astro
(`siteConsent.manageConsent()`), so drop the stale import and script and
keep only the live-status wiring.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Add seoTitle to videos page to satisfy SEO length guard

The redesigned videos page composed an og:title of only 23 characters
("Aspire Live 📺 · Aspire"), below the 30-65 guard range enforced by
tests/unit/seo-lengths.vitest.test.ts. Add a verbatim `seoTitle`
override (55 chars, in the 50-60 optimal range) so the social-card title
stays keyword-rich without lengthening the visible page title.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Remove obsolete cookie-preferences SPA-nav e2e test

This PR-only test leaked into the merge without its `openCookiePreferences`
helper, causing a ReferenceError across all e2e shards. The helper asserted
the `#pm__title` vanilla-cookieconsent modal, but upstream/main removed that
library in favor of a global `[data-cookie-manage-consent]` delegated
listener (Head.astro) that survives client-side navigation by design. The new
mechanism is already covered by cookie-consent.spec.ts, so drop the stale test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Harden live status production readiness

Back production stream settings with Azure Key Vault, make YouTube polling and WebSub quota-safe, and repair local proxy, client-navigation lifecycle, static asset, and autoplay behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Ignore generated Aspire publish output

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Prefer provider handoff for mobile live streams

Keep Document Picture-in-Picture on desktop while touch-first devices use provider apps or the existing iframe page. Improve chooser focus, failure feedback, touch targets, and iframe permissions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Update live command tests for Aspire 13.5

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Use read-only site secrets and reduce live-status overhead

Provision a shared siteconfig vault without secret values, preserve read-only references, reuse SSE frames and timers, and avoid redundant player and navigation work.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Use parameters for live configuration

Keep non-sensitive live-status settings as deployment parameters while retaining provider credentials and webhook signing keys as read-only Key Vault references.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Update ASP.NET Core OpenAPI package

Use Microsoft.AspNetCore.OpenApi 10.0.12 so StaticHost resolves a current, non-vulnerable Microsoft.OpenApi dependency.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Isolate live status scaling

Keep the public StaticHost on Aspire's normal per-site worker ceiling and proxy live-status traffic to a dedicated single-worker coordinator.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Use Redis for distributed live status

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Rename live cache variable

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Load live secrets from Key Vault configuration

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address live status review feedback

Use direct Redis optimistic concurrency for shared records, require distributed production services, clarify resource names and startup behavior, and share frontend brand tokens with Scalar.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Remove Redis key versions

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Address live status review feedback and coverage gaps

Preserve consent and API search ownership across navigation, fence YouTube observations, make WebSub retries idempotent, and refresh rejected Twitch tokens. Add mapped HTTP, Redis, and accessibility coverage and run backend suites in CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Use Aspire to manage Redis integration test infrastructure

Create the Redis-only application with DistributedApplicationTestingBuilder and the same Azure Managed Redis hosting integration as the AppHost. Remove the separate CI image pin, external connection-string contract, and shared-instance ownership guards.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix live embeds on staging and show YouTube uploads offline

Resolve Twitch parent from the browser hostname before loading, correct the shared Aspire YouTube channel, and switch between the non-autoplaying uploads playlist and exact live video. Cover source transitions, hidden tabs, and client navigation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>

* Make Dev Container guides language-neutral (#1667)

* docs: recreate language-neutral dev container guides

Recreates #1627, including the certificate trust clarification and refreshed template screenshots.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* docs: remove unnecessary SDK aside from container guides

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* [auto-sec] aspire.dev: remediate 12 frontend npm security alerts (#1645)

Clears all open Dependabot alerts for src/frontend (1 critical, 6 high, 5 medium):
- astro 7.1.3 -> 7.2.10 (critical GHSA + medium)
- sharp 0.35.3 -> 0.35.4 (high)
- vitest 4.1.10 -> 4.1.11; @vitest/mocker -> 4.1.11 (medium)
- pnpm overrides: js-yaml 4.3.2, nanoid 3.3.18, svgo 4.1.0, fflate 0.7.5, smol-toml 1.7.1

Verified: pnpm install OK; pnpm audit -> no known vulnerabilities; astro sync OK.

Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: restore site search after client-side navigation (#1675)

* fix: restore site search after client-side navigation

Initialize Pagefind per connected search element and dispose stale listeners and UI instances when navigating. Add navigation, history, API-page, and delayed-import regressions.

Fixes #1673

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: preserve Mermaid layout across client navigation

Keep Mermaid centering and responsive SVG sizing in the bundled stylesheet rather than relying on the plugin's one-time injected head style. Cover repeated navigation in light and dark themes on desktop and mobile.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: keep two-slash hover popups paired across navigation

Replace competing plugin and inline hover initializers with one site-owned runtime that pairs each token with its generated popup and initializes each new page idempotently. Preserve rendered hover styles and Expressive Code tools.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* fix: restore filters and history across client navigation

Reinitialize API version, integration, and sample filters for each page and dispose outgoing listeners and debounce timers. Preserve Astro history state when filtering and avoid adding history entries during gallery initialization. Cover repeated navigation and Back on desktop and mobile.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* test: use the touch navigation fallback for site search

Match the existing client-navigation suites by exercising native view transitions on desktop and ClientRouter's supported swap fallback on mobile and tablet. This prevents Chromium touch-emulation transition-abort exceptions from failing the strict search error assertions. Preserve all input, results, focus, history, and page-error checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix YouTube WebSub topic and back off subscription failures (#1674)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Fix registry-specific tarball URLs in frontend lockfile

Remove Microsoft mirror URL overrides while preserving pinned versions and integrity hashes so CI passes pnpm supply-chain checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Restore npm SHA-512 integrity hashes in frontend lockfile

Refresh integrity metadata from the public npm registry while preserving all pinned dependency versions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Remove duplicate Floci integration catalog entry

Keep the Floci entry with 100 downloads and remove the earlier duplicate that caused resource catalog prerendering to fail.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: David Pine <david.pine@microsoft.com>
Co-authored-by: David Pine <7679720+IEvangelist@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: aspire-repo-bot[bot] <268009190+aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: aspire-repo-bot[bot] <aspire-repo-bot[bot]@users.noreply.github.com>
Co-authored-by: David Aniebo <aniebovictor001@gmail.com>
Co-authored-by: Alistair Matthews <alistairwebdojo@live.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Maddy Montaquila <maddy@MadBook-Pro-20.local>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot-Session: b8ebe88c-337e-4d4a-aa80-e14c2c76289b
Copilot-Session: 5b0c81a3-d822-462e-8cf5-8eb6debfe968
Copilot-Session: b156fe61-0b1c-460c-9735-1eaeaa356b0a
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.

5 participants