#100 — Local Review Environment — Hot-Reload Dev Loop + Seeded Playground #99

Closed
opened 2026-08-18 13:14:01 +02:00 by lena · 0 comments
lena commented 2026-08-18 13:14:01 +02:00 (Migrated from git.butzei.de)

Story #100: Local Review Environment — Hot-Reload Dev Loop + Seeded Playground

As a developer (human) reviewing what the agent team just built,
I want to see a feature working in a running app within seconds of it landing on master, against a
database that already has realistic data in it,
so that I don't have to wait on the full CI pipeline (build, test, 4-leg Playwright matrix, Docker
image, redeploy) just to look at something, and a red e2e leg doesn't block me from reviewing at all.


Background

Reviewing a feature today means waiting for master to go through Gitea CI end-to-end and then pulling a
new image to todo.moekies.de — slow, and doubly so when an e2e leg goes red, since there is currently no
way to look at the app without going through that whole pipeline.

Most of the pieces already exist:

  • docker-compose.dev.yml already gives a persisted Postgres + Redis (named volume todo_dev_postgres)
    behind a devbox container with the full toolchain (docs/dev-container.md).
  • MigrationService/Worker.cs and CqsTodo/Setup.cs already seed an idempotent test account
    (testuser / geheim123!) with one list/todo on every startup — but the two copies of that seed logic are
    independently maintained (flagged in a comment on Worker.cs), and the seed itself is bare-minimum (just
    enough to satisfy foreign keys), not really "data to play around with."
  • Story #33 (the dev container itself) explicitly scoped hot-reload out: "Hot-reload configuration
    inside the container (developers can set this up manually)."
    docs/dev-container.md still documents
    dotnet run (not dotnet watch), i.e. a manual restart per change.

This story closes those two gaps: a genuinely fast local loop (hot reload, not rebuild-and-redeploy) and a
seed dataset worth clicking around in — decoupled entirely from CI/e2e status.

Resolved during PO discussion (2026-08-07) — see docs/roadmap.md Decisions Log for the full reasoning:

  • The loop stays fully unattended. This is not a gate — nothing pauses waiting for a human to look.
    The review instance is just kept ready so a human can look, on their own time.
  • Claude Code runs this project's "go" loop from two different kinds of places on different days: a
    restricted sandbox with no Docker at all (verified: no docker binary), and, on other days, directly
    on the human's own Linux or Windows machine with full Docker access (Docker Desktop on Windows, Docker
    Engine on Linux). The story must work in both, degrading gracefully rather than failing when Docker isn't
    reachable.
  • When Docker is reachable, Claude Code is allowed to start/restart the review stack itself as a
    convenience step at the end of a cycle — never blocking on it, never failing the cycle if it can't.
  • Docker Desktop is assumed available on a Windows host too, so Postgres/Redis run in containers there
    exactly as on Linux; only the outer process that runs the backend/frontend differs by OS, not the
    database story.

Acceptance criteria

Hot-reload dev loop

  • docs/dev-container.md documents dotnet watch --project CqsTodo.WebApi --urls http://+:5000 (in
    place of the current dotnet run) and the already-hot-reloading npm run dev as the standard way to
    review work — start once, leave running, refresh the browser after each change lands.
  • The same two commands work outside the devbox too — directly on a bare host (Linux or Windows)
    that has the .NET 10 SDK and Node 22 installed natively — against db/redis started standalone via
    docker compose -f docker-compose.dev.yml up -d db redis (no devbox container required). Both paths
    point at the same named Postgres volume, so review data doesn't reset depending on which way a given
    cycle happened to run.
  • docs/dev-container.md gets a short "bare host" section covering both OSes (commands only — dotnet,
    npm, docker compose are all cross-platform; no OS-specific scripting needed).

Docker socket proxy (scoped container access from inside the devbox)

  • A docker-proxy service is added to docker-compose.dev.yml using tecnativa/docker-socket-proxy.
    It mounts the host Docker socket read-only and exposes a filtered HTTP API on tcp://docker-proxy:2375
    inside the dev network. Only the CONTAINERS and POST permissions are enabled; everything else
    (images, volumes, networks, build, exec, etc.) defaults to denied.
  • The devbox sets DOCKER_HOST=tcp://docker-proxy:2375 in its environment so that docker CLI calls
    inside the container reach the proxy, not the raw socket. The existing raw-socket mount
    (/var/run/docker.sock) is retained as-is for Testcontainers — it connects via the socket path
    directly and is unaffected by the DOCKER_HOST env var.
  • From inside the devbox, Claude Code can run docker restart <container> and docker start <container>
    for containers in the todo_dev compose project (e.g. todo_dev-db-1, todo_dev-redis-1), but
    cannot create, delete, or build containers, cannot access volumes or images, and cannot exec into
    containers. Verified by attempting a denied operation and confirming it is rejected by the proxy.
  • The proxy service is documented in docs/dev-container.md with a note on what is and isn't permitted.

Autonomous, non-blocking lifecycle management

  • At the point in the "go" loop where this fits (see Architect's design note for exactly where),
    Claude Code checks whether Docker is reachable in its current environment (docker info or equivalent).
    If not (e.g. in the restricted sandbox), it skips this step silently and moves on — the cycle is
    unaffected either way.
  • If Docker is reachable, Claude Code may restart db/redis via the proxy as a convenience step
    (e.g. to pick up a migration after a schema change). This must be idempotent — running it against an
    already-running stack must not error.
  • This step never blocks, retries indefinitely, or fails the cycle. A human reviewing later is always
    welcome, never required.

Seed data

  • The two independently-maintained copies of the seed logic (CqsTodo/Setup.cs and
    CqsTodo.MigrationService/Worker.cs) are consolidated into one shared method — MigrationService
    already project-references CqsTodo, so there's no reason for two copies to drift.
  • The seed grows from "one list, one todo" to a modest, still-idempotent playground under the same
    testuser account: a second list with its own category, a few todos in a mix of done/open states, one
    shopping list with a couple of products, and one recurring todo. Login stays testuser / geheim123!
    — nothing about how you log in changes.

Out of scope for this story

  • Any change to the Gitea CI pipeline (ci.yml), the production Dockerfile/docker-compose.yml, or the
    todo.moekies.de deployment — this story is purely about the local review loop; CI stays the release gate
    for the Docker image.
  • Any new gate/pause in the autonomous loop. The loop stays fully unattended per the 2026-08-07 decision;
    this story must not introduce anything a human has to acknowledge before the loop continues.
  • A Docker-free fallback for either OS (e.g. a natively-installed Postgres, SQLite, etc.). Docker (Desktop on
    Windows, Engine on Linux) is assumed available whenever the bare-host path is used; if it genuinely isn't
    on a given machine, that machine simply doesn't get this feature yet.
  • Any new screenshot/reporting tooling — Playwright screenshot capability already exists from #33 and is
    unchanged by this story.
  • Any change to auth/security posture — same dev-only seeded credentials as today, Email__Mode=log etc.
    unchanged.
  • Filtering the proxy's access down to only the todo_dev compose project's containers specifically. The
    socket proxy filters by Docker API endpoint type, not by container label or name — Claude could
    technically restart any container visible to the daemon via the proxy, not just the stack's own ones.
    Accepted: on a personal dev machine this is a low-risk gap, and the alternative (a custom control sidecar)
    would require maintaining custom code. If a second stack runs on the same host and isolation matters, that
    should be revisited then.

Open questions

None outstanding. The environment/ownership questions this story originally needed a human for were resolved
directly in the 2026-08-07 PO discussion (see Background above and the matching Decisions Log entry in
docs/roadmap.md) before this story was drafted.

# Story `#100`: Local Review Environment — Hot-Reload Dev Loop + Seeded Playground **As a** developer (human) reviewing what the agent team just built, **I want to** see a feature working in a running app within seconds of it landing on `master`, against a database that already has realistic data in it, **so that** I don't have to wait on the full CI pipeline (build, test, 4-leg Playwright matrix, Docker image, redeploy) just to look at something, and a red e2e leg doesn't block me from reviewing at all. --- ## Background Reviewing a feature today means waiting for `master` to go through Gitea CI end-to-end and then pulling a new image to `todo.moekies.de` — slow, and doubly so when an e2e leg goes red, since there is currently no way to look at the app without going through that whole pipeline. Most of the pieces already exist: - `docker-compose.dev.yml` already gives a **persisted** Postgres + Redis (named volume `todo_dev_postgres`) behind a `devbox` container with the full toolchain (`docs/dev-container.md`). - `MigrationService/Worker.cs` and `CqsTodo/Setup.cs` already **seed** an idempotent test account (`testuser` / `geheim123!`) with one list/todo on every startup — but the two copies of that seed logic are independently maintained (flagged in a comment on `Worker.cs`), and the seed itself is bare-minimum (just enough to satisfy foreign keys), not really "data to play around with." - Story `#33` (the dev container itself) explicitly scoped hot-reload **out**: *"Hot-reload configuration inside the container (developers can set this up manually)."* `docs/dev-container.md` still documents `dotnet run` (not `dotnet watch`), i.e. a manual restart per change. This story closes those two gaps: a genuinely fast local loop (hot reload, not rebuild-and-redeploy) and a seed dataset worth clicking around in — decoupled entirely from CI/e2e status. **Resolved during PO discussion (2026-08-07)** — see `docs/roadmap.md` Decisions Log for the full reasoning: - The loop stays **fully unattended**. This is not a gate — nothing pauses waiting for a human to look. The review instance is just kept ready so a human *can* look, on their own time. - Claude Code runs this project's "go" loop from two different kinds of places on different days: a restricted sandbox with **no Docker at all** (verified: no `docker` binary), and, on other days, directly on the human's own Linux or Windows machine with full Docker access (Docker Desktop on Windows, Docker Engine on Linux). The story must work in both, degrading gracefully rather than failing when Docker isn't reachable. - When Docker *is* reachable, Claude Code is allowed to start/restart the review stack itself as a convenience step at the end of a cycle — never blocking on it, never failing the cycle if it can't. - Docker Desktop is assumed available on a Windows host too, so Postgres/Redis run in containers there exactly as on Linux; only the outer process that runs the backend/frontend differs by OS, not the database story. --- ## Acceptance criteria *Hot-reload dev loop* - [ ] `docs/dev-container.md` documents `dotnet watch --project CqsTodo.WebApi --urls http://+:5000` (in place of the current `dotnet run`) and the already-hot-reloading `npm run dev` as the standard way to review work — start once, leave running, refresh the browser after each change lands. - [ ] The same two commands work **outside** the devbox too — directly on a bare host (Linux or Windows) that has the .NET 10 SDK and Node 22 installed natively — against `db`/`redis` started standalone via `docker compose -f docker-compose.dev.yml up -d db redis` (no `devbox` container required). Both paths point at the same named Postgres volume, so review data doesn't reset depending on which way a given cycle happened to run. - [ ] `docs/dev-container.md` gets a short "bare host" section covering both OSes (commands only — `dotnet`, `npm`, `docker compose` are all cross-platform; no OS-specific scripting needed). *Docker socket proxy (scoped container access from inside the devbox)* - [ ] A `docker-proxy` service is added to `docker-compose.dev.yml` using `tecnativa/docker-socket-proxy`. It mounts the host Docker socket read-only and exposes a filtered HTTP API on `tcp://docker-proxy:2375` inside the `dev` network. Only the `CONTAINERS` and `POST` permissions are enabled; everything else (images, volumes, networks, build, exec, etc.) defaults to denied. - [ ] The devbox sets `DOCKER_HOST=tcp://docker-proxy:2375` in its environment so that `docker` CLI calls inside the container reach the proxy, not the raw socket. The existing raw-socket mount (`/var/run/docker.sock`) is **retained as-is** for Testcontainers — it connects via the socket path directly and is unaffected by the `DOCKER_HOST` env var. - [ ] From inside the devbox, Claude Code can run `docker restart <container>` and `docker start <container>` for containers in the `todo_dev` compose project (e.g. `todo_dev-db-1`, `todo_dev-redis-1`), but cannot create, delete, or build containers, cannot access volumes or images, and cannot exec into containers. Verified by attempting a denied operation and confirming it is rejected by the proxy. - [ ] The proxy service is documented in `docs/dev-container.md` with a note on what is and isn't permitted. *Autonomous, non-blocking lifecycle management* - [ ] At the point in the "go" loop where this fits (see Architect's design note for exactly where), Claude Code checks whether Docker is reachable in its current environment (`docker info` or equivalent). If not (e.g. in the restricted sandbox), it skips this step silently and moves on — the cycle is unaffected either way. - [ ] If Docker **is** reachable, Claude Code may restart `db`/`redis` via the proxy as a convenience step (e.g. to pick up a migration after a schema change). This must be idempotent — running it against an already-running stack must not error. - [ ] This step never blocks, retries indefinitely, or fails the cycle. A human reviewing later is always welcome, never required. *Seed data* - [ ] The two independently-maintained copies of the seed logic (`CqsTodo/Setup.cs` and `CqsTodo.MigrationService/Worker.cs`) are consolidated into one shared method — `MigrationService` already project-references `CqsTodo`, so there's no reason for two copies to drift. - [ ] The seed grows from "one list, one todo" to a modest, still-idempotent playground under the same `testuser` account: a second list with its own category, a few todos in a mix of done/open states, one shopping list with a couple of products, and one recurring todo. Login stays `testuser` / `geheim123!` — nothing about how you log in changes. --- ## Out of scope for this story - Any change to the Gitea CI pipeline (`ci.yml`), the production `Dockerfile`/`docker-compose.yml`, or the `todo.moekies.de` deployment — this story is purely about the local review loop; CI stays the release gate for the Docker image. - Any new gate/pause in the autonomous loop. The loop stays fully unattended per the 2026-08-07 decision; this story must not introduce anything a human has to acknowledge before the loop continues. - A Docker-free fallback for either OS (e.g. a natively-installed Postgres, SQLite, etc.). Docker (Desktop on Windows, Engine on Linux) is assumed available whenever the bare-host path is used; if it genuinely isn't on a given machine, that machine simply doesn't get this feature yet. - Any new screenshot/reporting tooling — Playwright screenshot capability already exists from `#33` and is unchanged by this story. - Any change to auth/security posture — same dev-only seeded credentials as today, `Email__Mode=log` etc. unchanged. - Filtering the proxy's access down to only the `todo_dev` compose project's containers specifically. The socket proxy filters by Docker API *endpoint type*, not by container label or name — Claude could technically restart any container visible to the daemon via the proxy, not just the stack's own ones. Accepted: on a personal dev machine this is a low-risk gap, and the alternative (a custom control sidecar) would require maintaining custom code. If a second stack runs on the same host and isolation matters, that should be revisited then. --- ## Open questions None outstanding. The environment/ownership questions this story originally needed a human for were resolved directly in the 2026-08-07 PO discussion (see Background above and the matching Decisions Log entry in `docs/roadmap.md`) before this story was drafted.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#99
No description provided.