Forráskód Böngészése

SPEC.md: mark Phase 19 shipped (stack-shift to Twig+Alpine+htmx+Sortable)

Updates per §14 maintenance contract:
- §2 tech stack rewritten — Twig 3, Tailwind 3 build-time, vanilla JS
  + Alpine CSP + htmx + SortableJS, no jQuery, strict CSP
- §3 directory layout refreshed (*.twig views, public/assets/js/vendor/,
  data/twig-cache/, tests/Http/)
- §9 add Phase 19 entry with SHA 75e96e2
- §10 drop the obsolete jQuery UI dark-mode cosmetic gap (jQuery UI gone)
- §11 test count bumped to 108 / 281
- §12 resume blurb mentions Phase 19
- §13 append the new SHAs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 3 napja
szülő
commit
9dd13406bd
1 módosított fájl, 99 hozzáadás és 37 törlés
  1. 99 37
      SPEC.md

+ 99 - 37
SPEC.md

@@ -24,16 +24,31 @@ per-cell audit trail.
 
 ## 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.
 - 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),
   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
 
@@ -59,9 +74,13 @@ per-cell audit trail.
 │       ├── css/app.css         # GENERATED at image-build time (gitignored)
 │       └── js/
 │           ├── 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/
 │   ├── Auth/            LocalAdmin, OidcClient, SessionGuard
 │   ├── Controllers/     AuthController, WorkerController, SprintController,
@@ -80,13 +99,16 @@ per-cell audit trail.
 ├── migrations/          001_init.sql (full schema per spec §3)
 │                        002_sprint_week_active_days.sql (Phase 12 — mask column)
 │                        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)
@@ -676,6 +698,52 @@ with a `BOOTSTRAP_ADMIN` audit row.
       four cases, days writes preserving status,
       `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
 
 Nothing scheduled.
@@ -692,15 +760,6 @@ Nothing scheduled.
   are E_DEPRECATED but still emit — harmless, and silenced by
   `ini_set('display_errors','0')` in production. Upstream library needs a
   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))
   hasn't been executed end-to-end by a human yet — it's a documentary
   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:
 ```bash
 vendor/bin/phpunit
-# → OK (105 tests, 265 assertions)
+# → OK (108 tests, 281 assertions)
 ```
 
 ## 12. How to resume in a fresh Claude session
@@ -744,17 +803,18 @@ vendor/bin/phpunit
 Tell Claude:
 
 > 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
 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)
 
 ```
+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>
 205876a SPEC.md: mark Phase 18 shipped (task-status colours + filter)
 9cb7669 Phase 18: per-cell task-status colours + filter + global toggle