diff --git a/PLAN.md b/PLAN.md index b152237..69e229e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -655,6 +655,36 @@ framework/ - `make:bridge:resource` maker implemented end-to-end (entity + controller + lifecycle wiring + QML snippet). - Convention test: run `bin/console make:bridge:resource Todo`, then `make:migration` and `doctrine:migrations:migrate`; verify a QML `ListView` updates on backend changes triggered from a CLI command. No handwritten glue between the two sides. +#### Phase 2 detailed scope + +Phase 2 turns the framework from "transports work" into "you can ship a reactive list-of-X with three commands". After this phase, the smallest working bridge app is `make:bridge:resource Foo && make:migration && doctrine:migrations:migrate` plus a `List.qml` snippet — and the list updates live as `Foo` rows change. + +**Stack additions (skeleton):** + +| Thing | Choice | +| --- | --- | +| ORM | Doctrine ORM 3.x + DoctrineBundle + DoctrineMigrationsBundle | +| Dev DB | SQLite at `var/data.sqlite` (zero-config) | +| Default ID type | auto-incrementing `int` (the maker takes a flag for UUIDv7 if asked) | +| Pagination | cursor-based (opaque base64-JSON of `{lastId, lastSortKey}`), default page size 50 | +| Doctrine→Mercure trigger | `postPersist` / `postUpdate` / `postRemove` event subscribers (synchronous) | + +**Sub-commits (each ends runnable):** + +1. **Doctrine + migrations into the skeleton.** `composer require doctrine/orm doctrine/doctrine-bundle doctrine/doctrine-migrations-bundle`, generate `config/packages/doctrine.yaml` and `doctrine_migrations.yaml`, point the dev DB at `var/data.sqlite`. `bridge:doctor` gains a `database reachable` check. `make doctor` is green on a fresh clone after `make install` + `bin/console doctrine:migrations:migrate`. +2. **`ModelPublisher` (PHP) + Doctrine subscriber.** New service in `framework/php/src/`: takes a Doctrine entity + change op + correlation key, computes the envelope and dual-publishes to `app://model/{name}` (collection topic) and `app://model/{name}/{id}` (entity topic). The subscriber introspects entities tagged with `#[BridgeResource]` and routes lifecycle events through `ModelPublisher`. PHPUnit covers the envelope shape, dual publish, and correlation-key passthrough. +3. **Reactive models + full Update Semantics (QML).** `ReactiveListModel` (`QAbstractListModel` + topic subscription + initial fetch + cursor-driven `fetchMore` + `pending` role + diff application). `ReactiveObject` (single-entity equivalent). `BackendConnection`'s enum extended to `Connecting / Online / Reconnecting / Offline` with thresholds (PLAN.md §5). `AppShell.qml` ships a `Reconnecting` top banner and `Offline` overlay with retry. Optimistic command wiring: `RestClient.invoke()` returns a Promise that resolves on the matching Mercure echo (correlation-key-matched), rolls back on `4xx`/`5xx` or timeout (default 10s). +4. **`make:bridge:resource` maker.** `symfony/maker-bundle` becomes a `require-dev` of the bundle. `BridgeResourceMaker` generates: `src/Entity/.php` (`#[BridgeResource]` attribute, `id` + `title` stub fields), `src/Controller/Controller.php` (CRUD on `/api/`), and `qml/List.qml` (a starter `ListView` bound to a `ReactiveListModel`). After-hint points at `make:migration`. Lifecycle wiring is automatic (the subscriber from sub-commit 2 handles any `#[BridgeResource]` entity), so no per-resource listener is generated. The maker output is checked into the skeleton as a regression reference for Phase 3's CI snapshot test. +5. **Convention test + phase closure.** Run the maker against a `Todo` resource, run migrations, trigger inserts/updates/deletes via `bin/console` (a one-liner) and confirm the skeleton's QML window shows the list updating live, with row-level `pending` rendering during the brief in-flight window. Capture a short `framework/skeleton/README.md` walkthrough so future readers can reproduce. + +**Done criteria:** + +- `make:bridge:resource Todo` plus `make:migration` plus `doctrine:migrations:migrate` produces a working reactive list with no handwritten bridge glue. +- Triggering CRUD via `bin/console` updates the QML `ListView` within ~50 ms of the SQL commit. +- Killing FrankenPHP mid-mutation: `connectionState` transitions to `Reconnecting` then `Offline`; the optimistic row stays `pending` until rollback fires; reconnect re-fetches and clears. +- `make quality` stays green (PHPStan, cs-fixer, PHPUnit, qmllint). +- The skeleton's checked-in maker output is byte-for-byte the same as a fresh maker run, so Phase 3's CI snapshot test has a baseline. + ### Phase 3 — POC application, testing infrastructure (built via the makers) - Build `examples/todo` by running the makers — `make:bridge:resource Todo`, `make:bridge:command MarkAllDone`, `make:bridge:window TodoWindow`. The example doubles as a maker-output regression test (CI diffs generator output against a checked-in reference).