Files
deepdrft/CLAUDE.md
T

11 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code when working with code in this repository.

Architecture Overview

DeepDrftHome is a net10.0 solution consisting of ten projects implementing a dual-database media management system for the DeepDrft electronic music collective, split into two independent Blazor applications: the public site (DeepDrftPublic) and the CMS (DeepDrftManager).

Core Projects

  • DeepDrftPublic: ASP.NET Core host. Blazor Web App with Server + WASM render modes. Pure Blazor host with no data layer; fetches track metadata from DeepDrftAPI via HTTP. Owns MudBlazor theme prerender and TypeScript→JS audio interop. Public-facing site for listeners.
  • DeepDrftPublic.Client: Blazor WebAssembly assembly. All interactive UI (pages, player stack, dark-mode plumbing, HTTP clients for both backends). Consumed by the public site.
  • DeepDrftManager: ASP.NET Core host. Blazor Web App with server-rendered InteractiveServer render mode. Hosts all CMS Razor components and pages under Components/Pages/Cms/, Components/Pages/Tracks/, Components/Layout/CmsLayout.razor, and Components/Shared/ (all inlined from the former DeepDrftCms RCL). Gated by AuthBlocks login and hierarchical Admin role authorization. All track operations (upload, metadata read/write, delete) are HTTP proxies via ICmsTrackService / CmsTrackService injected directly into Blazor components; no in-process data layer.
  • DeepDrftShared.Client: Razor Class Library. Shared Blazor components consumed by both DeepDrftPublic and DeepDrftManager for consistency across public and admin surfaces.
  • DeepDrftData: Class library. EF Core domain logic: DeepDrftContext, TrackConfiguration, Migrations, TrackRepository, TrackService, TrackManager. Consumed by DeepDrftAPI and tests.
  • DeepDrftAPI: ASP.NET Core host. Dual-database authority (SQL metadata + FileDatabase binary). AuthBlocks API host (owns registration, migration/seed, JWT endpoints). Seven track endpoints: GET api/track/{id} unauthenticated streaming; PUT api/track/{id} vault write (ApiKey); POST api/track/upload upload + SQL persist (ApiKey); DELETE api/track/{id:long} SQL delete + vault remove (ApiKey); GET api/track/page paged metadata list (unauthenticated); GET api/track/meta/{id:long} single metadata (ApiKey); PUT api/track/meta/{id:long} metadata update (ApiKey).
  • DeepDrftContent: Class library. The FileDatabase implementation in full (Models, Services, Utils, Abstractions, Constants), WavOffsetService, AudioProcessor, content-side TrackService. Consumed by hosts and tests.
  • DeepDrftModels: Shared contracts. TrackEntity, TrackDto, PagingParameters<T>, PagedResult<T>. Every project references this.
  • DeepDrftTests: NUnit test suite. Comprehensive FileDatabase tests (vault creation, media storage, indexing, factory patterns, utilities). Integration-focused with temp-directory test isolation.

External: NetBlocks (absolute path C:\lib\NetBlocks\). Provides Result, ResultContainer<T>, ApiResult<T>, ApiResultDto<T>.

Database Architecture

Dual-database approach — browser never reaches storage directly:

DeepDrftPublic.Client (WASM)
    ├── HttpClient "DeepDrft.API"     ──►  DeepDrftAPI         ──►  EF Core / SQLite (metadata)
    └── HttpClient "DeepDrft.Content" ──►  DeepDrftAPI         ──►  FileDatabase / disk (binary)
  1. SQL Database (SQLite): Metadata and track info via Entity Framework

    • Location: ../Database/deepdrft.db
    • Entity: TrackEntity with Id, EntryKey, TrackName, Artist, Album?, Genre?, ReleaseDate?, ImagePath?
    • Context: DeepDrftContext in DeepDrftData
  2. FileDatabase: Custom file-based binary storage system

    • Location: ../Database/Vaults (configurable via filedatabase.json)
    • Root contains typed MediaVaults (Media, Image, Audio)
    • Each vault has a JSON index file listing entries + per-entry metadata
    • Entries are user-supplied strings sanitized to [a-zA-Z0-9-] + file extension
    • Binary hierarchy: FileBinaryMediaBinary (+ Extension/MIME) → AudioBinary (+ Duration/Bitrate) | ImageBinary (+ AspectRatio)
    • Error-handling philosophy: public operations swallow exceptions and return null/false — callers must check return values, not catch.

Key Architectural Decisions

Service projects vs. host projects

The split between host projects (DeepDrftPublic, DeepDrftManager, DeepDrftContent) and *.Services class libraries (e.g., DeepDrftData, DeepDrftContent.Services) is deliberate: hosts own HTTP surface (endpoints/controllers exposed to network), config, DI wiring, and UI components; *.Services are plain class libraries holding domain logic. This separation allows multiple hosts to consume the same service implementations. Within a host, domain logic like CMS mutations lives in host-internal service classes (e.g., CmsTrackService in DeepDrftManager/Services/), injected directly into Blazor components with no in-process HTTP roundtrip.

New domain logic goes in *.Services (shared class libraries) for logic consumed by multiple hosts, or in host-internal service classes (e.g., Services/) for host-specific logic — not in controllers, which should be thin HTTP boundaries.

TrackEntity is a join, not a content blob

TrackEntity holds only metadata. The link to binary content is EntryKey (string) — the entry id inside the tracks vault in FileDatabase. Dual-database add flow:

  1. DeepDrftContent.TrackService.AddTrackFromWavAsync processes WAV, generates entry GUID, stores audio in vault, returns unpersisted TrackEntity.
  2. DeepDrftAPI.Services.UnifiedTrackService.UploadAsync persists the entity to SQL via DeepDrftData.TrackManager and returns the persisted entity with Id.

If step 1 succeeds and step 2 fails, audio is orphaned in the vault (no rollback today).

Streaming-first audio playback

The player is not fetch-then-play:

  1. Client calls GET api/track/{id} on DeepDrftContent and receives WAV bytes as a stream (HttpCompletionOption.ResponseHeadersRead).
  2. StreamingAudioPlayerService reads in adaptive 1664 KB chunks, pushes each via AudioInteropService.processStreamingChunk.
  3. TypeScript StreamDecoder parses WAV header, decodes chunks to AudioBuffers. PlaybackScheduler schedules them on a Web Audio graph.
  4. Playback starts as soon as a min buffer is queued; UI duration from parsed header (not waiting for full file).
  5. Seek beyond buffer: if seek target is past what's decoded, client issues GET api/track/{id}?offset={byteOffset}. Server's WavOffsetService block-aligns offset, synthesises a fresh 44-byte WAV header, streams [new header][data from offset]. Player tears down and re-initialises decoder for the new stream.

Keep this seam clean — it is the most architecturally load-bearing part of the playback path.

Theming and dark mode

  • MudBlazor is the UI framework. Light and dark palettes (bespoke "Charleston in the Day" / "Lowcountry Summer Nights") defined inline in MainLayout.razor.
  • Dark mode toggles via cookie (darkMode, 365 days). Client-side via JS interop.
  • During server prerender, DarkModeService (in DeepDrftPublic) reads the cookie and seeds DarkModeSettings.IsDarkMode, which carries into WASM render via PersistentComponentState. Avoids "wrong theme flash" on initial paint.
  • DarkModeSettings lives in DeepDrftPublic.Client.Common (consumed by both server prerender and client components).
  • Typography: Google Fonts (Bodoni Moda, Cormorant, DM Sans). Hand-rolled gas-lamp icon (lit/unlit) lives in DDIcons.cs.

TypeScript interop, not raw JS

Audio interop authored in TypeScript under DeepDrftPublic/Interop/audio/, compiled to wwwroot/js/audio/ via Microsoft.TypeScript.MSBuild. One module per responsibility (AudioContextManager, StreamDecoder, PlaybackScheduler, SpectrumAnalyzer, AudioPlayer), plus index.ts exposing window.DeepDrftAudio. tsconfig.json is not copied to output. In dev, raw .ts served from /Interop/ for source-map debugging.

Development Commands

Build and Test

# Build entire solution
dotnet build DeepDrftHome.sln

# Run all tests
dotnet test DeepDrftTests/

# Run specific test class
dotnet test DeepDrftTests/ --filter "ClassName=FileDatabaseTests"

Running Applications

# Run main web application
dotnet run --project DeepDrftPublic

# Run API (dual-database authority and AuthBlocks host)
dotnet run --project DeepDrftAPI

Entity Framework (SQL Database)

# Add migration (from solution root)
dotnet ef migrations add MigrationName --project DeepDrftData --startup-project DeepDrftPublic

# Update database
dotnet ef database update --project DeepDrftData --startup-project DeepDrftPublic

Key Configuration Files

All projects load secrets via CredentialTools.ResolvePathOrThrow() from gitignored environment/ files:

  • DeepDrftPublic/appsettings.json: Logging and URL config (DeepDrftAPI base URL). No secrets file dependency.
  • DeepDrftManager/appsettings.json: Logging and URL config. Secrets loaded from environment/api.json (DeepDrftAPI base URL and API key).
  • DeepDrftAPI/appsettings.json: Logging and hosting config. Secrets loaded from environment/filedatabase.json (FileDatabase vault path), environment/apikey.json (API key), environment/connections.json (SQL and Auth connection strings), environment/authblocks.json (AuthBlocks JWT/email/admin creds).

Folder-Level Guidance

Folder-level CLAUDE.md files provide specifics on structure, patterns, and commands for each project. Start with the project's folder CLAUDE.md when entering that directory. The root CLAUDE.md here sets the architectural context; folder files answer "what's in this folder and how do I work here?"

Important Patterns

Result Pattern (NetBlocks)

Services return Result, ResultContainer<T>, or ApiResult<T> rather than throwing for expected failure modes. Catch at service boundary, surface via result.

Error Swallowing in FileDatabase

Public Load* / Register* operations in FileDatabase swallow exceptions and return null / false. Callers must check return values. This matches the TypeScript original and is load-bearing — do not change without a design pass.

Pagination

Services build PagingParameters<T> with an OrderBy expression. Switch in the service maps a string sort column to the expression. New sort columns extend this switch. Nulls sort to end (padded sentinel strings / DateOnly.MaxValue).

External Dependencies

  • Entity Framework Core 10.0.1 (SQLite)
  • MudBlazor 8.15.0
  • NUnit 4.4.0
  • NetBlocks (Result patterns)
  • Microsoft.TypeScript.MSBuild (TS compilation)

See individual project files for detailed dependency lists and versions.