# 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.

> **Status.** The root `CLAUDE.md` is current — it reflects the post-split ten-project solution, `net10.0`, and the dual-app topology. This file (`CONTEXT.md`) was the lagging document and §2 / §4 / §7 below have been brought back into line with the root `CLAUDE.md` as of 2026-06-06. Folder-level `CLAUDE.md` files are still being swept (`DOC_PLAN.md`); treat framework-version and structural claims in any *folder* `CLAUDE.md` not yet rewritten as potentially stale until that sweep lands.

---

## 1. What this project is

DeepDrftHome is the home + listening surface for **DeepDrft**, a two-person electronic music collective based in Charleston, SC (per `DeepDrftPublic.Client/Pages/Home.razor`). The product is, at minimum:

- A public-facing site (hero, about, "experience" features) at `DeepDrftPublic`.
- 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).
- A browser-based **CMS** (`DeepDrftManager`) for adding, editing, and deleting tracks — gated behind AuthBlocks login and the `Admin` role. This replaced the former `DeepDrftCli` Terminal.Gui admin tool, which has been retired.

The interesting engineering bet is the **dual-database split**: structured track metadata in PostgreSQL via EF Core, and binary media + per-vault indexes in a hand-rolled `FileDatabase` that lives on disk. The split is enforced through a dedicated authority host (`DeepDrftAPI`) so that the browser never reaches the database directly.

---

## 2. Solution shape (current)

Ten projects in `DeepDrftHome.sln`, plus an external `NetBlocks` referenced from `C:\lib\NetBlocks\`. The solution is split into **two independent Blazor applications** — the public site (`DeepDrftPublic`) and the CMS (`DeepDrftManager`) — both fronting a single dual-database authority host (`DeepDrftAPI`).

```
── Public application ──────────────────────────────────────────────────────
DeepDrftPublic              ASP.NET Core host. Blazor Web App (Server + WASM render
                            modes). Owns the browser-facing proxy controller for
                            api/track/* (metadata listing + audio streaming),
                            MudBlazor theme prerender, and TypeScript→JS audio interop
                            sources under Interop/. The public listening surface.
DeepDrftPublic.Client       Blazor WebAssembly assembly. All interactive public UI —
                            pages, the player stack, dark-mode plumbing, HTTP clients
                            for the backend. Consumed by DeepDrftPublic.

── CMS application ─────────────────────────────────────────────────────────
DeepDrftManager             ASP.NET Core host. Blazor Web App (InteractiveServer).
                            Hosts all CMS Razor components/pages (Components/Pages/Cms/,
                            Components/Pages/Tracks/, Components/Layout/CmsLayout.razor,
                            Components/Shared/ — inlined from the former DeepDrftCms RCL).
                            Gated by AuthBlocks login + hierarchical Admin role. All track
                            operations proxy via ICmsTrackService / CmsTrackService.

── Dual-database authority ─────────────────────────────────────────────────
DeepDrftAPI                 ASP.NET Core host. The single authority over both databases
                            (SQL metadata + FileDatabase binary). AuthBlocks API host
                            (registration, migration/seed, JWT endpoints). Seven track
                            endpoints (stream, vault write, upload, delete, paged list,
                            single metadata read, metadata update).
DeepDrftData                Class library. EF Core domain logic: DeepDrftContext,
                            TrackConfiguration, Migrations, TrackRepository, TrackService,
                            TrackManager. Consumed by DeepDrftAPI and tests.
DeepDrftContent             Class library. The FileDatabase implementation in full
                            (Models, Services, Utils, Abstractions, Constants),
                            WavOffsetService, AudioProcessor, content-side TrackService.
                            Consumed by hosts and tests.

── Shared ──────────────────────────────────────────────────────────────────
DeepDrftShared.Client       Razor Class Library. Shared Blazor components consumed by
                            BOTH DeepDrftPublic and DeepDrftManager (e.g. TrackCard,
                            TracksGallery) for consistency across public and admin surfaces.
DeepDrftModels              Shared contracts: TrackEntity, TrackDto, PagingParameters<T>,
                            PagedResult<T>, plus waveform DTOs. Every project references this.
DeepDrftTests               NUnit. Covers the FileDatabase, MediaVault, IndexSystem,
                            MediaVaultFactory, SimpleMediaTypeRegistry, utility code, model
                            behaviour, and the waveform loudness algorithm. References
                            DeepDrftContent.

NetBlocks (external)        Result patterns: Result, ResultContainer<T>, ApiResult<T>,
                            ApiResultDto<T>. Referenced via absolute path.
```

**Naming history (for readers of older docs/commits):** `DeepDrftWeb` → `DeepDrftPublic`, `DeepDrftWeb.Client` → `DeepDrftPublic.Client`, `DeepDrftWeb.Services` → `DeepDrftData`, `DeepDrftContent.Services` → `DeepDrftContent` (the host that previously owned the binary API is gone; its proxy duties moved into `DeepDrftPublic`, its authority duties into `DeepDrftAPI`). `DeepDrftCli` and the `DeepDrftCms` RCL have both been removed — the CLI retired in favour of the CMS, and the CMS RCL was inlined into `DeepDrftManager`.

**Subdomain topology (deployment):** `deepdrft.com` (public) and `manage.deepdrft.com` (CMS), behind nginx. CD infrastructure (Gitea workflows + installer scripts + systemd/nginx templates) has landed — see `COMPLETED.md` "Deployment Infrastructure."

---

## 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):

- `docs: update CLAUDE.md files to reflect Range header seek, remove WavOffsetService references`
- `chore: remove WavOffsetService and ?offset= seek path, superseded by Range header (Phase 4.1)`
- `feat: replace ?offset= seek with HTTP Range streaming across API, proxy, and client`
- `refactor: extract StreamNowButton component shared by hero and nav menu`
- (earlier: WaveformSeeker improvements, play-state icon normalization, CMS build-out, the two-app split, deployment infrastructure)

Observations:

1. **The big structural moves have landed.** Since the last revision of this doc, three large initiatives shipped: the **two-app split** (public/CMS separation with `DeepDrftAPI` as the dual-database authority), the **browser CMS** replacing the CLI (auth via AuthBlocks, stealth-routed `/cms/*`, full add/list/edit/delete parity), and **CD infrastructure** (Gitea workflows + host installer + systemd/nginx templates). The substrate is no longer the frontier — the product and presentation layers are.
2. **Phase 4 streaming work is complete.** HTTP Range header seek (`Range: bytes=X-`) is now the sole seek mechanism; `WavOffsetService` and the `?offset=` path have been removed (Phase 4.1, merged 2026-06-09). `StreamDecoder.reinitializeForRangeContinuation` handles Range continuations by retaining the parsed WAV header. The streaming substrate is solid.
3. **The embeddable iframe player has landed** (commit `c83b132`, 2026-06-07). The presentation layer now includes a chrome-free single-track embed surface for off-site use, completing the Phase 4 feature set.
4. **The "Track Gallery" is still the only real public content page.** `/tracks` is the working listening surface; `/` is the (reskinned) marketing home. Nav (in `Layout/Pages.cs`) is still essentially `Home` + `Track Gallery`. The CMS adds admin surfaces under `/cms` but those are not public.
5. **The metadata/streaming surface is consolidated on `DeepDrftAPI`.** It exposes seven track endpoints (stream, vault write, upload, delete, paged list, single-metadata read, metadata update) plus waveform endpoints. `DeepDrftPublic` is a thin browser-facing proxy in front of it; the browser never reaches `DeepDrftAPI` or the databases directly.

---

## 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.
- **Web upload — landed.** *(Historical note: this was a "likely direction" when the CLI was the only producer. It has since shipped.)* The CMS (`DeepDrftManager`) now produces tracks via `POST api/track/upload` on `DeepDrftAPI`, proxied through the auth-gated CMS surface. The CLI has been retired. The dual-write rollback gap (`PLAN.md §4.3`) still stands.
- **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<TrackEntity>` and differ only at the rendering layer.
- **Result types from NetBlocks.** Services return `Result`, `ResultContainer<T>`, or `ApiResult<T>` 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)

Two layers of drift remain. The root `CLAUDE.md` and this `CONTEXT.md` are current; the lag is now in **folder-level `CLAUDE.md` files** and the in-tree `FileDatabase` README. `DOC_PLAN.md` holds the per-folder rewrite briefs, but note that `DOC_PLAN.md` itself was authored against the *pre-split* project names (2026-05-16) and is partly superseded — see the warning at the end of this section.

**Project-rename drift (the big one).** The two-app split renamed or removed most projects. Any folder `CLAUDE.md` still using the old names is wrong at the structural level, not just the framework-version level:
- `DeepDrftWeb` → `DeepDrftPublic`; `DeepDrftWeb.Client` → `DeepDrftPublic.Client`; `DeepDrftWeb.Services` → `DeepDrftData`.
- `DeepDrftContent.Services` (class library) is now just `DeepDrftContent`; the old `DeepDrftContent` *host* is gone — binary-API duties split between the `DeepDrftPublic` proxy and the `DeepDrftAPI` authority.
- `DeepDrftCli` and the `DeepDrftCms` RCL are **deleted**. Any `CLAUDE.md` for them should be removed, not rewritten.

**Known content drift to correct in the sweep:**
- Framework version: any folder `CLAUDE.md` still saying ".NET 9" / "ASP.NET Core 9.0" — reality is `net10.0` across the board.
- `TrackEntity.MediaPath` references (notably the `FileDatabase/README.md`) — the field is `EntryKey`, column `entry_key`.
- The `FileDatabase/README.md` refers to `ImageDirectoryVault` (the type is `ImageVault`) and a "FileDatabase.csproj (.NET 9.0)" that no longer exists (FileDatabase is a subdirectory of `DeepDrftContent`).
- `DeepDrftData` and `DeepDrftContent` are where most domain logic lives and are the highest-value targets for accurate `CLAUDE.md` coverage.

**Already corrected (no longer stale):**
- `DeepDrftPublic.Client/CLAUDE.md` was rewritten in commit `9110b4b` and reflects the current player stack, `PlaybackIcons`/`PlayStateIcon`, and the post-split structure.

> **`DOC_PLAN.md` caveat.** `DOC_PLAN.md` predates the two-app split — its per-folder briefs reference `DeepDrftWeb*`, `DeepDrftCli`, and a SQLite backend (now PostgreSQL). Treat its *intent* (lead-with-truth, cross-reference root, no docs for build output) as still valid, but its *project list and per-folder details* need reconciling against the current ten-project solution before doc-keeper executes against it. Flag to Daniel whether to refresh `DOC_PLAN.md` first or let doc-keeper work from the root `CLAUDE.md` directly.
