diff --git a/CHANGELOG.md b/CHANGELOG.md index 4900f20..f2a31a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ## [Unreleased] -This section tracks work landing on `dev` toward **v0.2.0** (next minor; pre-1.0 SemVer permits API breaks). See PLAN.md §13 for the full v0.2.0 scope. +### Added + +- (none yet — next changes land here) + +## [0.2.0] — 2026-05-03 + +First minor release. Pre-1.0 SemVer permits API breaks; the only one is `ModelPublisher::publishEntityChange()`'s `string $op` → `BridgeOp $op` signature change. Apps that only consumed the framework via the makers, the Doctrine listener, and the QML module are unaffected. + +The release closes the post-v0.1.2 architecture audit (interfaces, typed enum, `BridgeBundleInfo`, maker DRY, DTO-shaped controller scaffold) and delivers the §12 *Operations* row from PLAN.md (port negotiation, pre-migration auto-backup, `bridge:export`, periodic auto-update check, native-dialogs boundary doc) plus two new makers (`make:bridge:event`, `make:bridge:read-model`) and `qmltestrunner`-based QML unit tests in CI. ### Added @@ -105,7 +113,8 @@ First public preview. Phases 0 through 4a in PLAN.md are complete plus the Phase - The bundle ships without `composer.lock` (it's a library); the skeleton and the todo example carry their own. - Licensed under **LGPL-3.0-or-later** (`LICENSE` + `LICENSE.GPL` at the repo root). Chosen to align with Qt 6's LGPLv3 licensing — see PLAN.md §12 for the relinkability obligations the AppImage build already honours. -[Unreleased]: https://src.bundespruefstelle.ch/magdev/php-qml/compare/v0.1.2...HEAD +[Unreleased]: https://src.bundespruefstelle.ch/magdev/php-qml/compare/v0.2.0...HEAD +[0.2.0]: https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.2.0 [0.1.2]: https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.1.2 [0.1.1]: https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.1.1 [0.1.0]: https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.1.0 diff --git a/PLAN.md b/PLAN.md index 59527ed..c03feb5 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,6 +1,6 @@ # php-qml — Plan for a Symfony/FrankenPHP + Qt/QML Desktop Framework -> **Status (2026-05):** v0.1.0 + v0.1.1 + v0.1.2 shipped 2026-05-03 (LGPL-3.0-or-later). Planning is version-based — see §13. +> **Status (2026-05):** v0.1.0 + v0.1.1 + v0.1.2 + v0.2.0 shipped 2026-05-03 (LGPL-3.0-or-later). Planning is version-based — see §13. > > **Where else to look:** > @@ -550,41 +550,23 @@ Bugfix release. Bundles the v0.1.1 follow-up that surfaced during the cycle (cle - **`SessionAuthenticator` problem+json on entry-point path.** `onAuthenticationFailure` already returned RFC 7807 `application/problem+json` for *bad-token* requests, but Symfony's default entry point fired for *no-token* requests — yielding a Form-flavoured 302/401 instead. Implemented `AuthenticationEntryPointInterface::start`, factored the response into a `problemJson()` helper, so QML's RestClient sees one shape regardless of which path the firewall takes. Added test coverage. - **`CorrelationKeyListener::onTerminate` sub-request guard.** `onRequest` already had `isMainRequest()`, but `onTerminate` cleared unconditionally — so a sub-request finishing mid-controller would wipe the main request's correlation key, causing the matching Mercure echo to lose its `correlationKey` field and the optimistic UI to never reconcile. Defensive: real-world impact is low (FrankenPHP worker mode does not currently emit sub-requests), but cheap to fix and the obvious correctness bug. -### v0.2.0 — next minor +### v0.2.0 — shipped 2026-05-03 -Pulls in the originally-Phase-3/5-deferred items that don't need new operational dependencies, plus the smaller §12 risks **and** the public-API / DX items surfaced by the post-v0.1.2 audit. Cross-platform packaging is parked at v0.9.0 — the operational lift (self-hosted runners + platform certs) is too big to fold into the next minor and Linux remains the primary target until the framework's surface stabilises. +First minor release. Closes the post-v0.1.2 architecture audit, the §12 *Operations* row from PLAN.md (port negotiation, pre-migration auto-backup, `bridge:export`, periodic auto-update check, native-dialogs boundary doc), two new makers (`make:bridge:event`, `make:bridge:read-model`), and `qmltestrunner`-based QML unit tests in CI. Pre-1.0 SemVer permits API breaks; the only one is `ModelPublisher::publishEntityChange()`'s `string $op` → `BridgeOp $op` signature change. Full entry in `CHANGELOG.md`. -**Shipped on `dev` toward v0.2.0** (audit-driven cleanup batch — see CHANGELOG `[Unreleased]` for full notes): +**Public-API surface (audit-driven):** `PublisherInterface` / `ModelPublisherInterface` / `CorrelationContextInterface` — internal typehints switched over, autowire continues to inject the implementations. `BridgeOp` string-backed enum replacing the raw op strings. `BridgeBundleInfo` VO replacing the publisher canary in `HealthController` (`/healthz` `bundle` field reports the bundle FQCN, new `name` field reports `php-qml/bridge`). -- ✅ `PublisherInterface` / `ModelPublisherInterface` / `CorrelationContextInterface`. Concrete classes implement them; internal typehints switched over. -- ✅ `BridgeOp` enum (`Upsert` / `Delete` / `Replace` / `Event`). `ModelPublisher::publishEntityChange()` now takes `BridgeOp` instead of `string` (pre-1.0 SemVer break). -- ✅ `BridgeBundleInfo` VO. `HealthController` constructor-injects this instead of `PublisherInterface` as the deep-load canary; `/healthz` `bundle` field reports `PhpQml\Bridge\BridgeBundle`, plus a new `name` field (`php-qml/bridge`). -- ✅ `Maker\Support\NameInput::askOrFail()` + `Maker\Support\Naming::camelTo()`. The duplicated name-prompt closure and camel-conversion regex collapsed into single call sites. -- ✅ `make:bridge:resource --with-dto` opt-in. Generates `CreateDto` + `UpdateDto` and rewrites controller actions to `#[MapRequestPayload]` dispatch — closes the input-validation gap (malformed JSON → 400 problem+json automatically; no more `if (isset($data['title']))` boilerplate). Skeleton + example/todo composer.json pull `symfony/validator`. Snapshot test covers both modes. +**Maker + DX:** `Maker\Support\NameInput` + `Naming` helpers collapse the duplicated prompt closure + camel-conversion regex. `make:bridge:resource --with-dto` opt-in generates `CreateDto` + `UpdateDto` and dispatches via `#[MapRequestPayload]`, closing the input-validation gap. New `make:bridge:event` and `make:bridge:read-model` makers cover the third + fourth rows of §8's makers table. -**Still open for v0.2.0** (PLAN.md notes preserved below): +**Operations:** Bundled-mode port negotiation via `QTcpServer` port-0 trick (`var/bridge.port` sentinel for tests). Pre-migration `var/data.sqlite` auto-backup (5 most recent). `bridge:export` command + `BackendConnection.exportDatabase()` Q_INVOKABLE. Periodic auto-update check (10 s after first Online + every 6 h; `BRIDGE_AUTO_UPDATE_DISABLE=1` to opt out). -- **Generated controller `findOr404` boilerplate.** `update()` and `delete()` still inline the find-or-404 problem+json response. Audit suggested factoring a private helper or migrating to Symfony's `#[MapEntity]` attribute. MapEntity changes the 404 response shape away from problem+json unless framework-level error config is updated; a private helper is net-zero on lines. Parking until either (a) we bake skeleton-level RFC 7807 error wiring (so MapEntity preserves shape), or (b) we flip `--with-dto` to default-on and the legacy template's polish becomes irrelevant. -- **Flip `--with-dto` to default-on.** Once snapshot-test churn settles and the DTO templates have surface-feedback, make it the default and gate `--no-dto` for users who want the legacy shape. +**Docs + testing:** `docs/native-dialogs.md` (Qt.labs.platform boundary). `qmltestrunner` harness under `framework/qml/tests/` wired into CTest, `make qmltest`, and the Gitea Actions Quality job. -**Makers + reactive types (Phase 3.x deferred):** +**Still open** (carried into later minors): -- **`make:bridge:event` maker.** Generate an event class + listener stub for app-side domain events. -- **`make:bridge:read-model` maker.** Generate a read-only projection (one or more entities → one denormalised view). -- ~~**`ReactiveObject` cursor pagination.**~~ Closed N/A on inspection. ReactiveObject already has `pending` / `invoke()` / Idempotency-Key correlation / version-gap detection at parity with ReactiveListModel; the only feature it lacks is *pagination*, which is meaningless for a single-entity model. If the surface-feedback later flags a real reactive-feature gap (related-collection fetches, sub-resource navigation), that's a separate item with its own design. - -**Testing (Phase 3/5 deferred + §12 testing-strategy row):** - -- **`qmltestrunner`-driven QML unit tests.** Wires into the `quality` job alongside qmllint. -- **End-to-end UI test (Squish or Qt Test).** Was §12's deferral; bridge-integration covers IPC, this would catch UI-only regressions. - -**Operations (§12):** - -- **Bundled-mode port negotiation.** `BackendConnection::m_port` is hardcoded to 8765 with no env override or negotiation, so two php-qml apps installed on the same machine collide on first launch (whichever loses the race goes Offline). Fix: bind a transient `QTcpServer` to `QHostAddress::LocalHost` port 0, grab `serverPort()`, hand it to FrankenPHP via the `PORT` env var. Needs a port-discovery mechanism for tests/perfsmoke that currently hardcode 8765 — likely write the chosen port to a sentinel file under the user data dir on supervisor activation. Surfaced from a v0.1.1 follow-up question; deferred to v0.2.0 because the test/consumer migration is wider than v0.1.x scope. -- **Pre-migration auto-backup** (§12, *Migrations on schema change*). Supervisor copies `var/data.sqlite` to `var/data.sqlite..bak` before invoking `doctrine:migrations:migrate`; trims to N most recent. -- **`bridge:export` console command + UI hook** (§12, *Data backup / export*). Lets users copy their data out before machine moves or risky migrations. -- **Periodic auto-update check.** Phase 5 noted this as a polish item but didn't ship; v0.1.0 only has menu-triggered manual checks. -- **Native dialogs boundary doc.** §12 noted file pickers / notifications belong on the QML side via Qt — document the boundary and ship a small `Q_INVOKABLE` helper for the common cases. +- **Generated controller `findOr404` boilerplate.** Update + delete still inline the find-or-404 problem+json response. Audit suggested factoring a private helper or migrating to Symfony's `#[MapEntity]` attribute. MapEntity changes the 404 response shape away from problem+json unless framework-level RFC 7807 error config is updated; a private helper is net-zero on lines. Parking until either (a) we bake skeleton-level RFC 7807 error wiring, or (b) we flip `--with-dto` to default-on and the legacy template's polish becomes irrelevant. +- **Flip `--with-dto` to default-on.** Once surface-feedback validates the DTO templates, make it the default and gate `--no-dto` for users who want the legacy shape. +- **End-to-end UI test (Squish or Qt Test).** Was §12's deferral; bridge-integration covers IPC, this would catch UI-only regressions. Moved to a later minor because the framework / runner choice is its own decision. ### v0.3.0 — later minor