Detail Phase 1 spec; switch task runner from Task to Make

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 00:25:58 +02:00
parent 9655b6fef9
commit 16dfffd916

93
PLAN.md
View File

@@ -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