# COMPLETED.md — DeepDrftHome
Archive of items that have moved out of `PLAN.md` and `CMS-PLAN.md`. Per `CONTEXT.md §6`, completed items are moved here rather than deleted. Each entry preserves the original "What / Why / Shape" body so this file reads as a decision record, not just an outcome list.
Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CMS-PLAN.md` themes) when there are enough entries to warrant it.
---
## Phase 2 — Product surface: player and theming
**Status:** Desktop AudioPlayerBar redesign landed on 2026-06-04.
### Desktop AudioPlayerBar — migrate to MudBlazor theme system
**Landed 2026-06-04.**
Desktop branch of `AudioPlayerBar.razor` migrated off dead CSS palette tokens (`--charleston-*`, `--lowcountry-*`, `--deepdrft-theme-*` — none of which are defined in the live stylesheet) onto the active MudBlazor theme system. This was simultaneously a bug fix (player styling broken against the current palette) and a structural redesign.
**Structural changes:**
- `.player-backdrop` div replaced with `MudPaper Elevation="8"` — surface colour now derives from `--mud-palette-surface` via the live theme, and flips automatically with dark mode (off-white in light, navy in dark).
- Three new zone sub-components extracted: `PlayerTransportZone` (left transport cluster), `PlayerSeekZone` (centre seek+spectrum, owns the seek pointer-handler logic), `PlayerWindowControls` (minimize/close buttons). These remove duplication (seek handlers no longer inline-copied) and name the layout zones explicitly.
- `MudStack` replaces all raw `
` throughout the desktop branch and sub-components (`PlayerControls`, `VolumeControls`, `TimestampLabel`).
- `SpectrumVisualizer` bar colour fixed: `var(--mud-palette-primary)` replaces the undefined `--deepdrft-theme-secondary` token.
- Minimized dock replaced with `MudFab Color="Color.Primary"` — rounded button picking up themed primary colour with no hand-rolled gradient.
- `AudioPlayerBar.razor.css` shrunk from ~176 lines (mostly dead-token theming) to ~74 lines (geometry and positioning only).
**Scope:**
- Desktop branch only (`@if (_isDesktop)`). Mobile branch unchanged by design.
- Build clean: 0 errors, 0 new warnings.
**Notes for future work:**
- Mobile branch is also currently broken against the live palette for the same reason (spectrum bars + shared dead-token rules have no colour). A companion migration for mobile is implied but out of scope for this task — marked for future Phase 2 work.
---
## Deployment Infrastructure
**Status:** CD pipeline infrastructure landed on 2026-06-04.
### CD pipeline infrastructure (Gitea workflows + remote host installer)
**Landed 2026-06-04.**
Continuous deployment infrastructure for DeepDrftHome dual-app deployment. Consists of four Gitea workflows (`.gitea/workflows/`) — `deploy-public.yml`, `deploy-manager.yml`, `deploy-api.yml`, `package-install.yml` — all triggered by `dev` branch (beta) and `master` branch (prod) pushes, path-filtered to deploy only on changes to the affected service and its dependencies. Five installer scripts (`deploy/`) — `install.sh` (one-shot host provisioner), `bootstrap.sh` (curl-and-run entry point), `ssh-wrapper.sh` (forced-command dispatcher), three `deploy-*.sh` per-service deployment scripts — plus systemd service templates (`deploy/systemd/`) and nginx vhost templates (`deploy/nginx/`), and credential template files (`deploy/credentials/`). One auxiliary setup script `setup-step10-creds.sh` for interactive credential entry on the host. The installer creates users, directories, systemd services, PostgreSQL databases, nginx vhosts, and loads credential files via systemd `LoadCredential=` into the credential sandbox. The deploy scripts swap binaries in-place, run the EF migrations bundle for the API metadata database, and restart services without touching persistent vault data. Enables hands-off pushes to beta and prod with full CI/CD orchestration.
---
## Two-app split Wave 2 — Phase 4
**Status:** Phase 4 (project rename) landed on 2026-05-19.
### Phase 4 — Two-app split: rename `DeepDrftWeb` → `DeepDrftPublic`
**Landed 2026-05-19.**
Renamed `DeepDrftWeb` to `DeepDrftPublic` and `DeepDrftWeb.Client` to `DeepDrftPublic.Client` across all project files, `.csproj` files, namespace declarations, using directives, solution file, and deploy scripts. Updated all references in `CLAUDE.md` agent guidance to reflect the new names. Also updated prior references to `DeepDrftWeb.Services` to `DeepDrftData` to align with the Phase 2 library rename. The solution builds cleanly with all endpoints functional.
---
## CMS Wave 1 — Auth + scaffolding + parity
**Status:** All sub-items landed on 2026-05-18.
### W1.0 `DeepDrftContext` Postgres migration
**Landed 2026-05-18.**
Rewrite all existing EF Core migrations from SQLite to PostgreSQL. Update the `DeepDrftWeb` and `DeepDrftCli` connection strings in config. Migrate any existing data from `../Database/deepdrft.db` to Postgres. Verify the existing `api/track/page` and `api/track/{id}` endpoints function against the new backend. This is a prerequisite for W1.2 (which also runs migrations for AuthDbContext against the same Postgres instance).
### W1.1 `DeepDrftCms` RCL skeleton
**Landed 2026-05-18.**
Project created, added to solution, referenced from `DeepDrftWeb`. Empty `Pages/Cms/Index.razor` mounted at `/cms` returning a "CMS — under construction" placeholder, proving the mount works.
### CMS RCL inlined into `DeepDrftManager`
**Landed 2026-05-21.**
The `DeepDrftCms` Razor Class Library has been inlined into `DeepDrftManager` and the standalone project deleted from the solution. All Razor pages, components, and layouts (CmsLayout, DeleteTrackDialog, TrackList, TrackNew, TrackEdit, and the CMS index page) now live directly in `DeepDrftManager/Components/Pages/Cms/`, `DeepDrftManager/Components/Pages/Tracks/`, `DeepDrftManager/Components/Layout/`, and `DeepDrftManager/Components/Shared/`. The `DeepDrftManager.csproj` no longer references the now-deleted `DeepDrftCms` project. `DeepDrftManager/Program.cs` no longer calls `AddCmsServices()` or references the CMS assembly. Solution builds cleanly with all CMS endpoints and pages functional.
### W1.2 AuthBlocks integration + login
**Landed 2026-05-18.**
Reference `Cerebellum.AuthBlocks`, `Cerebellum.AuthBlocks.Web`, `Cerebellum.AuthBlocks.Models` from `DeepDrftWeb`; reference `Cerebellum.AuthBlocks.Web` from `DeepDrftWeb.Client`. Call `AddAuthBlocks(...)` in `Program.cs` with JWT secret/issuer/audience, Mailtrap email connection, Postgres connection string, and `AdminUserSettings` from `environment/authblocks.json`. Call `await app.Services.UseAuthBlocksStartupAsync()` post-build. Call `app.MapAuthBlocks()` to mount `/api/auth/*` routes. Add the `AuthBlocksWeb` assembly to `AddAdditionalAssemblies` so the bundled `/account/login` and `/account/logout` pages resolve. In `DeepDrftWeb.Client.Startup`, call `AuthBlocksWeb.Client.Startup.ConfigureServices(builder.Services)` for the prerender→WASM auth-state bridge. Add `CreatedByUserId : long?` column to `TrackEntity` via a nullable migration. Provision local Postgres (docker-compose) and document the dev setup. Includes `CmsStealthRoutingHandler` — a custom `IAuthorizationMiddlewareResultHandler` that returns 404 for any `/cms/*` hit that fails authorization, honouring the stealth-routing constraint: unauthorized access to admin routes returns 404, not 401 or redirect.
---
## CMS Wave 1 (legacy section header for reference)
**Status:** All sub-items landed on 2026-05-18.
Goal was: A logged-in collective member can do everything the CLI does today, from a browser.
### W1.3 CMS track list
**Landed in CMS Wave 3.**
`/cms/tracks` consuming the same `GET api/track/page` endpoint as the public gallery. Different rendering (table with admin affordances), same VM. No new SQL endpoint.
### W1.4 CMS upload endpoint + add page
**Landed in CMS Wave 3.**
New `POST api/cms/track` on `DeepDrftWeb` (auth-gated, see §5 for the transport decision). `/cms/tracks/new` page wires `InputFile` to the endpoint. Note: Option B is confirmed — this requires a new `POST api/track/upload` endpoint on `DeepDrftContent` (raw WAV in, unpersisted `TrackEntity` out) in addition to the CMS page and controller.
### W1.5 CMS delete endpoint + delete UI
**Landed in CMS Wave 3.**
New `DELETE api/cms/track/{id}` on `DeepDrftWeb`. Removes the SQL row and the vault entry; logs orphans if vault delete fails after SQL delete succeeds. Delete button + confirmation in the list and detail pages.
### W1.6 CMS edit endpoint + edit page
**Landed in CMS Wave 3.**
New `PUT api/cms/track/{id}` (metadata only — no binary replacement in Wave 1). `/cms/tracks/{id}` page.
---
## Phase 2 — Product surface: gallery, browsing, ingestion
### 2.4 Web-side track upload
**Landed in CMS Wave 1 (subsumed by `CMS-PLAN.md`).**
The CLI is the only producer of tracks today. A web upload UI would pair with `TrackService.AddTrackFromWavAsync` and the existing `PUT api/track/{id}` (already `[ApiKeyAuthorize]`-protected).
- **Why it matters:** Lowers the barrier to adding content. The collective can publish without shell access to the host.
- **Shape:**
- New page or modal on the web client, drag-and-drop file input.
- Upload streams to a `POST` endpoint on `DeepDrftWeb` (not `DeepDrftContent` — the web host orchestrates the dual-write, then forwards bytes to content with the API key it already holds).
- Authentication: this is the first user-facing action that needs to be gated. A new question — see open question below.
- **Prerequisite:** **Authentication model for the web side**. Currently the site has no user concept. Cookie-with-shared-password? OAuth? Per-collective-member account? Decide before building the UI.
- **Open question:** Same as above. This may also bring forward a wider session/identity decision that other features (favourites, listening history) will need eventually.
- **Constraint:** Today's dual-write has no compensating rollback — if content-side succeeds and SQL-side fails, the audio is orphaned in the vault. The CLI inherits this; pushing this onto a web upload increases the rate at which orphans can occur. A simple `DeadLetterLog` of orphaned `entryKey`s (suggested in the audit) becomes more pressing once the web upload exists.
---
## Phase 0 — Wireframe-driven home page redesign
**Status:** All sub-items landed on 2026-05-17.
A design wireframe (`deepdrft-wireframe.html` at the project root) is the source of truth for a full visual reskin of the public site. The current `Home.razor` is a MudPaper/MudGrid composition with a generic "purple-tint" feature card aesthetic that doesn't match the collective's intended voice. The wireframe replaces it with a layout-first, editorial design: 50/50 hero, frosted-glass nav, dark feature band, green origin/connect split, navy CTA banner with ghost-watermark, and an italic-serif accent treatment throughout.
Scope here is **the home page and the chrome that wraps it** (nav, layout container, theme palette, font loading). The track gallery (`TracksView.razor`), the audio player dock (`AudioPlayerBar.razor`), and the FileDatabase/streaming substrate are **out of scope** for Phase 0 — they keep working through the existing MudBlazor theme, which is being recoloured under them. The "Now Playing" card in the hero is a *new* surface that reads from the existing `IPlayerService` cascade; it is a view onto the player, not a replacement for the dock.
Phase 0 sub-items decompose into worktree-sized tracks. 0.1 is the foundation everything else inherits — land it first. 0.2–0.4 can proceed in parallel against that foundation. 0.5 is a follow-on tuning pass once the light theme is in.
### 0.1 Light palette + font system
- **What:** Replace the "Charleston in the Day" `PaletteLight` in `DeepDrftWeb.Client/Layout/MainLayout.razor` with the wireframe palette (`--white #FAFAF8`, `--navy #0D1B2A`, `--green #1A3C34`, `--green-accent #3D7A68`, `--muted #8A9BB0`), expressed as MudBlazor `PaletteLight` properties. Update the corresponding CSS custom properties in `DeepDrftWeb/wwwroot/styles/deepdrft-styles.css` so the `deepdrft-*` utility classes still resolve. Add `Geist Mono` to the Google Fonts `` in `DeepDrftWeb/Components/App.razor`. Upgrade the existing `Cormorant` link to `Cormorant Garamond` with the italic + 300/400/600 weight set used by the wireframe. Remove the `Bodoni Moda` link (and its `--font-hero` reference) if no remaining surface uses it.
- **Why it matters:** Every other Phase 0 sub-item consumes these tokens. Fonts and palette landing first means 0.2/0.3/0.4 can render at intended fidelity from the moment they're built, not approximate-then-correct. The font swap is also the only Phase 0 change that affects HTML served by the host project (`App.razor`), so isolating it cleanly keeps the render-mode seam clear.
- **Shape:**
- MudBlazor palette mapping (light): `Primary = navy`, `Secondary = green`, `Tertiary = green-accent`, `Background = white`, `Surface = white`, `AppbarBackground = "rgba(250,250,248,0.88)"`, `AppbarText = navy`, `TextPrimary = navy`, `TextSecondary = muted`, `Divider = "rgba(13,27,42,0.10)"`, `LinesDefault / TableLines` to match. Semantic colours (`Info/Success/Warning/Error`) stay at MudBlazor defaults.
- Typography block (light): `H1`–`H6` and a new wireframe-specific display class use `Cormorant Garamond`; `Button` / `Default` keep `DM Sans`; introduce a `Subtitle1` / `Caption` family pointing at `Geist Mono` for label/eyebrow text.
- CSS variables: rename or alias the existing `--deepdrft-primary/--deepdrft-secondary/etc.` to the wireframe palette in `:root`. Add `--font-mono: "Geist Mono", monospace;` and update `--font-hero` / `--font-headers` to `"Cormorant Garamond", serif`. Where the legacy palette has no wireframe equivalent (e.g. `--deepdrft-quaternary` warm gold), prefer mapping it to the closest wireframe colour rather than inventing a new one — the goal is convergence on the new vocabulary, not coexistence.
- Font loading: a single Google Fonts link, ideally one combined request with `family=Cormorant+Garamond:ital,wght@…&family=Geist+Mono:wght@…&family=DM+Sans:…`. One round-trip, three families.
- **Prerequisite:** None — this is the foundation.
- **Constraint:** The dark palette ("Lowcountry Summer Nights") must stay functional after this change even if visually mismatched — 0.5 is the dedicated pass for re-harmonising it. Do not edit the dark palette in 0.1. The dark-mode cookie + `PersistentComponentState` round-trip described in `CLAUDE.md` must be preserved unchanged.
### 0.2 Frosted-glass top nav
- **What:** Replace the current MudBlazor `MudAppBar`-based `DeepDrftMenu.razor` chrome (logo + nav stack + dark-mode toggle, default Material elevation) with the wireframe's fixed frosted-glass nav: 88% opacity off-white background, `backdrop-filter: blur(18px)`, 1px navy-alpha bottom border, no elevation shadow, navy-on-white "Stream Now" CTA pinned right, nav links in Geist Mono uppercase with the muted-to-navy hover transition.
- **Why it matters:** The nav sits across every page, so its visual language sets expectations for the rest of the site. The Material elevation + dropdown menu pattern is the strongest "this is a stock MudBlazor app" tell currently; replacing it is the single largest perceived-quality move of Phase 0.
- **Shape:**
- Keep `DeepDrftMenu.razor` as the file (the existing render-mode wiring and viewport-subscription mobile branch are reused) — rewrite the markup inside it.
- Wrap a styled `