Files
deepdrft/DeepDrftPublic/CLAUDE.md
T
daniel-c-harvey 1bda2b7bea
Deploy DeepDrftManager / Build & Publish (push) Successful in 1m29s
Deploy DeepDrftPublic / Build & Publish (push) Successful in 4m7s
Deploy DeepDrftManager / Deploy (push) Successful in 1m23s
Deploy DeepDrftPublic / Deploy (push) Successful in 1m28s
docs: reflect Phase 23 SEO crawl directives as landed
2026-06-23 07:40:57 -04:00

131 lines
9.9 KiB
Markdown

# 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) and `GET api/track/{trackId}` (proxies audio streaming without buffering, forwards `offset` query param for seek-beyond-buffer). Uses `RegisterForDispose` for clean connection cleanup.
- `Controllers/CrawlDirectiveController.cs`: Second controller; serves `GET /robots.txt` and `GET /sitemap.xml`. Reads `IWebHostEnvironment.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: walks `GET api/release` via the `"DeepDrft.API"` named client, emits six static roots + one `<url>` per release (loc = `SeoOptions.BaseUrl` + `ReleaseRoutes.DetailHref`, lastmod from `ReleaseDate`); resilient (partial read → well-formed roots-only doc, never 500). Non-production: sitemap returns 404. Routes automatically via `MapControllers()`.
- `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 (reads `darkMode` cookie, seeds `DarkModeSettings.IsDarkMode` via `IHttpContextAccessor`, carries to WASM via `PersistentComponentState`).
- `Components/App.razor`: Root component with `@rendermode="InteractiveAuto"`. Calls `DarkModeService.InitializeAsync()` in `OnInitialized`.
- `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 to `wwwroot/js/audio/` via `Microsoft.TypeScript.MSBuild`. `tsconfig.json` **must not** be copied to output. In dev, raw `.ts` served 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 in `DeepDrftData` (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" />` from `Components/App.razor`.
- WASM render-mode loads `DeepDrftPublic.Client._Imports` as 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/` via `Microsoft.TypeScript.MSBuild`.
- `index.ts` exposes all modules onto `window.DeepDrftAudio` for Blazor to invoke.
- `tsconfig.json` configured for ES module interop and must **not** be copied to output.
- In development, raw `.ts` is 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 from `environment/api.json` (via `CredentialTools.ResolvePathOrThrow`) in `Program.cs`.
- **WASM runtime**: Both clients point to `HostEnvironment.BaseAddress` (the Blazor host), so browser requests proxy through `TrackProxyController` to DeepDrftAPI.
- `Startup.ConfigureApiHttpClient` and `Startup.ConfigureContentServices` are static methods called from both the server `Program.cs` and the WASM `Program.cs`. At runtime, WASM overrides the base address to `HostEnvironment.BaseAddress`.
Server-side `Program.cs` adds:
- MudBlazor (`AddMudServices`)
- Controllers (`AddControllers()` and `MapControllers()`)
- 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:
1. `UseForwardedHeaders()` — reads `X-Forwarded-*` headers from nginx. HTTPS redirect is conditionally disabled via `ForwardedHeaders:DisableHttpsRedirection` so the app can sit behind a reverse proxy without forcing HTTPS at the host level.
2. Exception handler and HTTPS redirect (production only).
3. `UseStatusCodePagesWithReExecute("/404", createScopeForStatusCodePages: true)` — must sit *before* `UseAntiforgery()` so that re-executed requests (e.g. the `/404` route) pass through antiforgery middleware before reaching the endpoint. Placing it after `UseAntiforgery()` causes an `InvalidOperationException` on re-execution.
4. `UseAntiforgery()` — required by Blazor form handling.
5. **`UseStaticFiles()`** — serves compiled static assets from `wwwroot/` (including `/js/audio/*.js` compiled from TypeScript) with correct MIME types (`application/javascript` for `.js`). This must run *before* `MapStaticAssets()` to ensure production audio interop loads correctly.
6. Cache-control middleware (dev only) — disables caching for `/_framework` and `/_content` assets.
7. `MapStaticAssets()` — endpoint mapper for Blazor framework assets and remaining static content.
8. Development-only `UseStaticFiles()` — serves raw TypeScript from `/Interop/` for source-map debugging.
9. `MapControllers()` and `MapRazorComponents()` — 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 listing
- `GET api/track/{trackId}` — WAV audio streaming (handles `Range` header for seek-beyond-buffer)
- `GET api/track/albums` — distinct albums with counts
- `GET api/track/genres` — distinct genres with counts
- `GET api/track/random` — random track selection
- `GET api/track/meta/by-key/{entryKey}` — metadata lookup by vault entry key
- `POST 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
```bash
# 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 for `ApiUrls:*` (DeepDrftAPI base addresses), `Logging:*`, `AllowedHosts`, `ForwardedHeaders`. Port binding via `Kestrel:Endpoints` or `ASPNETCORE_URLS`.
- `environment/api.json`: Secrets file (via `CredentialTools.ResolvePathOrThrow`) with production DeepDrftAPI URLs (`ApiUrls:ContentApi`, `ApiUrls:SqlApi`).
- MudBlazor theme (`MainLayout.razor` in 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`.