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>
7.9 KiB
7.9 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.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.