9.9 KiB
CLAUDE.md - DeepDrftPublic
Guidance for working in the DeepDrftPublic project (the Blazor Web App host).
See the root CLAUDE.md for full architecture overview. This file covers what is specific to this project.
One-line purpose
The Blazor Web App host. Owns a browser-facing proxy controller for api/track/* (metadata and audio streaming), crawl-directive endpoints (/robots.txt + /sitemap.xml), MudBlazor theme prerender, and TypeScript→JS audio interop.
What lives here now (only)
Program.cs,Startup.cs: HTTP host config, DI wiring, port binding.Controllers/TrackProxyController.cs: Thin proxy controller at[Route("api/track")]. Two actions:GET api/track/page(proxies paged track metadata) andGET api/track/{trackId}(proxies audio streaming without buffering, forwardsoffsetquery param for seek-beyond-buffer). UsesRegisterForDisposefor clean connection cleanup.Controllers/CrawlDirectiveController.cs: Second controller; servesGET /robots.txtandGET /sitemap.xml. ReadsIWebHostEnvironment.IsProduction()directly (server-side only — no PersistentState bridge). Production robots.txt:Allow: /+Disallow: /FramePlayer+Disallow: /api/+Sitemap:pointer. Non-production robots.txt:Disallow: /. Production sitemap.xml: walksGET api/releasevia the"DeepDrft.API"named client, emits six static roots + one<url>per release (loc =SeoOptions.BaseUrl+ReleaseRoutes.DetailHref, lastmod fromReleaseDate); resilient (partial read → well-formed roots-only doc, never 500). Non-production: sitemap returns 404. Routes automatically viaMapControllers().Seo/RobotsTxt.cs: Pure builder for the robots.txt body (no HTTP, no DI — composition only).Seo/SitemapXml.cs: Pure builder for the sitemap XML body (no HTTP, no DI — composition only).Services/DarkModeService.cs: Server-side dark-mode prerender (readsdarkModecookie, seedsDarkModeSettings.IsDarkModeviaIHttpContextAccessor, carries to WASM viaPersistentComponentState).Components/App.razor: Root component with@rendermode="InteractiveAuto". CallsDarkModeService.InitializeAsync()inOnInitialized.Components/Pages/Error.razor: Error fallback.Interop/audio/: TypeScript sources (one module per responsibility:AudioContextManager.ts,StreamDecoder.ts,PlaybackScheduler.ts,SpectrumAnalyzer.ts,AudioPlayer.ts,index.ts). Compiled towwwroot/js/audio/viaMicrosoft.TypeScript.MSBuild.tsconfig.jsonmust not be copied to output. In dev, raw.tsserved from/Interop/for source-map debugging.wwwroot/: Static assets (compiled JS, CSS, fonts, images, favicons).
What does NOT live here anymore
TrackDirectDataService— deleted; no in-process data adapter.DeepDrftContext,TrackRepository,TrackService,Configurations/,Migrations/— all inDeepDrftData(consumed only by DeepDrftAPI).- Any FileDatabase code — that lives in
DeepDrftContent. - EF Core registration, SQL connection string — DeepDrftPublic has no data layer.
Blazor Web App render modes
Hybrid Blazor with AddInteractiveServerComponents() + AddInteractiveWebAssemblyComponents().
- Root component is
<Routes @rendermode="InteractiveAuto" />fromComponents/App.razor. - WASM render-mode loads
DeepDrftPublic.Client._Importsas an additional assembly. - New routable pages go in
DeepDrftPublic.Client/Pages, not here — the client project owns the interactive UI.
Server-side prerender happens before WASM kicks in. Dark mode, CORS, forwarded headers, and MudBlazor setup must all tolerate this split.
Dark-mode prerender bridge
DarkModeService in this project reads the darkMode cookie via IHttpContextAccessor in App.razor's OnInitialized and seeds DarkModeSettings.IsDarkMode. This setting is registered in DeepDrftPublic.Client.Startup.ConfigureDomainServices. The setting carries over to WASM via PersistentComponentState in MainLayout.razor.
The flow ensures the first paint uses the correct theme (no flash).
TypeScript interop pipeline
Audio interop is TypeScript, not raw JS:
- Sources live in
Interop/audio/with one module per responsibility. - Compiled to
wwwroot/js/viaMicrosoft.TypeScript.MSBuild. index.tsexposes all modules ontowindow.DeepDrftAudiofor Blazor to invoke.tsconfig.jsonconfigured for ES module interop and must not be copied to output.- In development, raw
.tsis served from/Interop/for source-map debugging.
Blazor calls TypeScript via AudioInteropService.ts (a JS interop wrapper in DeepDrftPublic.Client), which manages DotNetObjectReference lifetimes for progress, end-of-playback, and spectrum callbacks.
HTTP client wiring
Dual-mode wiring in DeepDrftPublic.Client.Startup:
- Named clients
"DeepDrft.API"(SQL metadata) and"DeepDrft.Content"(binary audio). - Server-side (SSR prerender): Both clients point directly at DeepDrftAPI via base addresses from
appsettings.json(ApiUrls:ContentApi,ApiUrls:SqlApi). These are loaded fromenvironment/api.json(viaCredentialTools.ResolvePathOrThrow) inProgram.cs. - WASM runtime: Both clients point to
HostEnvironment.BaseAddress(the Blazor host), so browser requests proxy throughTrackProxyControllerto DeepDrftAPI. Startup.ConfigureApiHttpClientandStartup.ConfigureContentServicesare static methods called from both the serverProgram.csand the WASMProgram.cs. At runtime, WASM overrides the base address toHostEnvironment.BaseAddress.
Server-side Program.cs adds:
- MudBlazor (
AddMudServices) - Controllers (
AddControllers()andMapControllers()) - Render-mode components
- SignalR tuning (if needed)
- Forwarded headers
- Calls to
Startup.ConfigureApiHttpClient/ConfigureContentServices/ConfigureDomainServices
Request pipeline and middleware
The middleware pipeline in Program.cs is ordered as follows:
UseForwardedHeaders()— readsX-Forwarded-*headers from nginx. HTTPS redirect is conditionally disabled viaForwardedHeaders:DisableHttpsRedirectionso the app can sit behind a reverse proxy without forcing HTTPS at the host level.- Exception handler and HTTPS redirect (production only).
UseStatusCodePagesWithReExecute("/404", createScopeForStatusCodePages: true)— must sit beforeUseAntiforgery()so that re-executed requests (e.g. the/404route) pass through antiforgery middleware before reaching the endpoint. Placing it afterUseAntiforgery()causes anInvalidOperationExceptionon re-execution.UseAntiforgery()— required by Blazor form handling.UseStaticFiles()— serves compiled static assets fromwwwroot/(including/js/audio/*.jscompiled from TypeScript) with correct MIME types (application/javascriptfor.js). This must run beforeMapStaticAssets()to ensure production audio interop loads correctly.- Cache-control middleware (dev only) — disables caching for
/_frameworkand/_contentassets. MapStaticAssets()— endpoint mapper for Blazor framework assets and remaining static content.- Development-only
UseStaticFiles()— serves raw TypeScript from/Interop/for source-map debugging. MapControllers()andMapRazorComponents()— route controller and component requests.
Controllers
Controllers/ now holds two controllers. Both are thin boundaries — no domain logic, no data layer.
TrackProxyController is the audio/metadata proxy. The WASM client points both named HttpClients ("DeepDrft.API" and "DeepDrft.Content") at the Blazor host's base address, so all browser requests route through this controller to DeepDrftAPI. Server-side SSR calls DeepDrftAPI directly (server-to-server) via the same named clients — no proxy hop on the server side.
The proxy forwards public, unauthenticated routes:
GET api/track/page— paged metadata listingGET api/track/{trackId}— WAV audio streaming (handlesRangeheader for seek-beyond-buffer)GET api/track/albums— distinct albums with countsGET api/track/genres— distinct genres with countsGET api/track/random— random track selectionGET api/track/meta/by-key/{entryKey}— metadata lookup by vault entry keyPOST api/event/play— anonymous play-event telemetry (Phase 16;EventProxyController,[IgnoreAntiforgeryToken])POST api/event/share— anonymous share-event telemetry (Phase 16;EventProxyController,[IgnoreAntiforgeryToken])
All actions use HttpCompletionOption.ResponseHeadersRead for streaming efficiency. Audio streaming registers the upstream response with HttpContext.Response.RegisterForDispose() so the stream is properly cleaned up after the response body is sent.
Development commands
# Run the web host (includes WASM from DeepDrftPublic.Client)
dotnet run --project DeepDrftPublic
# Watch during development
dotnet watch run --project DeepDrftPublic
# Build
dotnet build DeepDrftPublic
Configuration
appsettings.json: Dev defaults forApiUrls:*(DeepDrftAPI base addresses),Logging:*,AllowedHosts,ForwardedHeaders. Port binding viaKestrel:EndpointsorASPNETCORE_URLS.environment/api.json: Secrets file (viaCredentialTools.ResolvePathOrThrow) with production DeepDrftAPI URLs (ApiUrls:ContentApi,ApiUrls:SqlApi).- MudBlazor theme (
MainLayout.razorin client): bespoke light ("Charleston in the Day") and dark ("Lowcountry Summer Nights") palettes. - No
wwwroot/changes during normal development — TS → JS compilation is automatic.
Important patterns
This project is a Blazor host with a proxy layer. The proxy controller is a thin HTTP boundary — no domain logic, no data layer. All domain operations happen in DeepDrftAPI; this controller only forwards public, unauthenticated track routes. When working here, focus on the render surface (components, middleware, config), prerender coordination, and keeping the proxy transparent. New domain logic belongs in DeepDrftData / DeepDrftAPI.