The previous commit (0cceefc) staged the audit-driven fixes under a hypothetical v0.1.3 since v0.1.2 hadn't tagged yet. Tagging cadence decision: ship them all as v0.1.2 — there's no point spending a tag on the clean-shutdown fix alone when v0.1.3 was already lined up behind it. PLAN.md §13 + CHANGELOG.md collapse v0.1.2 + v0.1.3 into the single v0.1.2 entry, dated 2026-05-03; orphaned [0.1.3] link reference at the foot of CHANGELOG removed. No code changes — the four fixes from0cceefc+f132c3cstand as-is. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
Changelog
All notable changes to this project are documented here.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning. Pre-v1.0.0, minor bumps may break public API.
Unreleased
Added
- (none yet — next changes land here)
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 calledteardownChild(), but it ran during stack unwinding afterapp.exec()returned — by then the Qt event loop was already mid-shutdown andQProcess::waitForFinishedcouldn't reliably reap the child. Symptom: Qt loggedQProcess: Destroyed while process ("...frankenphp") is still running, frankenphp + its PHP workers became orphans. The constructor now also connectsQCoreApplication::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_pathis now actually configurable. TheBridgeResourceMakerandBridgeWindowMakerdocstrings claimed the QML scaffold path was settable via the bundle'sqml_pathoption, but the bundle'sconfigure()was empty and the constructor default ('../qml/') was the only knob.BridgeBundle::configurenow defines aqml_pathscalar node;loadExtensionexposes it as thebridge.qml_pathcontainer parameter;services.yamlbinds it into both makers. Apps can override withconfig/packages/bridge.yaml:bridge: { qml_path: ../qml/ }. Default unchanged.SessionAuthenticator: problem+json on the entry-point path.onAuthenticationFailurealready returned RFC 7807application/problem+jsonfor bad-token requests, but Symfony's defaultAuthenticationEntryPointInterface::startfired for no-token requests, returning a Form-flavoured 302/401 with the wrong shape for QML'sRestClienterror mapping. The authenticator now implementsAuthenticationEntryPointInterfaceand routes both paths through a sharedproblemJson()helper so QML sees one error shape regardless of which firewall path was taken. New test covers the entry-point response.CorrelationKeyListener::onTerminatesub-request guard.onRequestalready guarded withisMainRequest(), butonTerminatecleared unconditionally — a sub-request finishing mid-controller would wipe the main request's correlation key, causing the matching Mercure echo to lose itscorrelationKeyfield 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
- Wipe Symfony cache on bundled-mode launch. Symfony's compiled container bakes
kernel.project_diras 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) hitInvalidDirectoryfrom doctrine-migrations on the first launch-2 (and similar at any kernel.project_dir-sensitive lookup).BackendConnection::initBundledModenowrmdirs 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. HealthControllerdeep-loads the bundle. Constructor-injectsPublisherso/healthzreturns 200 only whenBridgeBundleis fully container-resolvable. v0.1.0's/healthzreturned 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 includesbundle: "PhpQml\\Bridge\\Publisher"as the canary value.- Caddyfile formatting.
framework/skeleton/Caddyfileandexamples/todo/Caddyfilereformatted withcaddy 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'sapplicationDirPath()dereferences symlinks via/proc/self/exe, so the real layout has to be mimicked closely; staged Symfony tree ischmod -R a-wto actually exercise the read-only-mount cache redirect) and exercises the supervisor end-to-end without needing a real.AppImagebuild. Asserts/healthzdeep-load + cache redirect. Wired into.gitea/workflows/ci.ymlafter the existing dev-mode integration test. - Skeleton AppImage parity.
framework/skeleton/Makefilegainsstaging-symfony+appimagetargets mirroringexamples/todo/Makefile's. Newframework/skeleton/packaging/skeleton.{desktop,png}provide minimal AppImage assembly inputs.bin/php-qml-initnow: (a) renames packaging files to match the scaffolded app name, (b) rewrites the.desktopfile'sName/Exec/Icon, (c) substitutes the newBUNDLE_SRCandPACKAGINGMakefile variables to either absolute framework paths (default) or vendored.bridge/.bridge-packagingpaths (--vendor). Scaffolded apps inheritmake appimageworking out of the box.
Notes
BackendConnection::m_portstays 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).BridgeBundlewires Doctrine subscriber,ModelPublisher,bridge:doctorconsole 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-Keyround-tripped to Mercure ascorrelationKey; in-flightpendingrole; offline overlay + reconnecting banner viaAppShell. - Headline makers (
symfony/maker-bundle):make:bridge:resource <Name>— entity (#[BridgeResource]+ UUIDv7 by default,--int-idfor 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_URLis unset (typical AppImage case),BackendConnectionspawns the embedded FrankenPHP, generates a per-session bearer token, runs first-launch migrations into~/.local/share/<app>/var/data.sqlite, and supervises the child withprctl(PR_SET_PDEATHSIG, SIGTERM)for cleanup safety. - Linux AppImage packaging.
packaging/linux/build-appimage.sh+make appimageproduce a single ~150 MB binary (Qt + Symfony + FrankenPHP + AppImageUpdate sidecar). - AppImageUpdate auto-update. Embedded
update-infoELF section points at the canonical Gitea Releases URL.BackendConnection.checkForUpdates()/applyUpdate()invoke the bundled sidecar. - Release CI (
.gitea/workflows/release.yml). Triggers onv*tags. Builds the AppImage, runstests/perfsmoke.shagainst PLAN.md §11 budgets (bundle ≤ 200 MB, cold start ≤ 4 s on shared CI runners, idle RSS ≤ 200 MB), generates zsync metadata +latest.jsonappcast +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 tomain. - 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. Copiesframework/skeleton/, rewrites identifiers, repoints the path-composer-repo and CMakeadd_subdirectory(framework/qml)reference, runscomposer installand migrations.--vendorproduces a portable copy..vscode/launch.json+tasks.json+settings.jsonand.idea/runConfigurations/shipped with skeleton and todo example.- Hot-reload story documented end-to-end (FrankenPHP
--watch, Qt Creator Reload,qmllslive 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. - Licensed under LGPL-3.0-or-later (
LICENSE+LICENSE.GPLat 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.