# Plan 3b: Editor Handlers, Views, and Front Controller Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build the six editor HTTP handlers, six view templates, the real front controller, and the full-flow integration test that complete the Plan 3 web layer. **Architecture:** A front controller (`public/index.php`) dispatches every request to a `Http\Router` that routes by HTTP method + path to one of eight `Http\Handlers\*` classes. Handlers parse the request through a `Http\Request` value object, do their work through `Application\*` services (which know the `Domain`), and return a `Http\Response` rendered by `Views\*` PHP templates. CSRF and per-session upload tokens are issued by the front controller and validated by the handlers. **Tech Stack:** PHP 8.3, PHPUnit 11, PHPStan level 6, PHP_CodeSniffer PSR-12, plain `php -S` for local dev. ## Global Constraints These apply to every task and are copied verbatim from the design spec (which is a delta on the Plan 3 spec). - Every state-changing form or request uses and verifies a CSRF token before mutation. Forms use a hidden `_csrf` field; fetch uses an `X-CSRF-Token` header. - All rendered output is escaped for its output context via the `Escape::html`, `Escape::attr`, and `Escape::url` helpers. Templates do not interpolate user input without one of these helpers. - All uploaded images are content-validated by reading the first 12 bytes and checking the magic number against an allowlist (PNG, JPEG, WebP, GIF), then `getimagesize()` confirms dimensions are ≤ 512×512, then size is checked at ≤ 2 MB. The original filename and declared MIME are discarded after validation. - `var/uploads/` is denied by `.htaccess` (Apache) and `index.php` (built-in server). - The dev server is the built-in `php -S 0.0.0.0:8000 -t public` (documented in README). - PHP follows the repository PHPCS rules and passes PHPStan at level 6. - Composer configuration passes `composer validate --strict`. - Lint, static analysis, unit and integration tests, the end-to-end smoke test, and Composer validation are required CI checks. - The 4 `@phpstan-ignore function.alreadyNarrowedType` annotations from Plan 1+2+3a reference "Plan 4" in their comments; the actual JSON sources land in Plan 3. The Plan 3b front controller does not introduce a new annotation; the front controller sits below the threshold and the handlers and templates are pure routing and rendering. - Plan 3b-specific: the front controller's `userToken` validation in `GetAssets` uses a strict format regex (`/^[a-f0-9]{32,}$/`) to harden the path-traversal surface. The `BATTLEFORGE_SECRET` env var takes precedence over the per-install file `var/secret.key` (a 32-byte binary file created on first run with `random_bytes(32)` and `chmod 0600`; git-ignored). - Domain code has no dependency on HTTP, sessions, storage, or browser code (unchanged from Plans 1, 2, 3a). ## File Structure ```text src/Http/Handlers/ ├── GetTeamEditor.php (NEW) — renders the team editor form ├── PostTeamEditor.php (NEW) — processes the team editor form POST ├── GetBattlefieldEditor.php (NEW) — renders the battlefield editor page ├── PostBattlefieldEditor.php (NEW) — processes the JSON body ├── PostImageUpload.php (NEW) — multipart upload, returns {url} JSON └── PostStartMatch.php (NEW) — JSON body, returns initial MatchState src/Views/ ├── layout.php (NEW) — shared chrome ├── home.php (NEW) — the home page (currently inline in GetHomePage) ├── team-editor.php (NEW) — the team editor form ├── battlefield-editor.php (NEW) — the battlefield editor with the empty grid ├── match-stub.php (NEW) — the Plan 4 placeholder └── upload-result.php (NEW) — iframe-style response for the upload form public/ ├── index.php (MODIFY) — replace the Task 1 stub with the real front controller └── assets/ └── (placeholders land in Plan 3c) tests/Integration/ ├── GetTeamEditorTest.php ├── PostTeamEditorTest.php ├── GetBattlefieldEditorTest.php ├── PostBattlefieldEditorTest.php ├── PostImageUploadTest.php ├── PostStartMatchTest.php └── FullFlowTest.php ``` ## Delivery Sequence Plan 3b is the fourth of four plans, picking up where Plan 3a left off. It is the last plan in the original "Plan 3" deliverable. 1. View templates — establish the rendering foundation (Tasks 1-3). 2. Editor handlers — wire the templates to the domain (Tasks 4-6). 3. Front controller — wire the handlers to the URL space (Task 7). 4. Full-flow integration test — exercise the whole stack (Task 8). 5. CI verification and final review (Tasks 9-10). ## Execution Preflight Execute this plan in an isolated worktree on `feature/editor-handlers-and-views`, branched from `develop`. The implementation branch will be submitted as a pull request into `develop` only after every completion check is green. ### Task 1: Shared `layout.php` chrome **Files:** - Create: `src/Views/layout.php` **Interfaces:** - Produces the shared template that all other templates `require` to set up the document chrome. The layout is invoked by a template that has already started buffering its body content; the layout's signature is `render_layout(callable $body, string $csrf, string $title, string $extraHead = ''): void`. The layout prints: - The HTML5 doctype. - ``. - `
` with charset, viewport, `No saved scenarios yet.
= $e($error) ?>
Scenario: = $e($scenarioId) ?>
Escape::html($v); $matchInfo = $matchJson !== null ? "Match loaded." : "No match in progress."; ?>= $e($matchInfo) ?>
= $e($matchJson) ?>
Battle interface coming in Plan 4.
Escape::html($v); $json = json_encode(['url' => $url], JSON_THROW_ON_ERROR); ?>Uploaded: = $e($url) ?>