Files
php-qml/CHANGELOG.md

96 lines
14 KiB
Markdown
Raw Normal View History

# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Pre-v1.0.0, minor bumps may break public API.
## [Unreleased]
v0.2.0 (1/N): public API surface — interfaces + BridgeOp enum Establishes the contract layer the rest of v0.2.0 builds on. Pre-1.0 SemVer break: ModelPublisher::publishEntityChange() now takes BridgeOp instead of a raw string. Interfaces (Symfony idiom: same namespace as concrete, like HubInterface next to Hub): - PublisherInterface — publish(string, array, bool) - ModelPublisherInterface — publishEntityChange(object, BridgeOp) - CorrelationContextInterface — set/get/clear App code should typehint these instead of the concretes so swappable implementations (offline-buffer publisher, multi-hub fan-out, request- stamp correlation) remain non-breaking. Concrete classes implement them unchanged; autowire continues to inject the implementations transparently. BridgeOp: PHP 8.1 string-backed enum with cases Upsert / Delete / Replace / Event matching PLAN.md §4's envelope `op` wire format. Internal call sites updated; tests use the cases directly. Switched typehints: - ModelPublisher ctor: PublisherInterface + CorrelationContextInterface - DoctrineBridgeListener ctor: ModelPublisherInterface - HealthController ctor: PublisherInterface (still emits `Publisher` as bundle canary value — `::class` resolves to the concrete class name regardless of typehint, so bundled-supervisor.sh's grep stays green) - skeleton PingController ctor: PublisherInterface (canonical app pattern — example/todo has no Publisher consumer to update) Drive-by: removed deprecated setAccessible(true) call in ModelPublisher::extractId — PHP 8.1+ allows reflection without it. PHPStan + cs-fixer + PHPUnit (17/17) + maker snapshot all pass; dev container compiles in the example app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:50:01 +02:00
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
v0.2.0 (1/N): public API surface — interfaces + BridgeOp enum Establishes the contract layer the rest of v0.2.0 builds on. Pre-1.0 SemVer break: ModelPublisher::publishEntityChange() now takes BridgeOp instead of a raw string. Interfaces (Symfony idiom: same namespace as concrete, like HubInterface next to Hub): - PublisherInterface — publish(string, array, bool) - ModelPublisherInterface — publishEntityChange(object, BridgeOp) - CorrelationContextInterface — set/get/clear App code should typehint these instead of the concretes so swappable implementations (offline-buffer publisher, multi-hub fan-out, request- stamp correlation) remain non-breaking. Concrete classes implement them unchanged; autowire continues to inject the implementations transparently. BridgeOp: PHP 8.1 string-backed enum with cases Upsert / Delete / Replace / Event matching PLAN.md §4's envelope `op` wire format. Internal call sites updated; tests use the cases directly. Switched typehints: - ModelPublisher ctor: PublisherInterface + CorrelationContextInterface - DoctrineBridgeListener ctor: ModelPublisherInterface - HealthController ctor: PublisherInterface (still emits `Publisher` as bundle canary value — `::class` resolves to the concrete class name regardless of typehint, so bundled-supervisor.sh's grep stays green) - skeleton PingController ctor: PublisherInterface (canonical app pattern — example/todo has no Publisher consumer to update) Drive-by: removed deprecated setAccessible(true) call in ModelPublisher::extractId — PHP 8.1+ allows reflection without it. PHPStan + cs-fixer + PHPUnit (17/17) + maker snapshot all pass; dev container compiles in the example app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:50:01 +02:00
- **`PublisherInterface`, `ModelPublisherInterface`, `CorrelationContextInterface`.** The bridge's three public services now ship as interfaces (same namespace as concrete, mirroring upstream `HubInterface`/`Hub`). App controllers and listeners should typehint these instead of the concrete classes so swappable implementations (offline-buffer publisher, request-stamp correlation context, etc.) remain non-breaking. Existing `Publisher` / `ModelPublisher` / `CorrelationContext` classes implement the new interfaces unchanged.
- **`BridgeOp` enum.** PHP 8.1 string-backed enum (`Upsert` / `Delete` / `Replace` / `Event`) replacing the raw `'upsert'`/`'delete'` strings previously passed between `DoctrineBridgeListener` and `ModelPublisher::publishEntityChange`. Values match PLAN.md §4's envelope `op` wire format. Typo'd ops are now caught at the type level instead of silently producing envelopes clients ignore.
- **`BridgeBundleInfo` value object** carrying the bundle's name + class FQCN. `HealthController` now constructor-injects this instead of `PublisherInterface` as the deep-load canary, so the readiness probe is no longer coupled to the publisher's contract. `/healthz` response gains a `name` field (`php-qml/bridge`); the `bundle` field now reports `PhpQml\Bridge\BridgeBundle` (was `PhpQml\Bridge\Publisher`).
- **`Maker\Support\NameInput`** — shared interactive name prompt. All three `make:bridge:*` makers (`resource`, `command`, `window`) re-implemented the same "prompt, trim, ucfirst, reject empty" closure inline; collapsed into one call site so empty-argument and validation behaviour stay in lockstep.
- **`Maker\Support\Naming`** — `camelTo($name, $separator)` helper. Replaces inline `preg_replace('/(?<!^)[A-Z]/', $sep.'$0', $name)` regex copies (BridgeResourceMaker emits `_`-joined route plurals, BridgeCommandMaker emits `-`-joined kebab slugs).
v0.2.0 (1/N): public API surface — interfaces + BridgeOp enum Establishes the contract layer the rest of v0.2.0 builds on. Pre-1.0 SemVer break: ModelPublisher::publishEntityChange() now takes BridgeOp instead of a raw string. Interfaces (Symfony idiom: same namespace as concrete, like HubInterface next to Hub): - PublisherInterface — publish(string, array, bool) - ModelPublisherInterface — publishEntityChange(object, BridgeOp) - CorrelationContextInterface — set/get/clear App code should typehint these instead of the concretes so swappable implementations (offline-buffer publisher, multi-hub fan-out, request- stamp correlation) remain non-breaking. Concrete classes implement them unchanged; autowire continues to inject the implementations transparently. BridgeOp: PHP 8.1 string-backed enum with cases Upsert / Delete / Replace / Event matching PLAN.md §4's envelope `op` wire format. Internal call sites updated; tests use the cases directly. Switched typehints: - ModelPublisher ctor: PublisherInterface + CorrelationContextInterface - DoctrineBridgeListener ctor: ModelPublisherInterface - HealthController ctor: PublisherInterface (still emits `Publisher` as bundle canary value — `::class` resolves to the concrete class name regardless of typehint, so bundled-supervisor.sh's grep stays green) - skeleton PingController ctor: PublisherInterface (canonical app pattern — example/todo has no Publisher consumer to update) Drive-by: removed deprecated setAccessible(true) call in ModelPublisher::extractId — PHP 8.1+ allows reflection without it. PHPStan + cs-fixer + PHPUnit (17/17) + maker snapshot all pass; dev container compiles in the example app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:50:01 +02:00
### Changed
- **`ModelPublisher::publishEntityChange()` signature: `string $op``BridgeOp $op`.** Pre-1.0 SemVer break. Internal callers updated; external callers (rare) need to migrate from raw strings to enum cases.
- **Internal typehints switched to interfaces.** `ModelPublisher` constructor takes `PublisherInterface` + `CorrelationContextInterface`; `DoctrineBridgeListener` takes `ModelPublisherInterface`; `HealthController` takes `BridgeBundleInfo`; the skeleton's `PingController` takes `PublisherInterface`. Autowire continues to inject the concrete implementations transparently.
- **`/healthz` response shape.** `bundle` field's value changes from `PhpQml\Bridge\Publisher` to `PhpQml\Bridge\BridgeBundle`; new `name` field reports the Composer package name. JSON consumers ignoring unknown keys are unaffected; consumers asserting the `bundle` value need to migrate.
v0.2.0 (1/N): public API surface — interfaces + BridgeOp enum Establishes the contract layer the rest of v0.2.0 builds on. Pre-1.0 SemVer break: ModelPublisher::publishEntityChange() now takes BridgeOp instead of a raw string. Interfaces (Symfony idiom: same namespace as concrete, like HubInterface next to Hub): - PublisherInterface — publish(string, array, bool) - ModelPublisherInterface — publishEntityChange(object, BridgeOp) - CorrelationContextInterface — set/get/clear App code should typehint these instead of the concretes so swappable implementations (offline-buffer publisher, multi-hub fan-out, request- stamp correlation) remain non-breaking. Concrete classes implement them unchanged; autowire continues to inject the implementations transparently. BridgeOp: PHP 8.1 string-backed enum with cases Upsert / Delete / Replace / Event matching PLAN.md §4's envelope `op` wire format. Internal call sites updated; tests use the cases directly. Switched typehints: - ModelPublisher ctor: PublisherInterface + CorrelationContextInterface - DoctrineBridgeListener ctor: ModelPublisherInterface - HealthController ctor: PublisherInterface (still emits `Publisher` as bundle canary value — `::class` resolves to the concrete class name regardless of typehint, so bundled-supervisor.sh's grep stays green) - skeleton PingController ctor: PublisherInterface (canonical app pattern — example/todo has no Publisher consumer to update) Drive-by: removed deprecated setAccessible(true) call in ModelPublisher::extractId — PHP 8.1+ allows reflection without it. PHPStan + cs-fixer + PHPUnit (17/17) + maker snapshot all pass; dev container compiles in the example app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 19:50:01 +02:00
### Fixed
- **`ModelPublisher::extractId` reflection cleanup.** Removed the `setAccessible(true)` call (deprecated since PHP 8.1; all properties are accessible via Reflection without it).
## [0.1.2] — 2026-05-03
Bugfix release. Bundles the v0.1.1 follow-up that surfaced during the v0.1.2 cycle (bundled-mode supervisor cleanly SIGTERMs its child on host exit) with three non-breaking fixes from a post-v0.1.1 architecture audit.
### Fixed
- **Bundled supervisor: clean child shutdown.** `BackendConnection`'s destructor was the only path that called `teardownChild()`, but it ran during stack unwinding *after* `app.exec()` returned — by then the Qt event loop was already mid-shutdown and `QProcess::waitForFinished` couldn't reliably reap the child. Symptom: Qt logged `QProcess: Destroyed while process ("...frankenphp") is still running`, frankenphp + its PHP workers became orphans. The constructor now also connects `QCoreApplication::aboutToQuit``teardownChild`, so the child is SIGTERM'd while the event loop is still active. The bundled-supervisor integration test gained a clean-shutdown assertion (no Qt warning + no orphan frankenphp under the host's PGID after SIGTERM).
- **`bridge.qml_path` is now actually configurable.** The `BridgeResourceMaker` and `BridgeWindowMaker` docstrings claimed the QML scaffold path was settable via the bundle's `qml_path` option, but the bundle's `configure()` was empty and the constructor default (`'../qml/'`) was the only knob. `BridgeBundle::configure` now defines a `qml_path` scalar node; `loadExtension` exposes it as the `bridge.qml_path` container parameter; `services.yaml` binds it into both makers. Apps can override with `config/packages/bridge.yaml`: `bridge: { qml_path: ../qml/ }`. Default unchanged.
- **`SessionAuthenticator`: problem+json on the entry-point path.** `onAuthenticationFailure` already returned RFC 7807 `application/problem+json` for *bad-token* requests, but Symfony's default `AuthenticationEntryPointInterface::start` fired for *no-token* requests, returning a Form-flavoured 302/401 with the wrong shape for QML's `RestClient` error mapping. The authenticator now implements `AuthenticationEntryPointInterface` and routes both paths through a shared `problemJson()` helper so QML sees one error shape regardless of which firewall path was taken. New test covers the entry-point response.
- **`CorrelationKeyListener::onTerminate` sub-request guard.** `onRequest` already guarded with `isMainRequest()`, but `onTerminate` cleared unconditionally — 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. FrankenPHP worker mode does not currently emit sub-requests so the user-visible impact is nil, but the asymmetry was a defensive bug.
## [0.1.1] — 2026-05-03
Bugfix release closing the four follow-ups identified during the v0.1.0 shakedown. No new public API surface; `/healthz` response gains an additive `bundle` field (existing JSON consumers ignore unknown keys).
### Fixed
bundled: wipe Symfony cache on every launch — mount path bakes into cache Reproduces with the v0.1.1 AppImage on the second launch (same user data dir, fresh AppImage mount): phpqml.bridge.bundled: symfony: "/tmp/.mount_Todo-xllnOHH/..." Cannot load migrations from "/tmp/.mount_Todo-xDBkOfG/.../migrations" ^^^^^^^ stale path from PREVIOUS launch's cache Symfony compiles `kernel.project_dir` (an absolute path) into its cached container under var/cache/. We redirect var/cache into the user data dir for read-only-mount survival (v0.1.0 fix), but the *content* of that cache references the mount path that was active when the cache was built. Next launch gets a different /tmp/.mount_<random>; the cached refs are stale; first project_dir-sensitive lookup blows up (doctrine migrations was the canary; would also surface as misrouted assets, broken Twig template paths, etc.). Fix: BackendConnection::initBundledMode does QDir(cacheDir).removeRecursively() right after creating the dirs but before runMigrations spawns the doctrine subprocess. Symfony rebuilds the cache against the current mount on every launch. Cost: ~1-2s of warmup per cold start. Permanent fix is build-time cache warmup (ship the prod cache inside the AppImage, copy to user data dir on first launch, no per-launch warmup) — already tracked as a v0.2.0 item in PLAN.md §13. v0.1.1 takes the simpler always-wipe approach since it's bugfix-class. Regression guard: examples/todo/tests/bundled-supervisor.sh gains a "2nd launch from fresh staging" step that tears down the first host, re-stages a fresh fake AppImage layout (different /tmp dir = different "mount path" from BackendConnection's perspective), and asserts /healthz comes back up. Without the cache wipe, that step would fail exactly the way doctrine did in the user's report. Verified locally: - bundled-supervisor.sh passes (incl. 2nd-launch step) - Real AppImage: two consecutive launches both reach "phpqml.bridge.bundled: migrations OK" + frankenphp spawn Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:23:30 +02:00
- **Wipe Symfony cache on bundled-mode launch.** Symfony's compiled container bakes `kernel.project_dir` as an absolute path. In bundled mode that path lives inside the AppImage's FUSE mount (`/tmp/.mount_<random>`), which is regenerated every launch. So the cache from launch N referenced mount-N's path; launch N+1 (different mount) hit `InvalidDirectory` from doctrine-migrations on the first launch-2 (and similar at any kernel.project_dir-sensitive lookup). `BackendConnection::initBundledMode` now `rmdir`s the cache before each spawn. Costs ~1-2s of warmup per launch; build-time cache warmup is the permanent fix (PLAN.md §13 v0.2.0). The bundled-supervisor integration test gained a 2nd-launch-from-fresh-staging step so this regresses if forgotten.
- **`HealthController` deep-loads the bundle.** Constructor-injects `Publisher` so `/healthz` returns 200 only when `BridgeBundle` is fully container-resolvable. v0.1.0's `/healthz` returned 200 against half-loaded bundles — both the path-repo symlink dangling at runtime and the read-only-cache failure shipped green through perfsmoke as a result. Response body now includes `bundle: "PhpQml\\Bridge\\Publisher"` as the canary value.
- **Caddyfile formatting.** `framework/skeleton/Caddyfile` and `examples/todo/Caddyfile` reformatted with `caddy fmt`. The "Caddyfile input is not formatted; run 'caddy fmt --overwrite'" warning that fired on every FrankenPHP boot is gone.
### Added
- **Bundled-mode supervisor integration test** (`examples/todo/tests/bundled-supervisor.sh`, `make integration-bundled`). Stages a fake AppImage layout in `/tmp` (host binary copied — Qt's `applicationDirPath()` dereferences symlinks via `/proc/self/exe`, so the real layout has to be mimicked closely; staged Symfony tree is `chmod -R a-w` to actually exercise the read-only-mount cache redirect) and exercises the supervisor end-to-end without needing a real `.AppImage` build. Asserts `/healthz` deep-load + cache redirect. Wired into `.gitea/workflows/ci.yml` after the existing dev-mode integration test.
- **Skeleton AppImage parity.** `framework/skeleton/Makefile` gains `staging-symfony` + `appimage` targets mirroring `examples/todo/Makefile`'s. New `framework/skeleton/packaging/skeleton.{desktop,png}` provide minimal AppImage assembly inputs. `bin/php-qml-init` now: (a) renames packaging files to match the scaffolded app name, (b) rewrites the `.desktop` file's `Name`/`Exec`/`Icon`, (c) substitutes the new `BUNDLE_SRC` and `PACKAGING` Makefile variables to either absolute framework paths (default) or vendored `.bridge` / `.bridge-packaging` paths (`--vendor`). Scaffolded apps inherit `make appimage` working out of the box.
### Notes
- `BackendConnection::m_port` stays hardcoded to 8765 — port-collision between two installed php-qml apps is a real bug surfaced during v0.1.1 prep, but the fix touches every consumer that hardcodes 8765 (perfsmoke, the new bundled-supervisor test) so it's tracked as a v0.2.0 item rather than a v0.1.x bugfix.
## [0.1.0] — 2026-05-03
First public preview. Phases 0 through 4a in PLAN.md are complete plus the Phase 5 DX-polish sub-commits. Linux is the only packaged target; macOS and Windows are deferred to 4b / 4c. Tagging is the user's call (release CI runs on `v*` tags).
### Added
- **Process-pair architecture.** Qt/QML host owns rendering; bundled FrankenPHP child runs the Symfony app in worker mode. They communicate via local HTTP and Mercure SSE.
- **Symfony bundle (`php-qml/bridge`).** `BridgeBundle` wires Doctrine subscriber, `ModelPublisher`, `bridge:doctor` console command, and the `#[BridgeResource]` attribute so app code stays idiomatic Symfony.
- **Qt module (`PhpQml.Bridge`).** `BackendConnection` (lifecycle + Update Semantics state machine: Connecting / Online / Reconnecting / Offline), `RestClient`, `MercureClient`, `ReactiveListModel`, `ReactiveObject`, `AppShell`, `SingleInstance` (QLocalServer-backed lock with launch-arg forwarding), `DevConsole`.
- **Update Semantics.** Optimistic mutations with `Idempotency-Key` round-tripped to Mercure as `correlationKey`; in-flight `pending` role; offline overlay + reconnecting banner via `AppShell`.
- **Headline makers** (`symfony/maker-bundle`):
- `make:bridge:resource <Name>` — entity (`#[BridgeResource]` + UUIDv7 by default, `--int-id` for auto-increment), CRUD controller, starter `<Name>List.qml`.
- `make:bridge:command <Name>` — controller stub for non-CRUD endpoints.
- `make:bridge:window <Name>` — second-window QML scaffold.
- **Skeleton application** (`framework/skeleton`) — minimal reference app exercised by every CI job.
- **POC todo app** (`examples/todo`) — full list UI, multi-window mirror, mark-all-done command, end-to-end test of multi-window coherence and crash-recovery.
- **bundled mode.** When `BRIDGE_URL` is unset (typical AppImage case), `BackendConnection` spawns the embedded FrankenPHP, generates a per-session bearer token, runs first-launch migrations into `~/.local/share/<app>/var/data.sqlite`, and supervises the child with `prctl(PR_SET_PDEATHSIG, SIGTERM)` for cleanup safety.
- **Linux AppImage packaging.** `packaging/linux/build-appimage.sh` + `make appimage` produce a single ~150 MB binary (Qt + Symfony + FrankenPHP + AppImageUpdate sidecar).
- **AppImageUpdate auto-update.** Embedded `update-info` ELF section points at the canonical Gitea Releases URL. `BackendConnection.checkForUpdates()` / `applyUpdate()` invoke the bundled sidecar.
- **Release CI** (`.gitea/workflows/release.yml`). Triggers on `v*` tags. Builds the AppImage, runs `tests/perfsmoke.sh` against PLAN.md §11 budgets (bundle ≤ 200 MB, cold start ≤ 4 s on shared CI runners, idle RSS ≤ 200 MB), generates zsync metadata + `latest.json` appcast + `SHA256SUMS`, optionally GPG-signs them, and uploads everything to the Gitea Release.
- **Quality CI** (`.gitea/workflows/ci.yml`). PHPStan + php-cs-fixer (check) + PHPUnit + qmllint + maker snapshot test + bridge-integration test (HTTP/SSE round-trip + crash-recover) on every push to `main`.
- **DX polish (Phase 5):**
- `DevConsole.qml` — opt-in window into the bundled FrankenPHP child's merged stdout/stderr; 500-line ring buffer; `Ctrl+`` toggles it in skeleton + todo example.
- `bin/php-qml-init <name>` — bash scaffolder. Copies `framework/skeleton/`, rewrites identifiers, repoints the path-composer-repo and CMake `add_subdirectory(framework/qml)` reference, runs `composer install` and migrations. `--vendor` produces a portable copy.
- `.vscode/launch.json` + `tasks.json` + `settings.json` and `.idea/runConfigurations/` shipped with skeleton and todo example.
- Hot-reload story documented end-to-end (FrankenPHP `--watch`, Qt Creator Reload, `qmlls` live preview).
### Notes
- Tooling versions enforced: PHP 8.4+, Symfony 8, Doctrine ORM 3, Qt 6.5+, FrankenPHP 1.12.2.
- The bundle ships without `composer.lock` (it's a library); the skeleton and the todo example carry their own.
Release prep v0.1.0: LGPL-3.0-or-later + real Gitea host URL Closes the two release-prep items called out in the Phase 5 closure paragraph (a3d35a7). License: LGPL-3.0-or-later. Chosen to align with Qt 6's LGPLv3, which keeps the AppImage's relinkability obligation (PLAN.md §12) satisfied and avoids version-mixing friction with upstream Qt. Two files at the repo root: - LICENSE — LGPL-3.0 text (the project license). - LICENSE.GPL — GPL-3.0 text the LGPL-3.0 explicitly incorporates ("This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License…"). framework/php/composer.json: "license": "proprietary" → SPDX "LGPL-3.0-or-later". CHANGELOG Notes section updated with the actual license + LICENSE/LICENSE.GPL pointer. Repo URL: every `gitea.example/<org|you>/php-qml` (and `<org>/<repo>` in docs/packaging-linux.md) replaced with the real `src.bundespruefstelle.ch/magdev/php-qml`. Touched README.md, CHANGELOG.md (compare + tag links), docs/getting-started.md, docs/packaging-linux.md (build-appimage --update-info example + latest.json appcast example). PLAN.md: status line bumped to "v0.1.0 ready to tag — LGPL-3.0-or-later license shipped, repo URL fixed". Phase 5 closure paragraph rewritten to record both items resolved (rather than pending). Only remaining manual edit at tag time: CHANGELOG `[0.1.0] — TBD` → `[0.1.0] — YYYY-MM-DD` (per Keep-a-Changelog), and the actual `git tag v0.1.0 && git push --tags` itself, which triggers .gitea/workflows/release.yml. Per the branching memory, releases land on main — merge dev → main first. Verified: `make quality` from framework/skeleton green (16 tests, 45 assertions; PHPStan + cs-fixer clean; maker snapshots match). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:50:15 +02:00
- 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
bundled: SIGTERM the frankenphp child via aboutToQuit, not just the dtor Symptom (user report on v0.1.1): QProcess: Destroyed while process ("/tmp/.mount_Todo-xbNoHHL/usr/bin/frankenphp") is still running. …and the frankenphp child + its PHP workers were left orphaned after the host exited. Cause: teardownChild() was only called from ~BackendConnection. By the time that destructor runs, app.exec() has already returned, QQmlApplicationEngine is mid-destruction, and Qt's event loop is half-torn-down. waitForFinished() doesn't reliably reap the child in that window — QProcess gets destroyed by the QObject parent-chain cleanup before the kernel reports the child as exited. Fix: in BackendConnection's constructor, connect QCoreApplication::aboutToQuit → teardownChild. aboutToQuit fires while the event loop is still active and BEFORE main() starts unwinding the stack, so SIGTERM + waitForFinished can do their job properly. The destructor's teardownChild call stays as belt-and- suspenders (no-op once aboutToQuit has already cleaned up — the function is idempotent via the m_child = nullptr at its end). The connect happens unconditionally in the constructor (not just for bundled mode) because m_child is also nullptr in dev mode and teardownChild handles that with its leading `if (!m_child) return;`. Regression guard: examples/todo/tests/bundled-supervisor.sh gains a "graceful shutdown" step: - Snapshots the host's child PIDs before SIGTERM - SIGTERMs the host, waits up to 3s for clean exit - Greps the host log for "QProcess: Destroyed while" — fail if found - Iterates the snapshotted PIDs, fails on any frankenphp orphan still alive Verified locally: real AppImage + the integration test both clean up without Qt warnings or orphan processes. PLAN.md: new v0.1.2 section above v0.1.1, this is its first entry. CHANGELOG.md: [0.1.2] — TBD section with the same Fixed entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 15:49:13 +02:00
[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
Release prep v0.1.0: LGPL-3.0-or-later + real Gitea host URL Closes the two release-prep items called out in the Phase 5 closure paragraph (a3d35a7). License: LGPL-3.0-or-later. Chosen to align with Qt 6's LGPLv3, which keeps the AppImage's relinkability obligation (PLAN.md §12) satisfied and avoids version-mixing friction with upstream Qt. Two files at the repo root: - LICENSE — LGPL-3.0 text (the project license). - LICENSE.GPL — GPL-3.0 text the LGPL-3.0 explicitly incorporates ("This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License…"). framework/php/composer.json: "license": "proprietary" → SPDX "LGPL-3.0-or-later". CHANGELOG Notes section updated with the actual license + LICENSE/LICENSE.GPL pointer. Repo URL: every `gitea.example/<org|you>/php-qml` (and `<org>/<repo>` in docs/packaging-linux.md) replaced with the real `src.bundespruefstelle.ch/magdev/php-qml`. Touched README.md, CHANGELOG.md (compare + tag links), docs/getting-started.md, docs/packaging-linux.md (build-appimage --update-info example + latest.json appcast example). PLAN.md: status line bumped to "v0.1.0 ready to tag — LGPL-3.0-or-later license shipped, repo URL fixed". Phase 5 closure paragraph rewritten to record both items resolved (rather than pending). Only remaining manual edit at tag time: CHANGELOG `[0.1.0] — TBD` → `[0.1.0] — YYYY-MM-DD` (per Keep-a-Changelog), and the actual `git tag v0.1.0 && git push --tags` itself, which triggers .gitea/workflows/release.yml. Per the branching memory, releases land on main — merge dev → main first. Verified: `make quality` from framework/skeleton green (16 tests, 45 assertions; PHPStan + cs-fixer clean; maker snapshots match). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:50:15 +02:00
[0.1.0]: https://src.bundespruefstelle.ch/magdev/php-qml/releases/tag/v0.1.0