Rewrite all folder-level CLAUDE.md files; update root

net10.0 throughout; corrected EntryKey field name; documented *.Services split.
Two new files (DeepDrftWeb.Services, DeepDrftContent.Services). FileDatabase
README inaccuracies fixed (ImageVault, project target).
This commit is contained in:
Daniel Harvey
2026-05-16 21:45:56 -04:00
parent de0909f38f
commit 7cf3d965b6
10 changed files with 1233 additions and 869 deletions
+99 -119
View File
@@ -1,146 +1,126 @@
# CLAUDE.md - DeepDrftWeb.Client
This file provides guidance to Claude Code (claude.ai/code) when working with the DeepDrftWeb.Client project.
Guidance for working in the DeepDrftWeb.Client project (the Blazor WebAssembly assembly).
## Project Overview
See the root `CLAUDE.md` for full architecture overview. This file covers what is specific to this project.
DeepDrftWeb.Client is a **Blazor WebAssembly** client project that provides interactive UI components for the DeepDrft music management system. It runs in the browser and communicates with the server-side DeepDrftWeb application.
## One-line purpose
## Architecture
All interactive UI for the site. Blazor WebAssembly. Pages, controls, the streaming audio player stack, theme/dark-mode plumbing, HTTP clients for both backends.
### Technology Stack
- **Blazor WebAssembly**: Client-side .NET runtime in browser
- **MudBlazor**: Material Design UI components
- **HttpClient**: API communication with server
- **ASP.NET Core 9.0**: Framework components
## Actual structure
### Project Structure
```
DeepDrftWeb.Client/
├── Pages/ # Routable page components
│ ├── Home.razor # Home page
│ ├── Counter.razor # Demo counter page
├── Weather.razor # Demo weather page
│ └── TracksView.razor # Main tracks interface
├── Controls/ # Reusable UI components
│ ├── TracksGallery.razor # Grid layout for tracks
│ └── TrackPlayer.razor # Individual track player
├── Layout/ # Layout components
│ ├── MainLayout.razor # Primary layout
│ └── NavMenu.razor # Navigation menu
├── Clients/ # HTTP API clients
├── ViewModels/ # Component view models
├── wwwroot/ # Static web assets
└── Program.cs # WebAssembly entry point
```
- `Pages/`: Routable components. `Home.razor` (hero/about), `TracksView.razor` (track gallery with pagination/sorting). **No demo pages** (`Counter.razor`, `Weather.razor` do not exist).
- `Layout/`: `MainLayout.razor` (root layout, wraps in `AudioPlayerProvider`, hosts theme switcher), `DeepDrftMenu.razor` (branded menu bar), `NavMenu.razor` (nav list), `Pages.cs` (centralised nav index — `MenuPages` for header, `AllPages` for exhaustive list).
- `Controls/`: Reusable components.
- `TrackCard.razor`: Individual track display (image, name, artist, album, genre, release date).
- `TracksGallery.razor`: Responsive grid of `TrackCard` items (MudBlazor `MudGrid` with breakpoints).
- `AppNavLink.razor`: Nav link with active-page highlight.
- `AudioPlayerProvider.razor`: Cascading host for `IPlayerService`. Everything inside it gets the player via `[CascadingParameter]`.
- `AudioPlayerBar.razor`: Dock UI at the bottom (play/pause/seek/volume).
- `SpectrumVisualizer.razor`: Bar-graph spectrum display, driven by `getSpectrumData` JS callback.
- `Services/`: Audio player + dark-mode services.
- `IPlayerService` / `IStreamingPlayerService`: Contracts exposed to UI.
- `AudioPlayerService`: Abstract base (lifecycle, initialise, select track, play/pause/stop/seek/volume).
- `StreamingAudioPlayerService`: Production implementation. Chunked stream from `TrackMediaClient`, adaptive 1664 KB buffer, early-playback, **seek-beyond-buffer** via offset request to the content API.
- `AudioInteropService`: JS interop wrapper over `window.DeepDrftAudio`. Manages `DotNetObjectReference` lifetimes for progress, end-of-playback, spectrum callbacks.
- Dark-mode services: `DarkModeServiceBase` (cookie name constant), `DarkModeCookieService` (JS cookie read/write).
- `Clients/`: HTTP API clients.
- `TrackClient`: SQL metadata API. Uses named `IHttpClientFactory` client `"DeepDrft.API"`. Async methods like `GetPageAsync(pageNumber, pageSize, sortColumn, sortDescending)``ApiResult<PagedResult<TrackEntity>>`.
- `TrackMediaClient`: Content API. Uses named `IHttpClientFactory` client `"DeepDrft.Content"`. Methods like `GetAudioStreamAsync(trackId, offset)``Stream`.
- `ViewModels/`: Component state.
- `TracksViewModel`: Scoped. Holds current page, page size, sort column, descending flag. `SetPage(pageNumber)` calls `TrackClient.GetPageAsync` and updates. Registered in `Startup.ConfigureDomainServices`.
- `Common/`: Shared utilities.
- `DarkModeSettings.cs`: `[PersistentState]`-annotated class (single source of truth for dark mode in the client). Registered scoped.
- `DDIcons.cs`: Hand-rolled SVG icons (gas-lamp lit/unlit for dark mode toggle).
- `Program.cs`: WASM entry point. Calls `Startup.ConfigureApiHttpClient`, `ConfigureContentServices`, `ConfigureDomainServices`.
- `_Imports.razor`: Global using statements and component imports.
## Key Patterns
## Two HTTP clients pattern
### MVVM Pattern
Components use ViewModels for data management and business logic separation:
```csharp
// TracksViewModel.cs - Manages tracks data and pagination
public class TracksViewModel
{
public PagedResult<TrackEntity>? Page { get; set; }
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; }
public string SortBy { get; set; }
public bool IsDescending { get; set; }
}
```
Both clients are configured in `Startup.cs` (static methods called from **both** server and WASM `Program.cs`):
### HTTP Client Pattern
API communication through dedicated client classes:
```csharp
// TrackClient.cs - Handles track-related API calls
public async Task<ApiResult<PagedResult<TrackEntity>>> GetPage(
int pageNumber, int pageSize, string? sortColumn = null, bool sortDescending = false)
```
- `TrackClient` uses `"DeepDrft.API"` (base address from `appsettings.json` `ApiUrls:SqlApi`). Fetches paginated metadata.
- `TrackMediaClient` uses `"DeepDrft.Content"` (base address from `appsettings.json` `ApiUrls:ContentApi`). Streams audio bytes, optionally with offset.
### Component Architecture
- **Pages**: Routable components (URL endpoints)
- **Controls**: Reusable components with parameters
- **Layout**: Shared layout structures
Both are configured with JSON serializer settings (case-insensitive property matching). The dual-client pattern keeps concerns separated: one for structured data, one for binary streaming.
## Key Components
## Audio player stack (deepest part of the codebase)
### TracksGallery.razor
Grid-based track display using MudBlazor responsive grid:
```razor
<MudGrid Spacing="3" Justify="Justify.Center">
@foreach (var track in Tracks)
{
<MudItem xs="12" sm="6" md="4" lg="2" xl="2">
<TrackPlayer Track="@track" />
</MudItem>
}
</MudGrid>
```
### Contracts
- `IPlayerService`: Initialize, SelectTrack, Play, Pause, Stop, Seek, SetVolume. Sync interface.
- `IStreamingPlayerService`: Extends above. SelectTrackStreaming(track) starts the chunked stream flow.
### TrackPlayer.razor
Individual track player component with audio controls and track information display.
### Implementation
- `AudioPlayerService` (abstract base): Lifecycle. Stores current track, playback state, volume. Derived classes implement `SelectTrackStreaming` / `SelectTrackImmediate`.
- `StreamingAudioPlayerService` (production): Constructor takes `TrackMediaClient`, `AudioInteropService`, logger. `SelectTrackStreaming`:
1. Calls `TrackMediaClient.GetAudioStreamAsync(trackId, offset: 0)`.
2. `StreamingAudioPlayerService.StreamAudioAsync` reads chunks (1664 KB adaptive), pushes each via `AudioInteropService.ProcessStreamingChunkAsync` (JS interop call).
3. TypeScript `StreamDecoder` parses WAV header (first chunk), decodes subsequent chunks to `AudioBuffer`s.
4. `PlaybackScheduler` schedules buffers on Web Audio `AudioContext`.
5. Playback starts as soon as a configurable min buffer count is queued.
6. **Seek beyond buffer**: if seek target is past the decoded range, `Seek(position)` calls `TrackMediaClient.GetAudioStreamAsync(trackId, offset: byteOffset)`. Server's `WavOffsetService` synthesises a new 44-byte WAV header and streams from the offset. Player tears down and re-initialises decoder for the new stream.
### TracksView.razor
Main tracks management interface combining gallery view with pagination and sorting controls.
### Interop bridge
- `AudioInteropService.ProcessStreamingChunkAsync(chunk)` calls JS `window.DeepDrftAudio.processStreamingChunk(chunk)` and awaits the Promise.
- `AudioInteropService` also manages callback registrations for progress (fired by `PlaybackScheduler`), end-of-playback (fired by `PlaybackScheduler`), and spectrum data (fired by `SpectrumAnalyzer`). Each callback is a `DotNetObjectReference` to a delegate.
## Development Commands
### Component integration
- `AudioPlayerProvider.razor` is the cascading host. It injects `IPlayerService` (resolved to `StreamingAudioPlayerService` in DI), stores it in a cascade, and keeps it alive across navigation.
- `AudioPlayerBar.razor` is the dock UI. It cascades the player, binds buttons to `Play()` / `Pause()` / `Seek()` / `SetVolume()`, and displays current time / duration / progress bar.
- `SpectrumVisualizer.razor` calls `AudioInteropService.GetSpectrumData()` on a timer, receives bar heights, renders via MudBlazor `MudChart` or custom canvas.
- `TracksView.razor` injects `TracksViewModel` + cascaded `IPlayerService`. `PlayTrack(track)` calls `PlayerService.SelectTrack(track)` (which resolves to `StreamingAudioPlayerService.SelectTrackStreaming(track)`).
## Dark-mode plumbing
- `DarkModeSettings` (`Common/`): `[PersistentState]`-annotated class with `IsDarkMode` property. Registered scoped in `Startup.ConfigureDomainServices`. Single source of truth in the client.
- `DarkModeServiceBase`: Holds the cookie name constant (`"darkMode"`).
- `DarkModeCookieService`: Reads/writes the cookie via JS (`document.cookie` interop). Calls `DarkModeSettings.IsDarkMode = value` when the cookie changes or user toggles the button.
- Server-side `DarkModeService` (in `DeepDrftWeb`, **not here**): Reads the cookie during prerender, seeds the `DarkModeSettings` instance, rounds it through `PersistentComponentState` to the client.
- `MainLayout.razor`: Wraps entire layout in `CascadingValue` of `DarkModeSettings`, so all children see the current dark-mode state. The dark-mode toggle button (hand-rolled lit/unlit gas-lamp icon from `DDIcons.cs`) calls `DarkModeCookieService.ToggleDarkModeAsync()`.
The flow ensures the first paint uses the correct theme (no flash), and toggling the button persists the setting to a 365-day cookie.
## MVVM convention
Component state lives in ViewModels (registered scoped in DI). Components render and dispatch only.
- `TracksViewModel`: Holds page number, page size, sort column, descending flag. `SetPage(pageNumber)` is the command. `TracksView.razor` injects it and calls `SetPage`.
- New VMs go in `ViewModels/` and register in `Startup.ConfigureDomainServices`.
## Theming convention
- Bespoke `PaletteLight` / `PaletteDark` defined inline in `MainLayout.razor` (MudBlazor theme objects).
- CSS classes prefixed `deepdrft-` live in `DeepDrftWeb/wwwroot/styles/deepdrft-styles.css` (shared across server and client).
- Custom SVG icons: `Common/DDIcons.cs` (hand-rolled gas-lamp, etc.).
## Development commands
### Building
```bash
# Build WebAssembly project
# The client runs as part of the DeepDrftWeb host:
dotnet run --project DeepDrftWeb
# Watch during development (rebuilds WASM as you change .cs/.razor/.ts files):
dotnet watch run --project DeepDrftWeb
# Build just the client (for verification):
dotnet build DeepDrftWeb.Client
# Publish for production
dotnet publish DeepDrftWeb.Client -c Release
```
### Running
The client runs as part of the DeepDrftWeb host application:
```bash
# Run from DeepDrftWeb (hosts the client)
dotnet run --project DeepDrftWeb
# Run client-specific tests (if any; currently none exist):
dotnet test DeepDrftTests/
```
## Configuration
### Service Registration
Services registered in `Startup.ConfigureDomainServices()`:
```csharp
// Clients for API communication
builder.Services.AddTransient<TrackClient>();
- `Program.cs`: Entry point. Calls `Startup.ConfigureApiHttpClient` (registers named clients), `ConfigureContentServices` (same), `ConfigureDomainServices` (registers services like `TracksViewModel`, `DarkModeSettings`, `AudioPlayerService`).
- Both `Startup` methods are static and called from **both** the server `DeepDrftWeb/Program.cs` and the client `Program.cs`, ensuring prerender and runtime DI are identical.
- No `appsettings.json` in the WASM assembly — config comes from the server `appsettings.json` via HTTP or is hardcoded.
// ViewModels for component state management
builder.Services.AddTransient<TracksViewModel>();
```
## Important patterns
### HTTP Client Setup
Configured to communicate with the hosting DeepDrftWeb server:
- Uses dependency injection for HttpClient
- JSON serialization with case-insensitive property matching
- Query string building for API parameters
- **Cascading parameters**: `AudioPlayerProvider` cascades `IPlayerService`. All children (including `MainLayout` and pages) access it via `[CascadingParameter] IPlayerService Player { get; set; }`.
- **Result types**: Clients return `ApiResult<T>` from NetBlocks. UI checks `Success` before using `Value`.
- **Async/await**: All operations are async.
- **Stream consumption**: `TrackMediaClient.GetAudioStreamAsync` returns a `Stream` (not fully buffered). `StreamingAudioPlayerService` reads it in chunks to avoid memory pressure on large files.
## Important Patterns
### API Communication
All API calls use the established result pattern:
- `ApiResult<T>` for typed responses
- JSON deserialization with `JsonSerializer`
- Query string construction for GET parameters
### MudBlazor Integration
- Responsive grid system (`MudGrid`, `MudItem`)
- Breakpoint-aware layout (xs, sm, md, lg, xl)
- Material Design components throughout
### Component Parameters
Components accept parameters for data binding:
```razor
[Parameter] public List<TrackEntity> Tracks { get; set; } = [];
[Parameter] public TrackEntity Track { get; set; } = null!;
```
### State Management
ViewModels handle component state, pagination, and sorting logic, keeping components focused on presentation.
When working with this project, maintain the separation between presentation (Razor components) and logic (ViewModels/Clients), and follow the established MudBlazor patterns for responsive UI design.
When working with this project, maintain the separation between presentation (Razor components) and logic (ViewModels/Clients), follow the established audio player architecture, and respect the dark-mode round-trip (cookie → DarkModeSettings → PersistentComponentState → client).