# ChicoryTV SPA Scaffold Build Integration Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build issue #78: a Vite React TypeScript SPA in `web/`, mounted at `/app`, included in ASP.NET publish output, Docker image builds, and Gitea CI.

**Architecture:** Keep the existing Blazor UI as the root fallback and mount the new SPA under a dedicated `/app` branch. Build `web/` to `ErsatzTV/wwwroot/app` so ASP.NET static file serving and Docker publish both carry the compiled SPA without changing `/iptv/*` or existing API routes.

**Tech Stack:** .NET 10 ASP.NET Core, Vite, React, TypeScript, npm, NUnit static integration tests.

---

### Task 1: Lock the Hosting Contract

**Files:**
- Create: `ErsatzTV.Tests/StartupSpaHostingTests.cs`
- Modify: `ErsatzTV/Startup.cs`

- [ ] Add tests that assert `Startup.cs` has a dedicated `/app` branch, serves `ErsatzTV/wwwroot/app`, falls back to `index.html`, and excludes `/app` from the Blazor branch.
- [ ] Run `dotnet test ErsatzTV.Tests/ErsatzTV.Tests.csproj --filter FullyQualifiedName~StartupSpaHostingTests -v minimal` and confirm the tests fail because the branch is missing.
- [ ] Add the `/app` static file branch before the non-IPTV Blazor branch.
- [ ] Re-run the targeted test and confirm it passes.

### Task 2: Scaffold the SPA

**Files:**
- Create: `web/package.json`
- Create: `web/package-lock.json`
- Create: `web/tsconfig.json`
- Create: `web/tsconfig.node.json`
- Create: `web/vite.config.ts`
- Create: `web/index.html`
- Create: `web/src/main.tsx`
- Create: `web/src/App.tsx`
- Create: `web/src/App.test.tsx`
- Create: `web/src/setupTests.ts`
- Create: `web/src/vite-env.d.ts`

- [ ] Add a React Testing Library test that expects the ChicoryTV placeholder shell to render and the design-system stylesheet to be imported.
- [ ] Run `npm test -- --run` in `web/` and confirm it fails before the app exists.
- [ ] Add the Vite React TypeScript app with `base: "/app/"`, dev proxy entries for `/api`, `/iptv`, and `/artwork`, and output directory `../ErsatzTV/wwwroot/app`.
- [ ] Import `../../design-system/styles.css` from the SPA entrypoint and render a compact placeholder shell.
- [ ] Run `npm ci`, `npm test -- --run`, `npm run lint`, `npm run typecheck`, and `npm run build`.

### Task 3: Wire Build and CI

**Files:**
- Modify: `.gitea/workflows/docker-build.yml`
- Modify: `docker/Dockerfile`
- Modify: `.gitignore`

- [ ] Add CI Node setup and `npm ci`, `npm run lint`, `npm run typecheck`, `npm run build` before the .NET build.
- [ ] Add Docker Node build layers that copy `web/` and `design-system/`, install npm dependencies, and build the SPA before `dotnet publish`.
- [ ] Ignore generated `ErsatzTV/wwwroot/app/` and `web/dist/` while keeping source and lockfiles tracked.
- [ ] Run the targeted .NET test, the web checks, and `dotnet build ErsatzTV/ErsatzTV.csproj --no-restore`.

### Task 4: Finish Issue #78

**Files:**
- Modify as needed based on verification.

- [ ] Run `git status --short` and review the diff.
- [ ] Use the `done` skill for issue #78 before claiming completion.
