No Description

ClaudePriv@chiappa.zhdk.ch 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
.claude 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
assets 4ae4db6b94 CSP: stop htmx from injecting its inline indicator <style> 1 day ago
bin 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
doc 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
migrations 22a3840570 Fix R01-N23: users.tombstoned_at — soft erasure for privacy requests 2 days ago
public 14b1cfd8a9 Fix R02-N01: drop JS-side capacity arithmetic, server is authoritative 1 day ago
src cccc7de3e1 Sprint create: pre-check only weekdays inside the date range 12 hours ago
tests cccc7de3e1 Sprint create: pre-check only weekdays inside the date range 12 hours ago
views cccc7de3e1 Sprint create: pre-check only weekdays inside the date range 12 hours ago
.dockerignore ab9430b0cc Phase 11: vendor Tailwind + drop inline onclick + tighten CSP 2 weeks ago
.env.example 4354266825 Build: make published host port configurable via HTTP_PORT (default 8080) 2 hours ago
.gitignore c1557b666c Tooling: /check Claude Code skill + container-tester (Haiku) subagent 1 day ago
ACCEPTANCE.md 857df15c93 Fix R01-N01: hash-only LOCAL_ADMIN_PASSWORD_HASH (no plaintext fallback) 2 days ago
CHANGELOG.md 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
Dockerfile a004c1a3a1 Build: split Dockerfile into 4 targets + dev compose overlay + Makefile 1 day ago
LICENSE 7449fb2432 Release v0.22.0: Apache 2.0 license + CHANGELOG + per-file headers 2 days ago
README.md 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
SPEC.md 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
appctl 50d34deaa2 Tooling: replace Makefile with bin/appctl bash wrapper + completion 1 hour ago
composer.json 8876239727 Phase 20: XLSX import wizard (PhpSpreadsheet + colour→status) 2 days ago
composer.lock 8876239727 Phase 20: XLSX import wizard (PhpSpreadsheet + colour→status) 2 days ago
docker-compose.dev.yml d1c3a0c0ec Fix: dev stack — css-watcher restart loop + readonly-DB 500 1 day ago
docker-compose.yml 4354266825 Build: make published host port configurable via HTTP_PORT (default 8080) 2 hours ago
package-lock.json 75e96e2470 Phase 19: Twig 3 + Tailwind 3 + Alpine CSP + htmx + SortableJS, jQuery removed 2 days ago
package.json 75e96e2470 Phase 19: Twig 3 + Tailwind 3 + Alpine CSP + htmx + SortableJS, jQuery removed 2 days ago
phpunit.xml 21d0c4ac33 Phase 7: audit viewer + security headers + PHPUnit 2 weeks ago
tailwind.config.js 7449fb2432 Release v0.22.0: Apache 2.0 license + CHANGELOG + per-file headers 2 days ago

README.md

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:

# 1. Clone and enter the repo
git clone <repository-url> 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 HTTP_PORT
#    from .env → container 80; default 8080, change in .env to taste)
docker compose up -d --build
#    Equivalent shortcut: `./appctl prod start`

# 5. Open the app
xdg-open http://localhost:8080   # substitute your HTTP_PORT if changed

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

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:

./appctl dev start   # 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:

./appctl dev build

./appctl help lists every command. The first ./appctl invocation in an interactive shell offers to wire up bash completion (a source line into ~/.bashrc); answer n to skip — re-asking is suppressed via a marker in ~/.config/appctl/. Full architecture (overlay rationale, dev edit-cycle table, tests Docker stage) lives in 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 — administrator setup, configuration, and operations guide.
  • SPEC.md — full specification: schema, routes, capacity math, build phase history. Read this if you intend to modify the code.
  • 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:

./appctl check    # lint + phpunit, in a one-shot --rm container
./appctl test     # phpunit only
./appctl 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 §11.3 for the full table.