From 16dfffd9169c666da206aa332e50b7f2ff980999 Mon Sep 17 00:00:00 2001 From: magdev Date: Sat, 2 May 2026 00:25:58 +0200 Subject: [PATCH] Detail Phase 1 spec; switch task runner from Task to Make MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds naming/identifier table, directory layout, eight sub-commits, and done criteria for Phase 1. Also swaps the planned Taskfile for a plain Makefile across §8 and §13 — Make is universal and skips a dependency that openSUSE Tumbleweed doesn't package. Co-Authored-By: Claude Opus 4.7 (1M context) --- PLAN.md | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/PLAN.md b/PLAN.md index 058610d..b6264a3 100644 --- a/PLAN.md +++ b/PLAN.md @@ -300,20 +300,22 @@ Maker-bundle templates are overridable via skeleton paths. We document the overr ### One-command dev loop -A `Taskfile.yml` (or `Makefile`) at the skeleton root provides: +A `Makefile` at the skeleton root provides: -- `task dev` — starts `frankenphp run --watch` against the Symfony source and launches the Qt host in dev mode in parallel; tears both down on `Ctrl-C`. -- `task make` — passthrough wrapper for `bin/console make:bridge:*` with shell-completion hints. -- `task doctor` — passthrough to `bridge:doctor`. +- `make dev` — starts `frankenphp run --watch` against the Symfony source and launches the Qt host in dev mode in parallel; tears both down on `Ctrl-C`. +- `make doctor` — passthrough to `bridge:doctor`. +- `make quality` — runs the same checks the CI `quality` job runs. -Intent: clone the skeleton, run `task dev`, code within 30 seconds. +Plain Make rather than Task/Just keeps the toolchain lean — no extra install, available on every dev box and CI runner. Scaffolding still goes through `bin/console make:bridge:*` directly. + +Intent: clone the skeleton, run `make dev`, code within 30 seconds. ### Editor support and debugging Documented in the skeleton's README so first-day setup doesn't require guesswork. - **Recommended editors:** VS Code with the Qt extension + `qmlls` for QML, plus Intelephense or Phpactor for PHP. JetBrains users get PhpStorm + Qt Creator side-by-side; both can attach to the running pair. -- **PHP debugger:** Xdebug into FrankenPHP works out of the box — `task dev:debug` starts FrankenPHP with `XDEBUG_MODE=debug,develop` and `XDEBUG_TRIGGER=1`, listening on the Symfony container's port. Skeleton ships a `.vscode/launch.json` and a `.idea/runConfigurations/` example wired up. +- **PHP debugger:** Xdebug into FrankenPHP works out of the box — `make dev-debug` starts FrankenPHP with `XDEBUG_MODE=debug,develop` and `XDEBUG_TRIGGER=1`, listening on the Symfony container's port. Skeleton ships a `.vscode/launch.json` and a `.idea/runConfigurations/` example wired up. - **QML debugger:** Qt's QML debugger attaches via `-qmljsdebugger=port:N`. The dev-mode host accepts a `--qml-debug-port=N` flag that opens it; off by default. - **Logs:** in dev mode, child stdout/stderr is forwarded to the host's stderr so everything streams to one terminal. Bundled mode writes to `var/log/` and the host surfaces a "Open log folder" menu item. @@ -566,9 +568,84 @@ Out of scope (lands in Phase 1+): optimistic updates, `Last-Event-ID` resume, pe - `framework/qml` with `BackendConnection`, `RestClient`, `MercureClient`, and `SingleInstance`. `connectionState` is wired but the Update Semantics layer (§5) is stubbed (just `Connecting`/`Online`/`Error` for now). - `BackendConnection` runs in **dev mode**: it reads a backend URL and bearer token from env / CLI flag instead of spawning a child. The developer runs FrankenPHP separately (`frankenphp run --watch` against the Symfony source). - `symfony/maker-bundle` wired in as `require-dev`; `bridge:doctor` command implemented (§8) so first-run readiness errors are actionable. -- `skeleton/` ships a `Taskfile.yml` with `task dev`, boots an empty window, acquires the single-instance lock, and connects to that dev backend. +- `skeleton/` ships a `Makefile` with `make dev`, boots an empty window, acquires the single-instance lock, and connects to that dev backend. - `.gitea/workflows/ci.yml` runs the `quality` job (PHPStan, php-cs-fixer, qmllint, PHPUnit) from day one. Per-OS `build` jobs land in Phase 4. -- Goal: clone, `task dev`, edit code, see changes — no packaging in the way. +- Goal: clone, `make dev`, edit code, see changes — no packaging in the way. + +#### Detailed scope + +Phase 1 turns the spike into the smallest dev-mode-only framework that can replace it. No bundled mode (Phase 4), no packaging, no auto-update. + +**Naming and identifiers (working, settable before any code):** + +| Thing | Value | +| --- | --- | +| Composer package | `php-qml/bridge` | +| PHP namespace | `PhpQml\Bridge\` | +| Qt module URI | `PhpQml.Bridge` | +| C++ namespace | `PhpQml::Bridge` | +| Symfony minimum | `^7.1` | +| PHP minimum | `^8.3` | +| Qt minimum | `6.5 LTS` (build), `6.11` is what's on the dev box | + +**Directory layout (additions over Phase 0):** + +```text +framework/ + php/ # Composer: php-qml/bridge + src/ + BridgeBundle.php + Bridge/{Publisher,SessionAuthenticator}.php + Controller/HealthController.php + Command/BridgeDoctorCommand.php + config/services.yaml + composer.json + phpunit.xml.dist + tests/ + qml/ # Qt module PhpQml.Bridge + src/{BackendConnection,SingleInstance,MercureClient}.{h,cpp} + qml/{AppShell.qml,RestClient.qml} + CMakeLists.txt + skeleton/ + symfony/ # Symfony app pre-wired with the bundle + composer.json + bin/console + config/{packages,routes,bundles.php} + public/index.php + src/Kernel.php + .env, .env.local + qml/ # QML app pre-wired with the module + CMakeLists.txt + main.cpp + Main.qml + Caddyfile # FrankenPHP config for dev mode + Makefile # make dev / make doctor / make quality +.gitea/ + workflows/ + ci.yml # quality job +``` + +**Sub-commits (each ends with something runnable):** + +1. **Repo restructure** — empty `framework/php`, `framework/qml`, `framework/skeleton`, `.gitea/workflows/ci.yml` stub. Update root `.gitignore`. Spike still in place. +2. **Symfony bundle** — `BridgeBundle`, `Publisher`, `HealthController`, `SessionAuthenticator` with PHPUnit smoke tests. +3. **`bridge:doctor` command** — readiness checks (env vars, Caddyfile present, FrankenPHP reachable in dev mode, Mercure JWT non-empty). +4. **Qt foundation types** — `BackendConnection` (dev mode: reads `BRIDGE_URL`, `BRIDGE_TOKEN` from env or CLI flag), `SingleInstance` (`QLocalServer` lock + arg forwarding). Buildable but not visibly useful yet. +5. **Qt transport types** — `MercureClient` (C++ SSE: `text/event-stream` parse, exponential backoff, `Last-Event-ID` resume), `RestClient.qml` (idempotency-key auto-attach, problem+json error mapping). +6. **Skeleton wiring** — Symfony app + QML app + Makefile + Caddyfile. `make dev` opens a window connected to a separately-run FrankenPHP and visibly tracks `connectionState`. Replaces the spike functionally. +7. **CI quality job** — `.gitea/workflows/ci.yml` runs PHPStan (level 6 to start), php-cs-fixer (check mode), PHPUnit, `qmllint`. Workflow file exists even if a runner isn't provisioned yet. +8. **Retire the spike** — `spike/` deleted; key lessons already captured in PLAN.md and the framework code. + +**Update Semantics is stubbed**, not realised: `connectionState` flips between `Connecting` / `Online` / `Error` only. `Reconnecting`, `Offline`, `pending`-role rollback, command queue all arrive in Phase 2 with the reactive models. + +**Done criteria:** + +- Fresh clone → `make dev` opens a window within ~3 s of FrankenPHP being ready, shows `Online`, displays a Mercure-pushed event when triggered. +- Killing the dev FrankenPHP → window flips to `Error`. Restart it → back to `Online`. +- Launching a second instance of the Qt host → first focuses, second exits. +- `bin/console bridge:doctor` flags missing config with actionable messages. +- CI's `quality` job runs (green when clean, red on real issues, not on misconfiguration). +- `spike/` is gone. ### Phase 2 — Reactive models, update semantics, and the headline maker