diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..4eb2306 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,200 @@ +# DeepDrftHome — Project Context + +Living orientation doc for what this repo is, how it is currently shaped, and where it appears headed. Sits alongside the root `CLAUDE.md` (operational guidance) — this file is the product/architecture view. + +> **Drift notice.** The root `CLAUDE.md` and every folder-level `CLAUDE.md` currently in the tree describe the project as `.NET 9`. The most recent commit upgraded all projects to `.NET 10` (every `.csproj` now targets `net10.0`, packages pinned at `10.0.1`). Until those docs are refreshed, treat any framework-version claim in them as stale. The other staleness items are listed at the bottom of this file. + +--- + +## 1. What this project is + +DeepDrftHome is the home + listening surface for **DeepDrft**, a two-person electronic music collective based in Charleston, SC (per `DeepDrftWeb.Client/Pages/Home.razor`). The product is, at minimum: + +- A public-facing site (hero, about, "experience" features). +- A **track gallery** that browses a library of WAV recordings, plays them in-browser with a persistent dock-style player, and supports seek (including seek beyond what's been streamed so far). +- An admin CLI for adding tracks (Terminal.Gui or scripted), running locally against the same dual-database substrate the site uses. + +The interesting engineering bet is the **dual-database split**: structured track metadata in SQLite via EF Core, and binary media + per-vault indexes in a hand-rolled `FileDatabase` that lives on disk. The split is enforced across two ASP.NET Core hosts so that the browser never reaches the database directly. + +--- + +## 2. Solution shape (current) + +Eight projects in `DeepDrftHome.sln`, plus an external `NetBlocks` referenced from `C:\lib\NetBlocks\`. + +``` +DeepDrftWeb ASP.NET Core host. Blazor Web App (Server + WASM render modes). + Owns the SQL-backed API (api/track/page), MudBlazor theme/host, + TypeScript→JS audio interop sources under Interop/. +DeepDrftWeb.Client Blazor WebAssembly assembly. All interactive UI lives here — + pages, controls, player services, dark-mode/theme plumbing, + HTTP clients for both backends. +DeepDrftWeb.Services Class library. EF Core: DeepDrftContext, TrackConfiguration, + Migrations, TrackRepository, TrackService. Sharable between + the web host and the CLI (avoids duplicating data-access). + +DeepDrftContent ASP.NET Core host. Binary content API (api/track/{id}). + ApiKey middleware, CORS, ForwardedHeaders. Returns audio bytes + (with optional byte offset) and accepts PUT of AudioBinaryDto. +DeepDrftContent.Services Class library. The FileDatabase implementation in full + (Models, Services, Utils, Abstractions, Constants), + WavOffsetService, AudioProcessor, TrackService (the content-side + orchestrator that processes WAVs and stores them in a vault). + +DeepDrftModels Shared contracts: TrackEntity, TrackDto, PagingParameters, + PagedResult. The only project all three layers reference. + +DeepDrftCli Console app. Two modes: classic `add` / `list` / `help` and + `gui` (Terminal.Gui). Consumes BOTH service libraries directly + (it's a local admin tool, not a network client). + +DeepDrftTests NUnit. Covers the FileDatabase, MediaVault, IndexSystem, + MediaVaultFactory, SimpleMediaTypeRegistry, utility code, and + model behaviour. References DeepDrftContent.Services. + +NetBlocks (external) Result patterns: Result, ResultContainer, ApiResult, + ApiResultDto. Referenced via absolute path. +``` + +Two stray .sln files (`WebAPI.sln`, `WebUI.sln`, `CLI.sln`) exist at the root alongside `DeepDrftHome.sln`. `DeepDrftHome.sln` is the canonical solution; the others appear to be subsets. + +--- + +## 3. Architectural decisions worth remembering + +### 3.1 Dual-database, two hosts, one client + +The system is partitioned so the browser never touches storage directly: + +``` +DeepDrftWeb.Client (WASM) + │ + ├── HttpClient "DeepDrft.API" ──► DeepDrftWeb host ──► EF Core / SQLite + │ (paged track metadata) + │ + └── HttpClient "DeepDrft.Content" ──► DeepDrftContent ──► FileDatabase / disk + (audio bytes + offset stream) (ApiKey required for PUT) +``` + +The two HttpClients are named, configured in `DeepDrftWeb.Client.Startup`, and base addresses are passed in from server-host configuration (`ApiUrls:ContentApi`). Read endpoints in DeepDrftContent (`GET api/track/{id}`) are **unauthenticated** — only mutating endpoints carry `[ApiKeyAuthorize]`. CORS is enforced per `CorsSettings.AllowedOrigins`. + +### 3.2 Service projects vs. host projects + +The split between `DeepDrftWeb` / `DeepDrftWeb.Services` (and the same for Content) is deliberate: the host projects own the HTTP surface, configuration, and DI wiring, while the *Services* projects are plain class libraries holding the domain logic. This is what lets `DeepDrftCli` reuse both `DeepDrftWeb.Services.TrackService` and `DeepDrftContent.Services.TrackService` without taking a dependency on either ASP.NET host. New domain logic should land in the *.Services projects unless it is genuinely host-specific (controllers, middleware, render-mode configuration, theme prerender). + +### 3.3 FileDatabase as a first-class subsystem + +The `FileDatabase` (in `DeepDrftContent.Services/FileDatabase/`) is the unusual piece. It is a port of a TypeScript system (see in-tree `FileDatabase/README.md`) and is the only place binary content lives. + +- A **FileDatabase** is a directory containing typed **MediaVaults**. +- Each MediaVault has its own JSON `index` file that lists entries and their per-entry metadata. +- Vaults are typed via `MediaVaultType` (`Media | Image | Audio`). The vault type is recorded in `VaultIndex` and rehydrated on startup. +- Entry keys are user-supplied strings, sanitised to `[a-zA-Z0-9]` plus `-` and concatenated with the file extension to produce on-disk filenames. +- Resources implement a binary hierarchy: `FileBinary → MediaBinary (+ Extension/MIME) → AudioBinary (+ Duration, Bitrate) | ImageBinary (+ AspectRatio)`. DTO variants ship as base64-over-JSON. +- An `IndexWatcher` (FileSystemWatcher) reloads a vault's index when an external process (e.g. the CLI) writes to it, so the web host stays consistent without restart. +- Error-handling philosophy: load/register operations **swallow exceptions and return `null`/`false`** to match the TypeScript original. Callers must check return values, not catch. + +The only authorised callers of FileDatabase APIs are the DeepDrftContent host and the CLI — anything that runs in the browser must go through the HTTP endpoints. + +### 3.4 TrackEntity is a join, not a content blob + +`TrackEntity` (in `DeepDrftModels`) holds *only* metadata. The link to binary content is the `EntryKey` string, which is the entry id inside the `tracks` audio vault in the FileDatabase. The current schema: + +```csharp +public class TrackEntity +{ + public long Id { get; set; } + public required string EntryKey { get; set; } // FileDatabase entry id, vault = "tracks" + public required string TrackName { get; set; } + public required string Artist { get; set; } + public string? Album { get; set; } + public string? Genre { get; set; } + public DateOnly? ReleaseDate { get; set; } + public string? ImagePath { get; set; } +} +``` + +Note: many existing docs (every folder `CLAUDE.md`, plus the FileDatabase README) still refer to the older `MediaPath` name. The migration `20250904233927_Initial.cs` already ships the `entry_key` column, so `MediaPath` is purely a docs-staleness artefact. + +### 3.5 Streaming-first audio playback + +The player is not a fetch-then-play model. The flow is: + +1. Client calls `GET api/track/{id}` on DeepDrftContent and receives the WAV bytes as a stream (`HttpCompletionOption.ResponseHeadersRead`). +2. `StreamingAudioPlayerService` reads the stream in adaptive 16–64 KB chunks and pushes each chunk into JS via `AudioInteropService.processStreamingChunk`. +3. The TypeScript `StreamDecoder` parses the WAV header, decodes chunks to `AudioBuffer`s, and the `PlaybackScheduler` schedules them on a single Web Audio graph. +4. Playback starts as soon as a configurable minimum number of buffers is queued; UI duration is set from the parsed WAV header (not waiting for the full file). +5. **Seek beyond buffer**: if a seek target is past what's been decoded, the client issues a new `GET api/track/{id}?offset={byteOffset}` request. The server's `WavOffsetService` block-aligns the offset, synthesises a fresh 44-byte WAV header sized for the remaining data, and streams `[new header][data from offset]`. The player tears down and re-initialises its decoder for the new stream. + +This is the seam where the FileDatabase's "everything is a complete blob in memory" model meets the browser's "I need to stream and seek" model. Keep this seam clean — it is the most architecturally load-bearing part of the playback path. + +### 3.6 Theming, dark mode, and prerender + +- MudBlazor is the UI framework. Light and dark palettes are bespoke ("Charleston in the Day" / "Lowcountry Summer Nights") and defined inline in `MainLayout.razor`. +- Dark mode toggles via a long-lived cookie (`darkMode`, 365 days), set client-side via JS interop. +- During server prerender, `DarkModeService` (in `DeepDrftWeb`) reads the cookie and seeds `DarkModeSettings.IsDarkMode`, which carries the value into the WASM render via `PersistentComponentState`. This is what avoids a "wrong theme flash" on initial paint. The `DarkModeSettings` lives in `DeepDrftWeb.Client.Common` because it is consumed by both the server `DarkModeService` and client components. +- Typography uses three Google Fonts (Bodoni Moda, Cormorant, DM Sans). The hand-rolled gas-lamp icon (lit/unlit) lives in `DDIcons.cs` and is what swaps in the dark-mode toggle. + +### 3.7 TypeScript interop, not raw JS + +The audio interop is authored in TypeScript under `DeepDrftWeb/Interop/audio/` and compiled into `wwwroot/js/audio/` via `Microsoft.TypeScript.MSBuild`. The split is intentional: AudioContextManager / StreamDecoder / PlaybackScheduler / SpectrumAnalyzer / AudioPlayer are each their own module, and `index.ts` glues them onto `window.DeepDrftAudio` for Blazor to invoke. The `tsconfig.json` is configured for ES module interop and is **not** copied to output. + +In dev, the host serves the original `.ts` sources at `/Interop/...` for source-map debugging. + +--- + +## 4. Where the system is today + +Recent commits (newest first): + +- `style simplification and publish upgrades for dotnet 10` +- `Styles & Home Page Content Cleanup Mobile Menu System & Dark Mode Cookie Theme Draft` +- `Theming Draft 2` +- `2026 Deep DRFT Theme Draft 1 WIP` +- `Spectrum Visualizer for player & Layout` + +Three observations: + +1. **The current arc is presentation, not capability.** The last five commits are framework upgrade, theming, content/layout cleanup, mobile menu, dark-mode persistence, and the spectrum visualiser. The playback substrate, streaming, and seek-beyond-buffer machinery landed earlier and is stable enough to support cosmetic iteration on top. +2. **The "Track Gallery" is the only real page.** `/tracks` is the working surface; `/` is marketing copy. Nav (in `Pages.cs`) defines only `Home` + `Track Gallery`. +3. **Content surface is narrow on purpose.** The DeepDrftContent API exposes exactly two routes: `GET api/track/{id}` (with optional `offset`) and `PUT api/track/{id}` (ApiKey). There is no listing endpoint there; listing lives on DeepDrftWeb because listings are SQL queries. + +--- + +## 5. Likely directions (inferred, not committed) + +Captured here so the next round of planning has a starting point — none of this is decided. + +- **More vault types in active use.** `MediaVaultType.Image` exists end-to-end (tests cover it) but the production surface only registers a `tracks` vault of type `Audio`. The path to releases/albums probably runs through images first (cover art via `ImagePath`, which is currently a free-form URL string). +- **More than one collection view.** The `TrackCard` already conditionally renders `ImagePath`, `Album`, `Genre`, `ReleaseDate` — the data shape supports album-grouped or genre-filtered views without schema work. +- **Upload from the web side, not just the CLI.** The CLI is currently the only producer of tracks. A web-side upload would re-use `DeepDrftContent.Services.TrackService.AddTrackFromWavAsync` and pair it with a `TrackService.Create` on the SQL side. The `[ApiKeyAuthorize]` middleware on `PUT api/track/{id}` is already in place. +- **Live/session content.** The home page advertises "Live Sessions" and "Video Content (coming soon)". No data model exists for these yet; they would likely need new vault types (`MediaVaultType.Media` is the obvious home for video) and new entity tables. +- **Non-WAV formats.** Today the producer side is WAV-only (`AudioProcessor.ProcessWavFileAsync` validates RIFF/WAVE/PCM). `MimeTypeExtensions` already knows mp3/flac/aac/ogg/m4a — the gap is a processor per format and a decoder strategy in the JS player (currently WAV-specific). +- **Search / filter on the gallery.** `TracksViewModel` exposes `SortBy` / `IsDescending` but no filter. `TrackService.GetPaged` accepts only sort, not filter. Adding filter would be a natural next step on the same pagination contract. + +--- + +## 6. Conventions that should hold + +- **Plan docs at the repo root.** `CONTEXT.md` (this file), and when work in flight warrants them, `PLAN.md` / `TODO.md` / `COMPLETED.md`. Completed `PLAN.md` items move to `COMPLETED.md` rather than being deleted. +- **`*.Services` libraries own domain logic.** Host projects only do HTTP/wiring/render concerns. New repositories, services, processors, and storage code go in `DeepDrftWeb.Services` or `DeepDrftContent.Services`. +- **Shared contracts in `DeepDrftModels` only.** If a type crosses a project boundary, it lives in `DeepDrftModels`. Project-specific DTOs (e.g. `AudioBinaryDto` for the content API) stay local to where they serialise. +- **No direct DB from network clients.** The browser must not import EF or speak to SQLite. All access goes through the two API hosts. (The CLI is allowed direct DB access because it is not a network client.) +- **One source of truth per concept, multiple views over it.** The dark-mode flow (cookie → `DarkModeSettings` → server `DarkModeService` for prerender + client `DarkModeCookieService` for runtime + persistent state across the boundary) is a small example of this: one truth, distinct rendering paths. New view modes for tracks should consume the same `TrackEntity` / `PagedResult` and differ only at the rendering layer. +- **Result types from NetBlocks.** Services return `Result`, `ResultContainer`, or `ApiResult` rather than throwing for expected failure modes. Catch at the service boundary, surface via the result. + +--- + +## 7. Staleness in existing docs (for doc-keeper to address) + +Captured so the next sweep of folder-level `CLAUDE.md` files can correct in one pass. + +- Every folder `CLAUDE.md` says ".NET 9" / "ASP.NET Core 9.0"; reality is `net10.0` across the board. +- `DeepDrftModels/CLAUDE.md` and `DeepDrftContent.Services/FileDatabase/README.md` reference `TrackEntity.MediaPath`; the field is `EntryKey` and the column is `entry_key`. +- `DeepDrftContent/CLAUDE.md` describes a `FileDatabase/` tree inside `DeepDrftContent/`; that tree has moved entirely to `DeepDrftContent.Services/FileDatabase/`. The DeepDrftContent host now contains only `Controllers/`, `Middleware/`, `Models/` (settings POCOs), `environment/`, `Program.cs`, `Startup.cs`. +- `DeepDrftContent/CLAUDE.md` documents only the PUT endpoint; the production API now also has `GET api/track/{id}?offset=` (unauthenticated read, with `WavOffsetService` for offset streaming). +- `DeepDrftWeb/CLAUDE.md` describes EF Core, repositories, services, migrations as living inside `DeepDrftWeb/Data` and `DeepDrftWeb/Services`. They have all moved to `DeepDrftWeb.Services`. The only things still in `DeepDrftWeb` are `Controllers/TrackController.cs`, `Services/DarkModeService.cs`, `Startup.cs`, `Program.cs`, `Components/`, `Interop/`, `wwwroot/`. +- `DeepDrftWeb.Client/CLAUDE.md` lists the `Pages/` directory as containing `Counter.razor` / `Weather.razor` (demo); those are gone. The real client structure is `Pages/Home.razor` + `Pages/TracksView.razor`, plus the `Controls/AudioPlayerBar/` cluster, `Controls/AudioPlayerProvider.razor`, `Services/AudioInteropService.cs` + `AudioPlayerService.cs` + `StreamingAudioPlayerService.cs` + `IPlayerService.cs` + dark-mode services, `Common/DarkModeSettings.cs` + `Common/DDIcons.cs`, and `Layout/Pages.cs` + `Layout/DeepDrftMenu.razor`. +- The `DeepDrftWeb.Services` and `DeepDrftContent.Services` projects have **no** `CLAUDE.md` yet — they are where most of the domain logic actually lives, so this is the biggest gap. +- `DeepDrftCli/CLAUDE.md` references `appsettings.json`; the CLI actually loads `environment/connections.json` into `CliSettings` (with `ConnectionString` and `VaultPath`). The "Available Commands" section is otherwise current, including the `gui` Terminal.Gui mode and interactive `add`. +- `DeepDrftContent.Services/FileDatabase/README.md` (an in-tree dev README, not a CLAUDE.md) refers to `ImageDirectoryVault`; the type is `ImageVault`. It also describes `EntryKey` as removed in favour of strings, which is accurate, but its diagram still says "FileDatabase.csproj (.NET 9.0)" — the FileDatabase no longer has its own csproj at all (it's a subdirectory of `DeepDrftContent.Services`). diff --git a/DOC_PLAN.md b/DOC_PLAN.md new file mode 100644 index 0000000..138d00b --- /dev/null +++ b/DOC_PLAN.md @@ -0,0 +1,254 @@ +# DOC_PLAN.md — Folder-level CLAUDE.md plan + +Brief for doc-keeper. Specifies, folder by folder, what each `CLAUDE.md` should cover and the conventions/patterns to surface. **Authored on a cold-storage read of the current code state on 2026-05-16.** See `CONTEXT.md` for the full architecture orientation. + +--- + +## Ground rules for every folder-level CLAUDE.md + +These apply across the whole sweep — do not restate them inside each file unless the local context genuinely differs. + +1. **Target framework is `net10.0`** for every project. Do not write ".NET 9" anywhere. Package versions are `10.0.1` family across EF Core, ASP.NET, and Extensions; MudBlazor is `8.15.0`; NUnit is `4.4.0`. +2. **Lead with what's true now, not what's aspirational.** Folder CLAUDE.md files are operational guidance for an agent walking into that directory cold. If something is "potential future usage", omit it. +3. **Cross-reference the root.** Root `CLAUDE.md` already explains the solution shape, dual-database model, and high-level patterns. Folder CLAUDE.mds should pick up *from there* and document what makes that folder specific. No re-running the full architecture overview in every file. +4. **No CLAUDE.md in build output / migrations / leaf static-asset folders.** This plan is deliberately scoped to the subfolders below; do not add files for `obj/`, `bin/`, `Migrations/`, `wwwroot/`, `environment/`, `Properties/`. Migrations are EF-generated and not interesting for agent guidance. +5. **Reference paths, not contents, for code.** If the CLAUDE.md needs to show how something is used, prefer a one-line "see `path/file.cs`" over a copied 30-line snippet. The code is the source of truth — folder CLAUDE.mds drift the moment they replicate it. Small inline examples (a 3–5 line signature or call) are fine when the call shape itself is the convention being documented. +6. **The eight existing folder CLAUDE.mds need rewrites, not patches.** All eight contain framework-version drift and most contain structural drift (see CONTEXT.md §7). Treat the existing files as raw material, not as a baseline to incrementally edit. + +--- + +## Files to write / rewrite + +There are **eight** folder CLAUDE.mds in scope. Five already exist and need rewrites; three are new (the two `*.Services` libraries and a recommendation about the `FileDatabase` README being either retired or replaced with a CLAUDE.md). + +| Folder | Status | Priority | +|---|---|---| +| `DeepDrftWeb/` | Rewrite — structural drift | high | +| `DeepDrftWeb.Client/` | Rewrite — major drift (page list, player stack) | high | +| `DeepDrftWeb.Services/` | **New** — no CLAUDE.md exists; domain logic lives here | high | +| `DeepDrftContent/` | Rewrite — FileDatabase tree has moved out | high | +| `DeepDrftContent.Services/` | **New** — no CLAUDE.md; FileDatabase + processors live here | high | +| `DeepDrftModels/` | Rewrite — `MediaPath`→`EntryKey` rename, framework version | medium | +| `DeepDrftCli/` | Rewrite — config file name, GUI/CLI dual mode is mostly accurate | medium | +| `DeepDrftTests/` | Rewrite — framework version, otherwise mostly accurate | low | + +Plus one judgement call: + +- `DeepDrftContent.Services/FileDatabase/README.md` is an in-tree developer README, not a CLAUDE.md. It is stale (`ImageDirectoryVault` is `ImageVault`; the `.csproj (.NET 9.0)` line is wrong since FileDatabase no longer has its own csproj). **Recommendation:** doc-keeper should ask Daniel whether to retire it in favour of a `DeepDrftContent.Services/FileDatabase/CLAUDE.md` or leave it as a long-form README and just refresh the inaccuracies. Default to leaving the README and not adding a separate CLAUDE.md inside the FileDatabase subtree — the parent `DeepDrftContent.Services/CLAUDE.md` should cover what an agent needs to know to operate inside `FileDatabase/`. + +--- + +## Per-folder briefs + +### `DeepDrftWeb/CLAUDE.md` + +**One-line purpose:** The Blazor Web App host. Owns HTTP surface (one controller + render-mode wiring), MudBlazor theme prerender, TypeScript→JS audio interop, and the SQL-side `api/track/page` endpoint. **Domain logic lives elsewhere** (`DeepDrftWeb.Services`). + +**Cover:** + +- What lives here now (only): `Program.cs`, `Startup.cs`, `Controllers/TrackController.cs`, `Services/DarkModeService.cs`, `Components/App.razor` + `Components/Pages/Error.razor`, `Interop/` (TypeScript sources), `wwwroot/` (compiled JS, CSS, fonts, images). +- What does *not* live here anymore: `DeepDrftContext`, `TrackRepository`, `TrackService`, `Configurations/`, `Migrations/`. All moved to `DeepDrftWeb.Services`. Don't write new repositories or EF code in this project. +- The render-mode model: Blazor Web App with `AddInteractiveServerComponents()` + `AddInteractiveWebAssemblyComponents()`. The root component is `` from `Components/App.razor`, and the WASM render-mode loads `DeepDrftWeb.Client._Imports` as an additional assembly. New routable pages should land in `DeepDrftWeb.Client/Pages`, not here. +- The dark-mode prerender bridge: `DarkModeService` reads the `darkMode` cookie via `IHttpContextAccessor` in `App.razor`'s `OnInitialized` and seeds `DarkModeSettings.IsDarkMode` (registered in `DeepDrftWeb.Client.Startup.ConfigureDomainServices`). The setting carries over to WASM via `PersistentComponentState` (`MainLayout.razor`). +- The TypeScript interop pipeline: sources live in `Interop/audio/` (one module per responsibility — `AudioContextManager`, `StreamDecoder`, `PlaybackScheduler`, `SpectrumAnalyzer`, `AudioPlayer`, plus `index.ts` that exposes `window.DeepDrftAudio`). They compile to `wwwroot/js/` via `Microsoft.TypeScript.MSBuild`. `tsconfig.json` must not be copied to output. In development, raw `.ts` is served from `/Interop/` for source-map debugging. +- HTTP client wiring lives mostly in `DeepDrftWeb.Client.Startup`. Server-side `Program.cs` only adds MudBlazor, controllers, render-mode components, SignalR tuning, forwarded-headers config, and calls the client's `Startup.ConfigureApiHttpClient` / `ConfigureContentServices` / `ConfigureDomainServices`. +- Reverse-proxy support: `UseForwardedHeaders` runs first; HTTPS redirect is conditionally disabled via `ForwardedHeaders:DisableHttpsRedirection` so the app can sit behind nginx. +- The one controller (`TrackController`) is a thin wrapper: `GET api/track/page?pageNumber&pageSize&sortColumn&sortDescending` → `DeepDrftWeb.Services.TrackService.GetPaged` → `ApiResultDto>`. If you're adding new SQL endpoints, this is the file; if you're adding new logic, that goes in `DeepDrftWeb.Services`. + +**Do NOT include:** + +- Generic Blazor / EF Core tutorials. +- Anything implying repositories or DbContext belong in this project. +- Build/run commands for the whole solution — keep those at the root. + +--- + +### `DeepDrftWeb.Client/CLAUDE.md` + +**One-line purpose:** All interactive UI for the site. Blazor WebAssembly. Pages, controls, the streaming audio player stack, theme/dark-mode plumbing, HTTP clients for both backends. + +**Cover:** + +- Actual structure: `Pages/` (`Home.razor`, `TracksView.razor`), `Layout/` (`MainLayout.razor`, `DeepDrftMenu.razor`, `NavMenu.razor`, `Pages.cs`), `Controls/` (`TrackCard`, `TracksGallery`, `AppNavLink`, `AudioPlayerProvider`, `AudioPlayerBar/` cluster), `Services/` (audio player services + dark-mode services + streaming error handler), `Clients/` (`TrackClient` for SQL API, `TrackMediaClient` for content API), `ViewModels/` (`TracksViewModel`), `Common/` (`DarkModeSettings`, `DDIcons`). +- The "old demo pages" (`Counter.razor`, `Weather.razor`) do not exist. The site has exactly two routable pages today: `/` and `/tracks`. Nav is centralised in `Layout/Pages.cs` (`MenuPages` + `AllPages`). +- The two HTTP clients pattern: `TrackClient` uses named `IHttpClientFactory` client `"DeepDrft.API"`, `TrackMediaClient` uses `"DeepDrft.Content"`. The clients are configured in `Startup.ConfigureApiHttpClient` and `Startup.ConfigureContentServices` — both static methods called from BOTH the server `Program.cs` and the WASM `Program.cs` so prerender and runtime see the same DI. +- The audio player stack (this is the deepest part of the project and most likely to be edited): + - `IPlayerService` / `IStreamingPlayerService` (the contracts exposed to UI). + - `AudioPlayerService` (abstract base: lifecycle, initialise, select track, play/pause/stop/seek/volume). + - `StreamingAudioPlayerService` (the production implementation: chunked stream from `TrackMediaClient`, adaptive 16–64 KB buffer, early-playback, **seek-beyond-buffer** via a new offset request). + - `AudioInteropService` (JS interop wrapper over `window.DeepDrftAudio`; manages `DotNetObjectReference` lifetimes for progress, end-of-playback, and spectrum callbacks). + - `AudioPlayerProvider.razor` is the cascading host; everything inside it gets the player via `[CascadingParameter]`. `AudioPlayerBar.razor` is the dock UI; `SpectrumVisualizer.razor` is the bar-graph driven by `getSpectrumData`. `MainLayout.razor` wraps the layout in `AudioPlayerProvider`, so the player survives navigation. +- Dark mode plumbing: + - `DarkModeSettings` (in `Common/`) is `[PersistentState]`-annotated and registered scoped — it is the single source of truth in the client. + - `DarkModeServiceBase` holds the cookie name constant. + - `DarkModeCookieService` writes the cookie via JS `document.cookie` and reads `DarkModeSettings`. + - The server-side `DarkModeService` (in `DeepDrftWeb`, NOT here) reads the cookie during prerender. The settings instance round-trips via `PersistentComponentState`. +- `TracksView.razor` flow: injects `TracksViewModel` + cascaded `IPlayerService`. `SetPage` calls `TrackClient.GetPage` and updates the VM. `PlayTrack` calls `PlayerService.SelectTrack(track)` — which under the hood resolves to `StreamingAudioPlayerService.SelectTrackStreaming(track)` and starts the chunked stream. +- MVVM convention: page state lives in a `ViewModel` (scoped DI), components only render and dispatch. `TracksViewModel` holds the current page, page size, sort. Add new VMs in `ViewModels/` and register in `Startup.ConfigureDomainServices`. +- Theming convention: bespoke `PaletteLight` / `PaletteDark` live inline in `MainLayout.razor`. CSS classes prefixed `deepdrft-` live in `DeepDrftWeb/wwwroot/styles/deepdrft-styles.css`. Custom SVG icons live in `Common/DDIcons.cs`. + +**Do NOT include:** + +- `Counter.razor` / `Weather.razor` references. +- "ASP.NET Core 9.0". +- Any claim that this project hosts a server or controllers — it is a class-libraryish WASM assembly consumed by `DeepDrftWeb`. + +--- + +### `DeepDrftWeb.Services/CLAUDE.md` (new) + +**One-line purpose:** SQL-side domain logic for tracks. EF Core context, configurations, migrations, repository, service, design-time factory. Consumed by both `DeepDrftWeb` and `DeepDrftCli`. + +**Cover:** + +- Why this project exists: separating domain from host so the CLI can reuse `TrackService` / `TrackRepository` / `DeepDrftContext` without referencing the ASP.NET host. New SQL-side domain code goes here, not in `DeepDrftWeb`. +- Layout: `Data/DeepDrftContext.cs`, `Data/DeepDrftContextFactory.cs` (design-time, hard-codes `../Database/deepdrft.db` for `dotnet ef`), `Data/Configurations/TrackConfiguration.cs`, `Migrations/`, `Repositories/TrackRepository.cs`, `TrackService.cs`. +- The Controller → Service → Repository → DbContext shape. Services return `Result` / `ResultContainer` from NetBlocks; repositories throw / return raw types. Exceptions are caught at the service boundary. +- `TrackEntity` field reality: `Id`, `EntryKey` (required, max 100, the FileDatabase entry id), `TrackName` (max 200), `Artist` (max 200), `Album?` (max 200), `Genre?` (max 100), `ReleaseDate?` (`DateOnly`), `ImagePath?` (max 500). Column names are snake_case. Table is `track` (singular). +- Pagination convention: `TrackService.GetPaged` builds a `PagingParameters` with an `OrderBy` `Expression>`. Switch in `GetPaged` maps a string sort column to the expression. New sort columns extend this switch. Nulls sort to the end (via padded sentinel strings / `DateOnly.MaxValue`). +- EF migration commands (run from solution root): `dotnet ef migrations add --project DeepDrftWeb.Services --startup-project DeepDrftWeb` and `dotnet ef database update --project DeepDrftWeb.Services --startup-project DeepDrftWeb`. The design-time factory means you can also run `dotnet ef ... --project DeepDrftWeb.Services` standalone for local development. +- The migrations namespace is `DeepDrftWeb.Migrations` (a legacy name, retained for migration history continuity). +- Connection string source: `appsettings.json` → `ConnectionStrings:DefaultConnection` on the web side; `environment/connections.json` → `CliSettings:ConnectionString` on the CLI side. Both point at `../Database/deepdrft.db`. + +**Do NOT include:** + +- ASP.NET Core lifecycle, controllers, middleware — those are not in scope here. +- HTTP client setup or theming. + +--- + +### `DeepDrftContent/CLAUDE.md` + +**One-line purpose:** The binary content API host. ApiKey middleware, CORS, forwarded headers. Returns audio bytes (with optional WAV-aware offset) and accepts authenticated PUTs. **FileDatabase implementation lives in `DeepDrftContent.Services`, not here.** + +**Cover:** + +- What lives here now (only): `Program.cs`, `Startup.cs`, `Controllers/TrackController.cs`, `Middleware/ApiKeyAuthenticationMiddleware.cs`, `Middleware/ApiKeyAuthorizeAttribute.cs`, `Models/` (settings POCOs: `ApiKeySettings`, `CorsSettings`, `FileDatabaseSettings`), `environment/filedatabase.json`, `environment/apikey.json` (not in repo). +- What does *not* live here anymore: any `FileDatabase/`, `Services/`, `Processors/`, or media model code. All moved to `DeepDrftContent.Services`. Don't add new domain code in this project. +- The endpoint surface (exactly two, do not add a third without product approval): + - `GET api/track/{trackId}?offset=0` — **unauthenticated**, returns the WAV bytes from the `tracks` vault. If `offset > 0`, `WavOffsetService.CreateOffsetStream` block-aligns the offset and synthesises a fresh 44-byte WAV header so the response is a valid standalone WAV starting from that byte position. + - `PUT api/track/{trackId}` — **`[ApiKeyAuthorize]`**, accepts `AudioBinaryDto` (base64 buffer + size + mime + duration + bitrate), writes via `FileDatabase.RegisterResourceAsync` into the `tracks` vault. +- ApiKey middleware behaviour: only enforces on endpoints with `[ApiKeyAuthorize]` metadata. Reads header `ApiKey`. Returns 401 with body `"API Key was not provided"` or `"Unauthorized client"`. Configured via `environment/apikey.json` (`ApiKeySettings.ApiKey`). +- CORS: `CorsSettings.AllowedOrigins` is required (the app throws on startup if missing). Policy is named `ContentApiPolicy`. AllowCredentials + AllowAnyMethod + AllowAnyHeader. +- Forwarded headers are enabled only in `Production` (this differs from `DeepDrftWeb` which enables them always — be aware when debugging proxy issues). +- Startup wiring (`Startup.ConfigureDomainServices`): adds singleton `WavOffsetService`, loads `environment/filedatabase.json`, awaits `FileDatabase.FromAsync(VaultPath)`, registers it as singleton, and ensures the `tracks` vault exists (`MediaVaultType.Audio`). The vault is created on first boot, then reused. +- OpenAPI is only mapped in `Development`. + +**Do NOT include:** + +- The internals of how `FileDatabase` works — that's the job of `DeepDrftContent.Services/CLAUDE.md`. +- A directory tree showing `FileDatabase/Services/` inside this project; it is not there. + +--- + +### `DeepDrftContent.Services/CLAUDE.md` (new) + +**One-line purpose:** Binary-content domain logic. The FileDatabase implementation, audio processing, WAV stream-with-offset, and the content-side track service. Consumed by `DeepDrftContent` (the host) and `DeepDrftCli`. + +**Cover:** + +- Layout: `FileDatabase/` (the subsystem — `Abstractions/`, `Models/`, `Services/`, `Utils/`), `Audio/WavOffsetService.cs`, `Processors/AudioProcessor.cs`, `Constants/VaultConstants.cs`, `TrackService.cs`. +- FileDatabase model (refer to `DeepDrftContent.Services/FileDatabase/README.md` for the long-form rationale — it's a port of a TypeScript system): + - `FileDatabase` is the root. Created via `FileDatabase.FromAsync(rootPath)`. Holds a `StructuralMap` and an `IndexWatcher`. Implements `IDisposable`. + - Each `MediaVault` is a subdirectory with its own JSON `index` file. Types: `Media | Image | Audio`. Concrete subclasses are `ImageVault` and `AudioVault` (the README's mention of `ImageDirectoryVault` is stale — the type is `ImageVault`). + - Entry filenames: `{sanitized-key}{extension}`, where sanitisation is `Regex.Replace(entryKey, @"[^a-zA-Z0-9]", "-")`. + - Binary hierarchy: `FileBinary → MediaBinary (+ Extension, MIME via `MimeTypeExtensions`) → AudioBinary (+ Duration, Bitrate) | ImageBinary (+ AspectRatio)`. Each has a matching `*Dto` for base64 JSON transport. + - Index lifecycle: `IndexFactoryService` loads-or-creates `DirectoryIndex` (the root index) and `VaultIndex` (per-vault, records `MediaVaultType`). Saved as JSON via `FileUtils.PutObjectAsync`. + - `IndexWatcher` uses `FileSystemWatcher` to detect external writes to a vault's `index` file (the CLI is the typical writer) and triggers `MediaVault.ReloadIndexAsync` so a long-running web host stays consistent. + - **Error-handling philosophy** (load-bearing): public `Load*` / `Register*` operations swallow exceptions and return `null` / `false` to match the TypeScript original. Callers must check return values. Do not change this without a deliberate design pass. +- WAV offset service: `WavOffsetService.CreateOffsetStream(buffer, byteOffset)` parses the WAV header, block-aligns the offset, synthesises a new 44-byte header for the remaining data, and returns `[new header][data from offset]` as a `MemoryStream`. Used by the content API to serve seek-beyond-buffer requests. Block alignment is *required* for clean audio — do not bypass it. +- Audio processor: `AudioProcessor.ProcessWavFileAsync(filePath)` validates the RIFF/WAVE/PCM structure, parses fmt and data chunks, computes duration + bitrate, falls back to defaults (180s / 1411 kbps / 44.1 kHz / 16-bit stereo) on parse failure with a `Console.WriteLine` warning. PCM-only today; other formats are unsupported. +- Content-side `TrackService` (orchestrator): `AddTrackFromWavAsync` reads a WAV from disk, generates a GUID entry key, ensures the `tracks` vault exists, stores the audio, and returns a populated `TrackEntity` (without saving it to SQL — caller does that). `GetAudioBinaryAsync` reads it back. `InitializeTracksVaultAsync` is a safety call. +- `VaultConstants.Tracks = "tracks"` — the one vault name in production use. New vault names go here. + +**Do NOT include:** + +- The HTTP surface of `DeepDrftContent` — that belongs in `DeepDrftContent/CLAUDE.md`. +- A reproduction of the FileDatabase README's full design discussion — link to it instead. + +--- + +### `DeepDrftModels/CLAUDE.md` + +**One-line purpose:** Shared contracts. Entities, DTOs, pagination types. Every project references this; nothing else references the projects that reference this. + +**Cover:** + +- Layout: `Entities/TrackEntity.cs`, `DTOs/TrackDto.cs`, `Models/PagingParameters.cs`, `Models/PagedResult.cs`. +- `TrackEntity` fields (verbatim from current code — replace any older field list): `Id` (long PK), `EntryKey` (required string, FileDatabase entry id), `TrackName` (required), `Artist` (required), `Album?`, `Genre?`, `ReleaseDate?` (`DateOnly`), `ImagePath?`. **No `MediaPath` field exists.** +- `TrackDto` mirrors `TrackEntity`. Used only where DTO/entity separation is needed for serialisation; in practice both flow over the wire today. +- `PagingParameters` (base): `Page` (1-based, default 1), `PageSize` (default 20, capped at 100). The cap is a hard ceiling. +- `PagingParameters`: adds `OrderBy: Expression>?` and `IsDescending`. `Skip` is computed `(Page - 1) * PageSize`. Type-safe sort expressions — strings only at the API boundary. +- `PagedResult`: `Items`, `TotalCount`, `Page`, `PageSize`. Derived: `TotalPages`, `HasNextPage`, `HasPreviousPage`. Includes `PagedResult.From` for cross-type mapping. +- Convention: required reference fields use `required` modifier; optional reference fields are `?`. Don't relax `required` — it's the compile-time guarantee that prevents half-built entities reaching the database. +- This project also references `NetBlocks` only transitively (the actual NetBlocks types — `Result`, `ResultContainer`, `ApiResult`, `ApiResultDto` — come into the services and clients directly). + +**Do NOT include:** + +- "Potential future usage in DeepDrftContent" — content already uses these. +- Generic C# tutorials on `required` or `Expression`. + +--- + +### `DeepDrftCli/CLAUDE.md` + +**One-line purpose:** Local admin tool. Adds and lists tracks. Two modes: classic CLI (`add` / `list` / `help`) and a Terminal.Gui interactive interface (`gui`). Has direct access to both the SQL DB and the FileDatabase (it's not a network client). + +**Cover:** + +- Why this is allowed to bypass the API: it is a local single-user admin tool, run on the same machine as the databases. Browsers never get to bypass — the API is for them. Don't extend this pattern to network clients. +- Layout: `Program.cs`, `Services/CliService.cs`, `Services/GuiService.cs`, `Models/CliSettings.cs`, `environment/connections.json` (the actual config file — *not* `appsettings.json`). +- `CliSettings`: `ConnectionString` (SQLite path) + `VaultPath` (FileDatabase root). Loaded from `environment/connections.json` resolved against `AppDomain.CurrentDomain.BaseDirectory`. `environment/config.json` is also copied to output but currently unused — leave it as a placeholder. +- DI wiring (`Program.cs`): `DeepDrftContext` (SQLite), `FileDatabase` (singleton via `FromAsync`, blocking on init), `TrackRepository`, `DeepDrftWeb.Services.TrackService`, `AudioProcessor`, `DeepDrftContent.Services.TrackService`, `CliService`, `GuiService`. Logging is console-only. +- Mode dispatch: `gui` / `--gui` runs `GuiService.RunAsync`; everything else runs `CliService.RunAsync(args)`. +- CLI commands: + - `add [album] [genre] [release-date]` — classic positional. + - `add -i|--interactive [...defaults]` — interactive prompts, command-line args become defaults. + - `list` — formatted table. + - `help` / `--help` / `-h`. +- GUI mode: Terminal.Gui application with a custom dark/brand colour scheme, list of tracks, status pane, persistent hotkey legend, and an add-track dialog. +- Dual-database flow for `add` (this is the critical contract — do not skip): + 1. `DeepDrftContent.Services.TrackService.AddTrackFromWavAsync` processes the WAV, generates an entry GUID, stores audio in the `tracks` vault, returns a populated `TrackEntity` (not yet saved to SQL). + 2. `DeepDrftWeb.Services.TrackService.Create` saves the entity to SQLite and returns the persisted version with `Id` assigned. + 3. If step 1 succeeds and step 2 fails, the audio is orphaned in the vault. There is no compensating rollback today — flag this if it comes up in planning. +- Publishing: `PublishSingleFile=true`. `SelfContained` and `IncludeNativeLibrariesForSelfExtract` are commented out — current publish is framework-dependent single file. +- Release date format is `YYYY-MM-DD`. Only `.wav` is accepted. + +**Do NOT include:** + +- Any claim that `appsettings.json` is the config file. +- Coverage of all GuiService implementation details — just the entry point and that it exists. + +--- + +### `DeepDrftTests/CLAUDE.md` + +**One-line purpose:** NUnit test project. Covers the FileDatabase subsystem end-to-end (the only piece of the codebase with non-trivial test coverage). References `DeepDrftContent.Services`. + +**Cover:** + +- Layout: one test class per subsystem area — `FileDatabaseTests`, `MediaVaultTests`, `MediaVaultFactoryTests`, `IndexSystemTests`, `SimpleMediaTypeRegistryTests`, `UtilityTests`, `ModelTests`. `TestData.cs` holds shared fixtures (a real 16x16 PNG byte buffer; factory helpers for `ImageBinary` / `AudioBinary`; named constants under `TestKeys` / `TestFiles`). `environment/filedatabase.json` is a test-config file copied at build. +- Test isolation pattern (used by `FileDatabaseTests` and others): each test creates a unique directory under `Path.GetTempPath() + "DeepDrftTests/{Guid}"` in `[SetUp]` and best-effort deletes it in `[TearDown]`. New tests touching the filesystem must follow this pattern — do not write into the real `Database/Vaults` path. +- The tests are the load-bearing documentation of the FileDatabase's behaviour. When changing FileDatabase semantics (especially the swallow-and-return-null contract, the entry-key sanitisation regex, the vault-type round-trip on index, or the index-watcher reload), the tests are where intent is anchored. Update them in the same change. +- `TestData.CreateTestAudioBinary` deliberately reuses PNG bytes as a mock audio buffer — the FileDatabase does not parse audio content, so any byte buffer with valid metadata round-trips correctly. Real WAV parsing is exercised by the CLI / content host, not the FileDatabase tests. +- Run commands: + - All: `dotnet test DeepDrftTests/`. + - One class: `dotnet test DeepDrftTests/ --filter "ClassName=FileDatabaseTests"`. + - One method: `dotnet test DeepDrftTests/ --filter "Name=FileDatabase_CanBeCreatedAtSpecifiedLocation"`. +- What is NOT tested (be honest): `TrackService` (Web or Content), `TrackController` (Web or Content), `TrackClient` / `TrackMediaClient`, the audio player services, the dark-mode round-trip, the WAV offset service, the audio processor. Any planned work in those areas should consider whether tests need to land alongside. + +**Do NOT include:** + +- A claim that the test suite has "comprehensive coverage". It is comprehensive for FileDatabase and not present elsewhere. +- Code coverage instructions if they are not in active use — only document the commands actually run. + +--- + +## Order of operations for doc-keeper + +1. Rewrite `DeepDrftWeb/CLAUDE.md` and `DeepDrftWeb.Client/CLAUDE.md` first — they are the most-touched directories during ongoing UI work and have the highest drift. +2. Create `DeepDrftWeb.Services/CLAUDE.md` and `DeepDrftContent.Services/CLAUDE.md` — these are the biggest content gaps (no docs exist; most domain logic lives there). +3. Rewrite `DeepDrftContent/CLAUDE.md` — high drift, but the surface is small. +4. Rewrite `DeepDrftModels/CLAUDE.md` — small file, but the `MediaPath`→`EntryKey` correction matters everywhere it's wrong. +5. Rewrite `DeepDrftCli/CLAUDE.md` and `DeepDrftTests/CLAUDE.md` — lower priority; mostly polish + framework version. +6. Pause and check in with Daniel about `DeepDrftContent.Services/FileDatabase/README.md` — retire, keep, or replace.