Live status header icon + redesigned videos page#758
Open
IEvangelist wants to merge 20 commits intomainfrom
Open
Live status header icon + redesigned videos page#758IEvangelist wants to merge 20 commits intomainfrom
IEvangelist wants to merge 20 commits intomainfrom
Conversation
- 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>
- 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>
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>
Do not keep the temporary GitHub PR description file in source control. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The cleanup helpers were workflow-only artifacts and should not be source controlled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
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 on lines
+71
to
+76
| private static IResult GetSnapshot(LiveStatusBroadcaster broadcaster) | ||
| { | ||
| var snapshot = broadcaster.Current; | ||
| return Results.Json(snapshot, LiveStatusJsonContext.Default.LiveStatus, | ||
| statusCode: StatusCodes.Status200OK); | ||
| } |
Comment on lines
+96
to
+98
| using var heartbeat = time.CreateTimer(static _ => { }, null, | ||
| TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15)); | ||
|
|
Comment on lines
+72
to
+83
| function init(): void { | ||
| const root = findTabRoot(); | ||
| if (!root || root.dataset.bound === '1') return; | ||
| root.dataset.bound = '1'; | ||
| userSticky = false; | ||
| root.querySelectorAll<HTMLElement>('[role="tab"]').forEach((tab) => { | ||
| tab.addEventListener('click', () => { | ||
| userSticky = true; | ||
| }); | ||
| }); | ||
| subscribe(applySnapshot); | ||
| } |
Comment on lines
+69
to
+78
| var channelId = youtube.ChannelId; | ||
| if (string.IsNullOrEmpty(channelId)) | ||
| { | ||
| channelId = await client.ResolveChannelIdAsync(youtube.ChannelHandle, cancellationToken).ConfigureAwait(false) ?? ""; | ||
| if (string.IsNullOrEmpty(channelId)) | ||
| { | ||
| logger.LogWarning("Could not resolve YouTube channel id for {Handle}.", youtube.ChannelHandle); | ||
| return; | ||
| } | ||
| } |
Comment on lines
+68
to
+79
| // Resolve channel id if needed. | ||
| var channelId = twitch.ChannelId; | ||
| if (string.IsNullOrEmpty(channelId)) | ||
| { | ||
| var user = await client.GetUserByLoginAsync(twitch.ChannelLogin, cancellationToken).ConfigureAwait(false); | ||
| if (user is null) | ||
| { | ||
| logger.LogWarning("Twitch user '{Login}' not found.", twitch.ChannelLogin); | ||
| return; | ||
| } | ||
| channelId = user.Id; | ||
| } |
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.
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
primarySourceand PiP-open state shared across Astro navigations.aspiredotdevchannel live-stream embed and Twitch loads theaspiredotdevchannel player even when no live event is active.GET /api/live,GET /api/live/stream, Twitch EventSub webhook, YouTube WebSub webhook, and a dev-only override endpoint.HttpClients, webhook signature validation, reconciling polls/subscription renewal, and safe idle behavior when provider credentials are missing.X-Aspire-Live-Dev-Command-Keyand validated by StaticHost.wwwroot/.gitignoreguard so local copies of the built frontend output cannot be accidentally added to source control.build:statichostandbuild:statichost:skip-searchthat build Astro directly intosrc/statichost/StaticHost/wwwrootwhile 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 theaspiredevresource, 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-searchIn AppHost run mode, StaticHost gets
Live__EnableDevEndpoint=trueand 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
aspiredevresource:/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.Testscovers 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.tscovers the live-status client public API.src/frontend/tests/e2e/live-status.spec.tscovers 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
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.