|
|
@@ -6,23 +6,188 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
|
|
|
## [Unreleased]
|
|
|
|
|
|
+## [0.26.0] — 2026-05-09
|
|
|
+
|
|
|
+Tooling- and dev-stack-heavy release on top of `v0.25.0`. Most of the
|
|
|
+work lives outside the running app: the Dockerfile splits into four
|
|
|
+named build targets (`css-builder` / `css-watcher` / `runtime` /
|
|
|
+`tests`), a `docker-compose.dev.yml` overlay adds a Tailwind `--watch`
|
|
|
+sidecar with edit-without-rebuild bind mounts, and a hand-written
|
|
|
+`bin/appctl` Bash wrapper replaces the Makefile so the project no
|
|
|
+longer needs `make`. A new `/check` Claude Code skill runs lint +
|
|
|
+PHPUnit inside the `tests` stage via a Haiku-powered subagent, keeping
|
|
|
+verbose phpunit output out of the main session. Inside the app, three
|
|
|
+REVIEW_02 simplifications fired: the JS-side capacity arithmetic is
|
|
|
+gone (server response is now authoritative), the task-row markup
|
|
|
+collapses into a single `_task_row.twig` partial cloned from a hidden
|
|
|
+`<template>` instead of being rebuilt by hand, and the form-endpoint
|
|
|
+admin/CSRF triple-line gets folded into a single
|
|
|
+`SessionGuard::requireAdminForm` helper. Smaller items: the present
|
|
|
+view gains a sprint-switcher dropdown and the brand logo, htmx no
|
|
|
+longer fights strict CSP by injecting an inline indicator stylesheet,
|
|
|
+the new-sprint settings page only pre-checks weekdays whose calendar
|
|
|
+date falls inside the chosen `[start, end]` range, the published host
|
|
|
+port becomes configurable via `HTTP_PORT`, and two dev-stack
|
|
|
+regressions (a css-watcher restart loop and a host-bind-mount
|
|
|
+read-only-database 500) get fixed.
|
|
|
+
|
|
|
+### Added
|
|
|
+
|
|
|
+- **`/check` Claude Code skill + `container-tester` (Haiku) subagent.**
|
|
|
+ New project-level slash command at `.claude/skills/check/SKILL.md`
|
|
|
+ delegates to a Haiku subagent (`.claude/agents/container-tester.md`,
|
|
|
+ Bash + Read only) that runs lint + PHPUnit inside the new `tests`
|
|
|
+ Dockerfile target and surfaces only failures + a one-line summary.
|
|
|
+ Verbose phpunit / docker build output stays inside the subagent and
|
|
|
+ never reaches the main session. Subcommands: `/check`,
|
|
|
+ `/check lint`, `/check test`, `/check <free text>`. `.gitignore`
|
|
|
+ flips from a blanket `/.claude/` ignore to `/.claude/* + !agents/ +
|
|
|
+ !skills/` so the two committed files ship with the repo while
|
|
|
+ session scratch / worktrees stay ignored.
|
|
|
+- **`HTTP_PORT` env var for the published host port.** `docker-compose.yml`
|
|
|
+ switches from a hard-coded `8088:80` to `${HTTP_PORT:-8080}:80` so
|
|
|
+ operators set the host port in `.env` alongside `APP_BASE_URL`
|
|
|
+ instead of editing the compose file. Default moves from `8088` to
|
|
|
+ `8080` so the compose default and the `APP_BASE_URL` default
|
|
|
+ finally agree. Docs: SPEC §8 env block, README quick-setup, and a
|
|
|
+ new admin-manual §3.3 "Host port".
|
|
|
+- **Present view: sprint switcher dropdown next to Close.** Header
|
|
|
+ `<select>` (visible to every signed-in user, hidden when only one
|
|
|
+ sprint exists) lists all sprints newest-start-first; on change the
|
|
|
+ page navigates to the chosen sprint's `/sprints/{id}/present`. The
|
|
|
+ change handler lives next to the existing `data-close-present`
|
|
|
+ block in `sprint-planner.js` so strict CSP stays intact.
|
|
|
+- **Present view: brand logo in the header.** Mirrors the cycle SVG
|
|
|
+ used in the main page header so the projection / beamer view
|
|
|
+ carries the same brand mark.
|
|
|
+- **`doc/REVIEW_02.md` — simplification + maintainability audit.**
|
|
|
+ Sister document to `REVIEW_01.md` (security). Catalogs over-complicated
|
|
|
+ patterns that can be rewritten more simply with no functional change;
|
|
|
+ same `R02-Nxx / Severity / Status` format so the `/loop` workflow
|
|
|
+ plugs in directly. The bold preamble requires re-reading SPEC.md
|
|
|
+ before raising any finding (the spec is the contract these refactors
|
|
|
+ must preserve, and §9's build-phase log explains why "complicated"
|
|
|
+ patterns landed that way). Three HIGH findings (R02-N01..N03) all
|
|
|
+ landed in this release.
|
|
|
+
|
|
|
### Changed
|
|
|
|
|
|
-- **Tooling: `make` → `appctl`.** The `Makefile` is gone; a hand-written
|
|
|
- bash wrapper at `bin/appctl` (with a top-level `./appctl` symlink)
|
|
|
- takes over. Subcommand surface follows the verb-then-action pattern:
|
|
|
+- **Build: split `Dockerfile` into 4 targets + dev compose overlay.**
|
|
|
+ The single Dockerfile now exposes `css-builder` (one-shot Tailwind
|
|
|
+ for prod, existing behaviour), `css-watcher` (node sidecar running
|
|
|
+ `tailwindcss --watch`, dev only), `runtime` (PHP/Apache prod image,
|
|
|
+ renamed but otherwise unchanged), and `tests` (`FROM runtime` +
|
|
|
+ `composer install` w/ dev deps for phpunit). `docker-compose.yml`
|
|
|
+ stays prod-shaped (now with explicit `target: runtime`).
|
|
|
+ `docker-compose.dev.yml` is an explicit overlay (no auto-load) that
|
|
|
+ flips `APP_ENV=development` (Twig `auto_reload`), bind-mounts the
|
|
|
+ source over the runtime image, and runs the css-watcher sidecar as
|
|
|
+ `${HOST_UID}:${HOST_GID}` so files written into bind-mounted host
|
|
|
+ paths land with normal ownership instead of root. `bin/dev-css-watcher.sh`
|
|
|
+ seeds vendor JS bundles (alpine-csp, htmx, sortable) into the
|
|
|
+ host-mounted `public/assets/js/vendor` on first start, then execs
|
|
|
+ `tailwindcss --watch` directly (skipping `npx`, which would want to
|
|
|
+ write `$HOME` under a non-root user). `tests` runs under
|
|
|
+ `profiles: [test]` for one-shot `--rm` invocations.
|
|
|
+- **Tooling: `make` → `appctl`.** A hand-written Bash wrapper at
|
|
|
+ `bin/appctl` (with a top-level `./appctl` symlink) replaces the
|
|
|
+ short-lived `Makefile` so the project no longer needs `make`.
|
|
|
+ Subcommand surface follows the verb-then-action pattern:
|
|
|
`./appctl dev start|stop|build|shell|logs`,
|
|
|
`./appctl prod start|stop|build`, and the unchanged check trio
|
|
|
- `./appctl lint|test|check`. `HOST_UID` / `HOST_GID` are still exported
|
|
|
- for the css-watcher's bind-mount ownership; the long
|
|
|
+ `./appctl lint|test|check`. `HOST_UID` / `HOST_GID` are still
|
|
|
+ exported for the css-watcher's bind-mount ownership; the long
|
|
|
`docker compose -f docker-compose.yml -f docker-compose.dev.yml …`
|
|
|
- invocation is still the one being wrapped. Bash completion ships at
|
|
|
+ invocation is what's being wrapped. Bash completion ships at
|
|
|
`bin/appctl-completion.bash`; the first interactive `./appctl`
|
|
|
invocation offers to add a `source` line to `~/.bashrc`, with a
|
|
|
marker file under `~/.config/appctl/` so the prompt only appears
|
|
|
- once. Updated SPEC §3 / §11, README, `doc/admin-manual.md`, the
|
|
|
- `/check` Claude Code skill, and the `container-tester` agent so
|
|
|
- `make check` no longer appears anywhere.
|
|
|
+ once (`APPCTL_NO_COMPLETION_PROMPT=1` silences it for CI). SPEC §3
|
|
|
+ / §11, README, `doc/admin-manual.md`, the `/check` skill, and the
|
|
|
+ `container-tester` agent all updated so `make check` no longer
|
|
|
+ appears anywhere.
|
|
|
+- **Sprint create: pre-check only weekdays inside the date range.**
|
|
|
+ `materializeWeeks` now takes `endDate` and seeds each week's
|
|
|
+ `active_days_mask` with the Mo–Fr bits whose calendar dates fall
|
|
|
+ inside `[start, end]` (`max_working_days = popcount(mask)`). The
|
|
|
+ settings page still renders all five checkboxes per week; only the
|
|
|
+ in-range ones are pre-checked, and editing/saving works as before.
|
|
|
+ The Weeks-table Start column also gains `dd.mm.YYYY` formatting,
|
|
|
+ matching the audit page's date convention.
|
|
|
+- **CSP: stop htmx from injecting its inline indicator `<style>`.**
|
|
|
+ Strict `style-src 'self'` was blocking htmx's built-in indicator
|
|
|
+ stylesheet on every page load, flooding the audit log with
|
|
|
+ `csp_violation` rows. Disable the auto-injection via
|
|
|
+ `<meta name="htmx-config" content='{"includeIndicatorStyles": false}'>`
|
|
|
+ and ship the equivalent `.htmx-indicator` / `.htmx-request` rules
|
|
|
+ from `app.css` instead.
|
|
|
+- **Fix R02-N01: drop JS-side capacity arithmetic — server is
|
|
|
+ authoritative.** The capacity formula was reproduced in
|
|
|
+ `sprint-planner.js` (~30 lines of `roundHalf` / `after_reserves` /
|
|
|
+ `committed_p1` chained math) so on-screen totals updated during
|
|
|
+ typing without the 400 ms PATCH round-trip. The PHP
|
|
|
+ `CapacityCalculator` is the spec's contract — every PATCH response
|
|
|
+ already returns the freshly computed `per_worker` values that
|
|
|
+ `applyServerCapacity()` applies, so the JS copy was just a
|
|
|
+ typing-feel optimisation that doubled the maintenance cost of any
|
|
|
+ future change. Path B from the finding: keep an immediate visual
|
|
|
+ update for the Ressourcen sum (a plain DOM sum of `[data-day]`
|
|
|
+ inputs) but drop the reserve arithmetic. Available / Reserves now
|
|
|
+ move on the server response. Removed: `capacity()`,
|
|
|
+ `committedPrio1FromDom()`, `recomputeAllCapacity()`, and the
|
|
|
+ `data-reserve-fraction` attribute on `show.twig` /
|
|
|
+ `present.twig`. `POST /tasks/{id}/move` now also returns
|
|
|
+ `per_worker` for the source sprint so the move-out path can call
|
|
|
+ `applyServerCapacity()` instead of recomputing locally. SPEC §5 /
|
|
|
+ §9 / §13 refreshed; the "any edit must touch both" line in §5 goes
|
|
|
+ away.
|
|
|
+- **Fix R02-N02: clone `<template data-task-row-template>` instead
|
|
|
+ of mirroring `_task_list` in JS.** Extracted the task-row markup
|
|
|
+ into `views/sprints/_task_row.twig` and call it twice from
|
|
|
+ `_task_list.twig` — once inside the for-loop, once inside a hidden
|
|
|
+ `<template data-task-row-template>` at the bottom (admin-only).
|
|
|
+ `sprint-planner.js`'s `buildTaskRow` now clones the template and
|
|
|
+ populates the few fields that vary; ~150 lines of hand-rolled DOM
|
|
|
+ construction are gone, along with the `ownerChoices()` /
|
|
|
+ `sprintWorkerHeaders()` helpers that only existed to feed it.
|
|
|
+ Server-rendered rows and JS-built rows share a single Twig source,
|
|
|
+ so the historical drift incidents (hotfixes 7c298d3, 23ab365, and
|
|
|
+ the whitespace-nowrap re-mirror in f204611) can no longer happen.
|
|
|
+ TwigViewTest pins both directions: a marker on the template body
|
|
|
+ for the admin path, and the absence of `<template
|
|
|
+ data-task-row-template>` for read-only users.
|
|
|
+- **Fix R02-N03: extract `SessionGuard::requireAdminForm` + drop
|
|
|
+ `SprintController::gateJsonAdmin`.** Form-handling controllers used
|
|
|
+ to open with the same three-line block (`requireAdmin` + Response
|
|
|
+ early-return + `verifyCsrf`) at nine sites: `SprintController::create`
|
|
|
+ / `::delete`, `WorkerController::create` / `::update`,
|
|
|
+ `UserController::update` / `::tombstone`, `SettingsController::update`,
|
|
|
+ `ImportController::upload` / `::commit`. New
|
|
|
+ `SessionGuard::requireAdminForm(Request, UserRepository): User|Response`
|
|
|
+ mirrors the existing `requireAdminJson` but speaks the form dialect
|
|
|
+ (redirect to `/auth/login` when anonymous, 403 text on non-admin,
|
|
|
+ 403 text on CSRF mismatch). All nine sites collapse to a single
|
|
|
+ call. `SprintController::gateJsonAdmin` was an identical local twin
|
|
|
+ of `requireAdminJson`; its eight call sites switch to the
|
|
|
+ `SessionGuard` helper and the private method goes away. Net: +37
|
|
|
+ -64 lines; tests unchanged at 340 / 340.
|
|
|
+
|
|
|
+### Fixed
|
|
|
+
|
|
|
+- **Dev stack: css-watcher restart loop + readonly-DB 500.** Two
|
|
|
+ unrelated `appctl dev start` regressions caused by the bind-mount
|
|
|
+ overlay clashing with the upstream images. The css-watcher
|
|
|
+ restarted forever because Tailwind's `--watch` listens on stdin and
|
|
|
+ exits on EOF; `compose up` closes stdin by default, so
|
|
|
+ `restart: unless-stopped` looped it. Fixed by adding
|
|
|
+ `stdin_open` + `tty` to keep chokidar alive. The host bind mount at
|
|
|
+ `/var/www/data` was masking the Dockerfile's `chown www-data`,
|
|
|
+ leaving `app.sqlite` (root:1000 644) and no `sessions/` dir, so the
|
|
|
+ first request 500'd with `attempt to write a readonly database`.
|
|
|
+ Self-heal in `bin/docker-entrypoint.sh` by `mkdir`-ing the data +
|
|
|
+ session dirs and `chown`-ing to `www-data` on every start (twice —
|
|
|
+ migrate runs as root and would otherwise leave new SQLite/WAL
|
|
|
+ files root-owned).
|
|
|
|
|
|
## [0.25.0] — 2026-05-07
|
|
|
|
|
|
@@ -417,6 +582,7 @@ R01-N09 (`SameSite=Lax` retained — `Strict` would block the OIDC
|
|
|
callback), R01-N17 (concurrent-tab OIDC clobber is correct
|
|
|
RFC behaviour), R01-N29, R01-N30, R01-N32, R01-N33, R01-N34.
|
|
|
|
|
|
-[Unreleased]: https://github.com/chiappa/sprint_planer_web/compare/v0.23.0...HEAD
|
|
|
+[Unreleased]: https://github.com/chiappa/sprint_planer_web/compare/v0.26.0...HEAD
|
|
|
+[0.26.0]: https://github.com/chiappa/sprint_planer_web/compare/v0.25.0...v0.26.0
|
|
|
[0.23.0]: https://github.com/chiappa/sprint_planer_web/compare/v0.22.0...v0.23.0
|
|
|
[0.22.0]: https://github.com/chiappa/sprint_planer_web/releases/tag/v0.22.0
|