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