# Sprint Planner Web replacement for an Excel-based sprint planning workbook used by a ~15-person ops/dev team. One sprint per page: an Arbeitstage matrix (working-days availability per worker per week) on top, a task list with per-worker day allocations on the bottom. Proper auth, database, and a per-cell audit trail. ## Stack - PHP 8.3 + Apache (Docker, multi-stage build) - SQLite via PDO (file on a mounted volume) - Server-rendered PHP templates + Tailwind CSS (vendored, compiled by a Node build stage) + jQuery / jQuery UI (CDN) - Microsoft Entra ID (OpenID Connect, Authorization Code + PKCE), with an optional local-admin fallback for dev / on-prem deployments - Dark mode (manual toggle, no FOUC), strict CSP ## Quick setup For a development or pilot install on a single host: ```bash # 1. Clone and enter the repo git clone sprint_planer_web cd sprint_planer_web # 2. Create the .env file from the template cp .env.example .env # 3. Generate a hash for the local-admin password. The app verifies sign-ins # with password_verify() against LOCAL_ADMIN_PASSWORD_HASH, so the # plaintext password never lands on disk. Pick the snippet that matches # what you have installed: # # a) Host PHP 8 (any minor): # php -r 'echo password_hash(readline("Password: "), PASSWORD_DEFAULT), PHP_EOL;' # # b) No host PHP — use the Docker image you already build with: # docker run --rm -it php:8.3-cli php -r \ # 'echo password_hash(readline("Password: "), PASSWORD_DEFAULT), PHP_EOL;' # # Both prompt for the password (no echo on most TTYs) and print a # bcrypt string starting with `$2y$`. Paste it into .env as: # LOCAL_ADMIN_EMAIL='you@example.com' # LOCAL_ADMIN_PASSWORD_HASH='$2y$...' # Use single quotes — the `$` in the hash will otherwise be eaten by the # shell on `docker compose up`. # # For Entra-based sign-in, fill ENTRA_TENANT_ID / ENTRA_CLIENT_ID / # ENTRA_CLIENT_SECRET instead (or in addition). chmod 600 .env # 4. Build and start the container (compose maps host port 8088 → container 80) docker compose up -d --build # Equivalent shortcut once `make` is installed: `make prod` # 5. Open the app xdg-open http://localhost:8088 # or just visit it in a browser ``` If you signed in via the local-admin fallback you are immediately the administrator (audit-logged as `BOOTSTRAP_ADMIN` with `via=local`). For an OIDC-only deploy, nominate the first admin up front via `BOOTSTRAP_ADMIN_OID` or `BOOTSTRAP_ADMIN_EMAIL` in `.env` — without one of them set the OIDC path will not auto-promote anyone (this closes finding R01-N03 in `doc/REVIEW_01.md`). Subsequent admin promotions happen through the **Users** page in the hamburger menu. The SQLite database is created at `./data/app.sqlite` on the host (mounted into the container at `/var/www/data/app.sqlite`). Migrations run from the Docker entrypoint (`bin/docker-entrypoint.sh` → `php bin/migrate.php`) BEFORE Apache binds the port — a failed migration aborts container start instead of leaving a half-applied schema (R01-N22). The web request path only checks `schema_version` and returns 503 Service Unavailable when something is pending. Wiping `./data/` resets the application to a blank slate. For everything else — Entra app registration, backups, troubleshooting, upgrades — see [`doc/admin-manual.md`](doc/admin-manual.md). ## Local development For iterating on code (vs. running the prod-shaped image you set up above), use the dev compose overlay. Source dirs are bind-mounted from the host and a sidecar runs `tailwindcss --watch`, so Twig / PHP edits are picked up on the next request and CSS changes appear without a container rebuild: ```bash make dev # foreground; equivalent to: # HOST_UID=$(id -u) HOST_GID=$(id -g) \ # docker compose -f docker-compose.yml -f docker-compose.dev.yml up ``` Rebuild the dev images only when `Dockerfile`, `composer.json/.lock`, or `package.json/.lock` change: ```bash make dev-build ``` `make help` lists every target. Full architecture (overlay rationale, dev edit-cycle table, `tests` Docker stage) lives in [`SPEC.md`](SPEC.md) §11. ## Layout ``` public/ front controller (index.php) + web root src/ application code (App\ namespace, PSR-4) views/ PHP templates migrations/ numbered .sql files applied by Migrator assets/ Tailwind source (compiled into public/assets/css/app.css) data/ SQLite + sessions (volume-mounted; gitignored) doc/ operator-facing documentation (admin manual) ``` ## Documentation - [`doc/admin-manual.md`](doc/admin-manual.md) — administrator setup, configuration, and operations guide. - [`SPEC.md`](SPEC.md) — full specification: schema, routes, capacity math, build phase history. Read this if you intend to modify the code. - [`ACCEPTANCE.md`](ACCEPTANCE.md) — manual acceptance checklist used to validate releases. ## Tests The preferred path is the `tests` Docker stage — same PHP version, extensions, and composer state as prod, plus the dev composer deps layered in: ```bash make check # lint + phpunit, in a one-shot --rm container make test # phpunit only make lint # php -l on src/ + tests/ ``` First run of any of these cold-builds the `tests` image (~30–60s); subsequent runs reuse it. No host PHP / composer needed. From inside Claude Code, the **`/check`** slash command runs the same pipeline via a Haiku-powered subagent that returns only the pass/fail summary — verbose phpunit output stays out of the conversation context. Direct `vendor/bin/phpunit` on the host still works if you have PHP + composer locally and want the fastest possible loop, but the Phase 20 parser tests then auto-skip on hosts missing `ext-gd` / `ext-zip` / `ext-xmlreader` etc. See [`SPEC.md`](SPEC.md) §11.3 for the full table.