# Local development How to run the site on your own machine and how the tests run. The app is one container (`eb-blazor`) that reads its content from a second container running PocketBase (`eb-pocketbase`); both come up together as a compose stack. For deploying a built image to Unraid, see `deployment.md`. For the architecture reasoning, see `architektur.md`. --- ## The three compose files There is one stack, described by a base file plus two overlays. You never edit the base for a local run — you layer an overlay on top of it. | File | Role | |---|---| | `compose.yaml` | Base, production-shaped: both services, **no** host ports, PocketBase data on the Unraid bind mount. Also holds the fixed project name `eb-stack`. | | `compose.dev.yaml` | Dev overlay: adds host ports (5000, 8090), builds the web image from local source instead of pulling it, swaps the Unraid bind mount for a throwaway volume, and seeds a local PocketBase superuser. | | `compose.test.yaml` | Test overlay: used **only** by the integration tests. Drops the fixed host port and container names so a test run does not collide with a running dev stack. | > **Project name.** `name: eb-stack` is set once, in the base. The overlays > deliberately don't set their own `name:` — a competing `name:` across `-f` > files resolves depending on load order (and the CLI and Rider load them in > opposite orders), so keeping it in one place avoids surprises. The tests > override it with `-p eb-test-stack`. --- ## Running the dev stack ### From Rider (one click) The run configuration **DevEnvironment** (`.run/DevEnvironment.run.xml`, checked in) starts the stack with `--build` and force-recreate. It loads the dev overlay on top of the base for you — just press run. ### From the terminal ```bash docker compose -f compose.yaml -f compose.dev.yaml up --build ``` PocketBase starts first; once its healthcheck passes, the web app starts (`depends_on: service_healthy`). Then: - Web app: - PocketBase admin UI: - PocketBase health: > The dev overlay auto-creates a PocketBase superuser (`test@example.com` / > `test-password`) on a **fresh** volume, so you skip the `/_/` setup screen. > These are throwaway local credentials, not the production ones. Stop and clean up (removes the throwaway PocketBase volume too): ```bash docker compose -f compose.yaml -f compose.dev.yaml down --volumes ``` If the admin UI already existed once and you want the superuser recreated from scratch, remove the volume first: `docker volume rm eb-stack_pb_data_dev`. --- ## Running the tests ```bash dotnet test ``` The integration tests (`PocketBaseFixture` in `Elternbeirat.Web.Tests`) start their **own** PocketBase from the very same compose files — the fixture shells out to: ```bash docker compose -p eb-test-stack -f compose.yaml -f compose.dev.yaml -f compose.test.yaml up --detach --wait eb-pocketbase ``` so the image version, environment and port are defined in one place, not duplicated in the test code. Only `eb-pocketbase` is started; the web app is not built for the tests. Because the test stack has its own project name (`eb-test-stack`), auto-named containers and a **random** host port, it runs happily **alongside** a running dev stack — you can have DevEnvironment up in Rider and run `dotnet test` at the same time. The fixture cleans up before and after each run, so a hard-killed earlier run leaves nothing behind. **Requirements:** Docker with the Compose plugin must be available when the tests run (they call `docker compose`). --- ## Content while developing The app reads its content from PocketBase over the compose network (`PocketBase__BaseUrl=http://eb-pocketbase:8090` — the *service* name, resolved inside the compose network, independent of the container name). Editing content means editing records in the PocketBase admin UI at , not editing files. See `redaktion.md`. --- ## Adding a new content type Editors can add **records** to the existing collections and add plain text `pages`, but a **new kind of list** — its own collection rendered on its own route with its own sorting or grouping — is a development task, not editorial. It takes three steps, all small because the plumbing is shared: 1. **Collection + DTO.** Create the collection in the PocketBase admin (give it a `public` bool and open List/View rules, like the others), then add a matching record type in `Elternbeirat.Contracts` with `[JsonPropertyName(...)]` on each field — mirror `Page`/`Post`. 2. **Client method.** Add one method to `PocketBaseClient`. The shared `GetRecordsAsync(collection, sort, ct)` already does the `public=true` filter, the sort and the deserialization, so a new type is a one-liner: `public Task> GetMinutesAsync(CancellationToken ct = default)` `=> GetRecordsAsync("minutes", "-date", ct);` 3. **Feature component.** Add a component under `Features//` with its own `@page "/…"` route that injects `PocketBaseClient`, calls the new method and renders the result. Literal routes win over the `/{Slug}` catch-all, so pick a slug that no `pages` record needs. Follow an existing list (`PostList`, `EventList`, `FaqList`) for the load-in-`OnInitializedAsync`, log-and-degrade pattern, and add a route smoke test plus a fixture seed. Text pages need none of this — they are all served generically by `ContentPage` (`@page "/{Slug}"`), which is why a new `pages` record is live without a rebuild.