|
@@ -24,16 +24,31 @@ per-cell audit trail.
|
|
|
|
|
|
|
|
## 2. Tech stack (non-negotiable)
|
|
## 2. Tech stack (non-negotiable)
|
|
|
|
|
|
|
|
-- Runtime: Docker, two-stage build, `node:20-alpine` for CSS + `php:8.3-apache` for runtime.
|
|
|
|
|
|
|
+- Runtime: Docker, two-stage build, `node:20-alpine` for CSS + JS-vendor
|
|
|
|
|
+ copy + `php:8.3-apache` for runtime.
|
|
|
- Language: PHP 8.3, strict types, PSR-12.
|
|
- Language: PHP 8.3, strict types, PSR-12.
|
|
|
- Database: SQLite via PDO, file at `/var/www/data/app.sqlite` (mounted volume).
|
|
- Database: SQLite via PDO, file at `/var/www/data/app.sqlite` (mounted volume).
|
|
|
-- Front end: server-rendered PHP templates + Tailwind CSS (vendored, compiled
|
|
|
|
|
- by the Node stage) + jQuery 3.x + jQuery UI 1.13 (from code.jquery.com CDN).
|
|
|
|
|
|
|
+- Front end (Phase 19):
|
|
|
|
|
+ - Templates: **Twig 3** (`*.twig` under `views/`, `{% extends %}`
|
|
|
|
|
+ inheritance, auto-escape ON, compiled cache in `data/twig-cache/`).
|
|
|
|
|
+ - Styles: **Tailwind CSS 3** compiled at image-build time
|
|
|
|
|
+ (`assets/css/input.css` → `public/assets/css/app.css`). No CDN.
|
|
|
|
|
+ - Behaviour: **vanilla JS** (delegated `addEventListener`, `fetch`)
|
|
|
|
|
+ for the live grid pipelines (Arbeitstage cells, RTB, task days,
|
|
|
|
|
+ task status, filters, sort) plus **SortableJS** for drag-reorder.
|
|
|
|
|
+ **Alpine.js (CSP build)** drives small declarative components
|
|
|
|
|
+ (hamburger menu, theme toggle). **htmx** wires the simple
|
|
|
|
|
+ form-post pages (auth, settings, workers, users, sprint create,
|
|
|
|
|
+ audit filter) for AJAX swaps without controller changes.
|
|
|
|
|
+ - Strict CSP: `script-src 'self'` / `style-src 'self'` only — no
|
|
|
|
|
+ `unsafe-eval`, no `unsafe-inline`, no third-party hosts. All JS
|
|
|
|
|
+ deps vendored under `public/assets/js/vendor/`.
|
|
|
- Auth: Microsoft Entra ID via OpenID Connect (Authorization Code + PKCE),
|
|
- Auth: Microsoft Entra ID via OpenID Connect (Authorization Code + PKCE),
|
|
|
plus an optional env-configured "local admin" fallback for dev / on-prem.
|
|
plus an optional env-configured "local admin" fallback for dev / on-prem.
|
|
|
-- Composer deps: `jumbojett/openid-connect-php`, `vlucas/phpdotenv`,
|
|
|
|
|
- `phpunit/phpunit` (dev).
|
|
|
|
|
-- npm deps: `tailwindcss` (build-time only).
|
|
|
|
|
|
|
+- Composer deps: `twig/twig`, `jumbojett/openid-connect-php`,
|
|
|
|
|
+ `vlucas/phpdotenv`, `phpunit/phpunit` (dev).
|
|
|
|
|
+- npm deps (build-time only): `tailwindcss`, `alpinejs`, `@alpinejs/csp`,
|
|
|
|
|
+ `htmx.org`, `sortablejs`.
|
|
|
|
|
|
|
|
## 3. Directory layout
|
|
## 3. Directory layout
|
|
|
|
|
|
|
@@ -59,9 +74,13 @@ per-cell audit trail.
|
|
|
│ ├── css/app.css # GENERATED at image-build time (gitignored)
|
|
│ ├── css/app.css # GENERATED at image-build time (gitignored)
|
|
|
│ └── js/
|
|
│ └── js/
|
|
|
│ ├── theme-init.js # Phase 16: synchronous dark-class set from localStorage (no FOUC)
|
|
│ ├── theme-init.js # Phase 16: synchronous dark-class set from localStorage (no FOUC)
|
|
|
-│ ├── app.js # site-wide; data-href click handler + hamburger menu + theme toggle
|
|
|
|
|
-│ ├── sprint-planner.js # /sprints/{id} Arbeitstage + task list
|
|
|
|
|
-│ └── sprint-settings.js # /sprints/{id}/settings
|
|
|
|
|
|
|
+│ ├── app.js # site-wide; data-href click delegation + Alpine appMenu + Alpine themeToggle + htmx CSRF wiring
|
|
|
|
|
+│ ├── sprint-planner.js # /sprints/{id} + /sprints/{id}/present — vanilla JS + SortableJS
|
|
|
|
|
+│ ├── sprint-settings.js # /sprints/{id}/settings — vanilla JS + SortableJS
|
|
|
|
|
+│ └── vendor/ # GENERATED at image-build time (gitignored)
|
|
|
|
|
+│ ├── alpine-csp.min.js # @alpinejs/csp — Alpine without `unsafe-eval`
|
|
|
|
|
+│ ├── htmx.min.js # htmx.org
|
|
|
|
|
+│ └── sortable.min.js # SortableJS
|
|
|
├── src/
|
|
├── src/
|
|
|
│ ├── Auth/ LocalAdmin, OidcClient, SessionGuard
|
|
│ ├── Auth/ LocalAdmin, OidcClient, SessionGuard
|
|
|
│ ├── Controllers/ AuthController, WorkerController, SprintController,
|
|
│ ├── Controllers/ AuthController, WorkerController, SprintController,
|
|
@@ -80,13 +99,16 @@ per-cell audit trail.
|
|
|
├── migrations/ 001_init.sql (full schema per spec §3)
|
|
├── migrations/ 001_init.sql (full schema per spec §3)
|
|
|
│ 002_sprint_week_active_days.sql (Phase 12 — mask column)
|
|
│ 002_sprint_week_active_days.sql (Phase 12 — mask column)
|
|
|
│ 003_task_status_and_app_settings.sql (Phase 18 — task-cell status + KV)
|
|
│ 003_task_status_and_app_settings.sql (Phase 18 — task-cell status + KV)
|
|
|
-├── views/ layout.php, home.php, auth/local.php,
|
|
|
|
|
-│ workers/index.php, users/index.php,
|
|
|
|
|
-│ sprints/{new,show,settings,present}.php,
|
|
|
|
|
-│ settings/index.php, audit/index.php
|
|
|
|
|
-├── tests/ TestCase + Services/ + Repositories/ + Controllers/ + Cascade/
|
|
|
|
|
-│ + Domain/ + Db/
|
|
|
|
|
-└── data/ SQLite + sessions directory (volume-mounted, gitignored)
|
|
|
|
|
|
|
+├── views/ (Twig 3) layout.twig, layout-bare.twig, home.twig,
|
|
|
|
|
+│ auth/local.twig, workers/index.twig,
|
|
|
|
|
+│ users/index.twig, audit/index.twig,
|
|
|
|
|
+│ settings/index.twig,
|
|
|
|
|
+│ sprints/{new,show,settings,present}.twig,
|
|
|
|
|
+│ sprints/_task_list.twig (shared partial)
|
|
|
|
|
+├── tests/ TestCase + Services/ + Repositories/ + Controllers/ +
|
|
|
|
|
+│ Cascade/ + Domain/ + Db/ + Http/ (Phase 19 TwigViewTest)
|
|
|
|
|
+└── data/ SQLite + sessions directory + twig-cache/
|
|
|
|
|
+ (volume-mounted, gitignored)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
## 4. Schema (migrations/001..003)
|
|
## 4. Schema (migrations/001..003)
|
|
@@ -676,6 +698,52 @@ with a `BOOTSTRAP_ADMIN` audit row.
|
|
|
four cases, days writes preserving status,
|
|
four cases, days writes preserving status,
|
|
|
`InvalidArgumentException` guard, `statusGridForSprint`).
|
|
`InvalidArgumentException` guard, `statusGridForSprint`).
|
|
|
|
|
|
|
|
|
|
+- [x] **Phase 19 — Twig 3 + Tailwind 3 + Alpine CSP + htmx + SortableJS,
|
|
|
|
|
+ jQuery removed** (`75e96e2`). Stack-shift of the entire UI layer
|
|
|
|
|
+ with zero changes to controllers, repositories, schema, capacity
|
|
|
|
|
+ math, or audit semantics — every behaviour preserved end-to-end.
|
|
|
|
|
+ All 11 `views/*.php` rewritten as `views/*.twig` using
|
|
|
|
|
+ `{% extends "layout.twig" %}` inheritance; new `layout-bare.twig`
|
|
|
|
|
+ backs `/sprints/{id}/present`'s own `<!doctype html>`; new
|
|
|
|
|
+ `_task_list.twig` partial is shared by `show.twig` and
|
|
|
|
|
+ `present.twig`. `src/Http/View.php` now wraps `Twig\Environment`
|
|
|
|
|
+ while keeping the historical `render($name, $data, $layout)`
|
|
|
|
|
+ signature so controllers don't change; auto-escape ON; compiled
|
|
|
|
|
+ cache written to `data/twig-cache/` (gitignored, www-data-owned
|
|
|
|
|
+ via the Dockerfile so first-render compilation succeeds). The
|
|
|
|
|
+ legacy `App\Http\e()` helper stays defined for backwards
|
|
|
|
|
+ compatibility but is unused by Twig templates. ~1500 lines of
|
|
|
|
|
+ jQuery / jQuery UI deleted from `app.js`, `sprint-planner.js`,
|
|
|
|
|
+ `sprint-settings.js`; each rewritten as a pure-vanilla IIFE
|
|
|
|
|
+ using `fetch` + delegated `addEventListener` against the
|
|
|
|
|
+ existing JSON-envelope endpoints. SortableJS replaces jQuery UI
|
|
|
|
|
+ sortable on the three drag-reorder lists. Alpine (CSP build,
|
|
|
|
|
+ `@alpinejs/csp` — no `unsafe-eval`) drives the hamburger menu
|
|
|
|
|
+ (`appMenu` factory) and theme toggle (`themeToggle` factory);
|
|
|
|
|
+ everything else is vanilla JS. htmx loaded site-wide; CSRF
|
|
|
|
|
+ token attached via `htmx:configRequest`; `hx-boost="true"`
|
|
|
|
|
+ sprinkled on the simple form-post pages (`/auth/local`,
|
|
|
|
|
+ `/workers` create+edit, `/users/{id}`, `/settings`,
|
|
|
|
|
+ `/sprints/new`, `/audit` filter) so submissions AJAX-swap the
|
|
|
|
|
+ body without a full reload. Sprint show/settings/present pages
|
|
|
|
|
+ stay native — their page-specific IIFEs would not re-init after
|
|
|
|
|
+ a body swap. CSP tightened: `script-src 'self'` and
|
|
|
|
|
+ `style-src 'self'` only — `https://code.jquery.com` dropped from
|
|
|
|
|
+ every directive. Dockerfile css-builder stage now also vendors
|
|
|
|
|
+ `node_modules/{@alpinejs/csp,htmx.org,sortablejs}/dist/*.min.js`
|
|
|
|
|
+ into `/build/vendor/`, which the runtime stage `COPY`s into
|
|
|
|
|
+ `public/assets/js/vendor/` (gitignored). New runtime dep
|
|
|
|
|
+ `twig/twig ^3.10`; new dev deps `alpinejs`, `@alpinejs/csp`,
|
|
|
|
|
+ `htmx.org`, `sortablejs`. Tests: 108 / 281 (was 105 / 265) —
|
|
|
|
|
+ new `tests/Http/TwigViewTest.php` adds three smoke renders
|
|
|
|
|
+ (home as signed-in admin, audit/index empty, sprints/show with
|
|
|
|
|
+ task grid + status filter); the prior 105 tests pass without
|
|
|
|
|
+ modification. `tailwind.config.js` content glob switched from
|
|
|
|
|
+ `views/**/*.php` to `views/**/*.twig`. The number-spinner reset
|
|
|
|
|
+ (`@layer base` block in `assets/css/input.css`) and Phase 18
|
|
|
|
|
+ `.assign-status-*` safelist + status-select styling all carry
|
|
|
|
|
+ over unchanged.
|
|
|
|
|
+
|
|
|
### Upcoming
|
|
### Upcoming
|
|
|
|
|
|
|
|
Nothing scheduled.
|
|
Nothing scheduled.
|
|
@@ -692,15 +760,6 @@ Nothing scheduled.
|
|
|
are E_DEPRECATED but still emit — harmless, and silenced by
|
|
are E_DEPRECATED but still emit — harmless, and silenced by
|
|
|
`ini_set('display_errors','0')` in production. Upstream library needs a
|
|
`ini_set('display_errors','0')` in production. Upstream library needs a
|
|
|
release.
|
|
release.
|
|
|
-- **jQuery UI CDN CSS is not dark-aware.** The base theme on
|
|
|
|
|
- `code.jquery.com/ui/1.13.3/themes/base/jquery-ui.css` is a light-only
|
|
|
|
|
- stylesheet. It only shows up during drag operations (worker reorder on
|
|
|
|
|
- `/sprints/{id}/settings`, sprint-worker / task reorder on
|
|
|
|
|
- `/sprints/{id}`); the sortable ghost element reads slightly out of
|
|
|
|
|
- place on a `dark:bg-slate-900` body. Accepted for now — the alternative
|
|
|
|
|
- is self-hosting a custom jQuery UI theme inside the Docker css-builder
|
|
|
|
|
- stage, which is a larger chunk of work than the cosmetic mismatch
|
|
|
|
|
- warrants.
|
|
|
|
|
- **Manual acceptance walkthrough** ([ACCEPTANCE.md](ACCEPTANCE.md))
|
|
- **Manual acceptance walkthrough** ([ACCEPTANCE.md](ACCEPTANCE.md))
|
|
|
hasn't been executed end-to-end by a human yet — it's a documentary
|
|
hasn't been executed end-to-end by a human yet — it's a documentary
|
|
|
follow-up that should happen in the running container.
|
|
follow-up that should happen in the running container.
|
|
@@ -736,7 +795,7 @@ for f in $(git ls-files '*.php'); do php -l "$f" | tail -1 | sed "s|^|$f: |"; do
|
|
|
Run the test suite:
|
|
Run the test suite:
|
|
|
```bash
|
|
```bash
|
|
|
vendor/bin/phpunit
|
|
vendor/bin/phpunit
|
|
|
-# → OK (105 tests, 265 assertions)
|
|
|
|
|
|
|
+# → OK (108 tests, 281 assertions)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
## 12. How to resume in a fresh Claude session
|
|
## 12. How to resume in a fresh Claude session
|
|
@@ -744,17 +803,18 @@ vendor/bin/phpunit
|
|
|
Tell Claude:
|
|
Tell Claude:
|
|
|
|
|
|
|
|
> Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
|
|
> Working on `/Users/achiappa/Development/claude_code_private/sprint_planer_web`.
|
|
|
-> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–18
|
|
|
|
|
-> are shipped (see §9; the Phase-17 slider popover was later
|
|
|
|
|
-> removed — typed entry is now the only edit path on number
|
|
|
|
|
-> inputs; Phase 18 added per-cell task-status colours + filter
|
|
|
|
|
-> + a new /settings page, all gated by a global flag that's off
|
|
|
|
|
-> by default). Nothing is currently scheduled. Outstanding
|
|
|
|
|
-> items are in §10 (mostly a human-run acceptance walkthrough in the
|
|
|
|
|
-> running container, plus the jQuery UI dark-mode cosmetic gap noted
|
|
|
|
|
-> there). If I ask you to plan or work a new phase, follow the
|
|
|
|
|
-> maintenance rule in §14 — commit code, then commit a SPEC.md
|
|
|
|
|
-> update separately that marks the new work shipped with its SHA.
|
|
|
|
|
|
|
+> Read `SPEC.md`, the git log, and `ACCEPTANCE.md`. Phases 1–19
|
|
|
|
|
+> are shipped (see §9; Phase 17's slider popover was removed —
|
|
|
|
|
+> typed entry is now the only edit path on number inputs; Phase 18
|
|
|
|
|
+> added per-cell task-status colours + filter + a new /settings page
|
|
|
|
|
+> gated by a global flag that's off by default; Phase 19 swapped the
|
|
|
|
|
+> stack to Twig 3 + Tailwind 3 + Alpine CSP + htmx + SortableJS and
|
|
|
|
|
+> removed jQuery + jQuery UI completely). Nothing is currently
|
|
|
|
|
+> scheduled. Outstanding items are in §10 (mostly a human-run
|
|
|
|
|
+> acceptance walkthrough in the running container). If I ask you to
|
|
|
|
|
+> plan or work a new phase, follow the maintenance rule in §14 —
|
|
|
|
|
+> commit code, then commit a SPEC.md update separately that marks
|
|
|
|
|
+> the new work shipped with its SHA.
|
|
|
|
|
|
|
|
Claude should verify what's described here against actual repo state
|
|
Claude should verify what's described here against actual repo state
|
|
|
before acting — nothing here is load-bearing once it grows stale.
|
|
before acting — nothing here is load-bearing once it grows stale.
|
|
@@ -762,6 +822,8 @@ before acting — nothing here is load-bearing once it grows stale.
|
|
|
## 13. Git history (as of this writing)
|
|
## 13. Git history (as of this writing)
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
|
|
+75e96e2 Phase 19: Twig 3 + Tailwind 3 + Alpine CSP + htmx + SortableJS, jQuery removed
|
|
|
|
|
+b3e5ec8 SPEC.md: note Phase 18 cell-markup hotfix
|
|
|
3e115f5 Fix: Phase 18 cell markup — drop span wrapper, color goes on <td>
|
|
3e115f5 Fix: Phase 18 cell markup — drop span wrapper, color goes on <td>
|
|
|
205876a SPEC.md: mark Phase 18 shipped (task-status colours + filter)
|
|
205876a SPEC.md: mark Phase 18 shipped (task-status colours + filter)
|
|
|
9cb7669 Phase 18: per-cell task-status colours + filter + global toggle
|
|
9cb7669 Phase 18: per-cell task-status colours + filter + global toggle
|