README is now tight and link-heavy: 60-second tour, then deep links into docs/. The wall of detail moved out. docs/ covers the framework end-to-end: - getting-started.md — prerequisites by distro (Tumbleweed, Fedora, Debian/Ubuntu, Arch), full first-run walkthrough, troubleshooting. - architecture.md — process pair, transport, dev/bundled mode. - update-semantics.md — state machine + optimistic mutations + key round-tripping. - reactive-models.md — ReactiveListModel, ReactiveObject, Mercure dual-publish. - makers.md — make:bridge:resource/command/window. - dev-workflow.md — hot reload (PHP + QML), dev console, editor configs, bridge:doctor, snapshot/integration test loops, perfsmoke. - bundled-mode.md — supervisor, per-session secret rotation, first-launch migrations, auto-update wiring. - packaging-linux.md — make appimage, build-appimage.sh CLI, AppImageUpdate sidecar, perfsmoke budgets, release CI, bundle-size breakdown. - qml-api.md / php-api.md — exhaustive symbol reference with all Q_PROPERTY/Q_INVOKABLE/signals + every public PHP service / attribute / command. - configuration.md — every env var (host, Symfony, dev script, packaging script, perfsmoke), every CLI flag (php-qml-init, build-appimage.sh), make targets, default ports/paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
201 lines
7.8 KiB
Markdown
201 lines
7.8 KiB
Markdown
# Dev workflow
|
|
|
|
Day-to-day: hot reload, dev console, editor configs, `bridge:doctor`. The skeleton README has a short version of this; here is the long version with the *why*.
|
|
|
|
## `make dev`
|
|
|
|
`make dev` is the canonical entry point. It:
|
|
|
|
1. Runs `make build` (cmake + make) for the Qt host, since QML files are baked into the binary's resource bundle.
|
|
2. Invokes [`scripts/dev.sh`](../framework/skeleton/scripts/dev.sh):
|
|
- Starts FrankenPHP under `--watch` in its own process group.
|
|
- Starts the Qt host with `BRIDGE_URL=http://127.0.0.1:8765` and `BRIDGE_TOKEN=devtoken`.
|
|
- Traps `SIGTERM` / `SIGINT` and tears down both processes (per-PID, not via `kill 0` — that broke under tmux).
|
|
|
|
Stop with `Ctrl-C`; you'll see two cleanup messages.
|
|
|
|
## Hot reload — PHP side
|
|
|
|
FrankenPHP `--watch` does the work. Save any file under `symfony/` and the next request through the host hits the new code. There's no opcache to clear and no service to restart.
|
|
|
|
Things that *don't* require even a save-reload:
|
|
- Editing a controller method body.
|
|
- Editing a service's body.
|
|
- Editing a Twig template (if you have any).
|
|
|
|
Things that need a `make:migration` + `migrate`:
|
|
- Adding/removing/renaming an entity field.
|
|
- Adding/removing an entity.
|
|
- Changing column types.
|
|
|
|
```bash
|
|
cd symfony
|
|
bin/console make:migration
|
|
bin/console doctrine:migrations:migrate -n
|
|
```
|
|
|
|
The Qt host stays up across all of this. The next time you ask Doctrine for the affected entity, the new schema is in effect.
|
|
|
|
Things that need a host restart:
|
|
- Changes to `framework/qml/src/*.cpp` (C++ — needs rebuild + relink).
|
|
- Changes to QML files baked into the QML cache (see below).
|
|
|
|
## Hot reload — QML side
|
|
|
|
QML resides in a Qt resource bundle baked into the host binary. Saving `Main.qml` does *not* automatically flip the running window. Three workflows that do:
|
|
|
|
### Qt Creator → File → Reload (`Ctrl+R`)
|
|
|
|
The lowest-friction option. Works on edits to existing QML files; new files need a rebuild because they have to be added to `QML_FILES` in CMake.
|
|
|
|
### `qmlls` live preview
|
|
|
|
[`qmlls`](https://doc.qt.io/qt-6/qtqml-tooling-qmlls.html) is the QML language server bundled with Qt 6.5+. With the *Qt for VSCode* extension or any other LSP-capable editor, you get completion, navigation, and a live preview window driven by `qmlls`. Edits show up instantly in the preview, no rebuild.
|
|
|
|
`qmlls` writes a `.qmlls.ini` next to your project; that file is git-ignored at the repo root.
|
|
|
|
### Run from source
|
|
|
|
Set `QML_IMPORT_TRACE=1` and pass `-DQT_QML_DEBUG` to the host build. Launch the host with Qt Creator's QML/JS Debugger attached. Save QML, the engine reloads in place.
|
|
|
|
This is more involved than the first two and we don't yet have it gated behind `BRIDGE_DEV=1` — see [PLAN.md §6](../PLAN.md#6-development-mode-toolchain) for the long-term plan.
|
|
|
|
## Dev console (`Ctrl+\``)
|
|
|
|
The skeleton's `Main.qml` ships a hidden `DevConsole` component bound to `Ctrl+\``:
|
|
|
|
```qml
|
|
DevConsole {
|
|
id: devConsole
|
|
visible: false
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 220
|
|
}
|
|
|
|
Shortcut {
|
|
sequences: ["Ctrl+`", "Ctrl+~"]
|
|
onActivated: devConsole.visible = !devConsole.visible
|
|
}
|
|
```
|
|
|
|
`Ctrl+\`` toggles a 220px-tall panel showing the bundled FrankenPHP child's merged stdout+stderr, scrolling live. The panel reads from a 500-line ring buffer that `BackendConnection` keeps regardless of whether the panel is open, so opening it is free — no IPC ramp-up.
|
|
|
|
The console only has content in **bundled mode**. Dev mode (`BRIDGE_URL` set) shows an "observe your terminal instead" hint, since the FrankenPHP child is owned by `make dev` and writes to its own controlling terminal.
|
|
|
|
`Ctrl+~` is provided as an alias because some keyboard layouts report the backtick as a shifted tilde and the original binding doesn't fire.
|
|
|
|
The other thing the console is good for: triggering it on a deployed AppImage to inspect why something didn't boot. The 500-line ring buffer typically catches the entire stack trace if the child died at startup.
|
|
|
|
## `bridge:doctor`
|
|
|
|
A Symfony console command that checks the dev environment is wired up correctly:
|
|
|
|
```bash
|
|
make doctor
|
|
# → bridge:doctor
|
|
# ✓ Symfony bundle present and registered
|
|
# ✓ Mercure URL reachable
|
|
# ✓ BRIDGE_TOKEN configured
|
|
# ✓ JWT secret length (≥256 bits)
|
|
# ✓ SQLite database created
|
|
# ✓ Doctrine connection works
|
|
# ✓ No pending migrations
|
|
```
|
|
|
|
Add `--connect` to also probe the Qt host's expected `BRIDGE_URL`:
|
|
|
|
```bash
|
|
make doctor-connect
|
|
# additionally:
|
|
# ✓ BackendConnection probe succeeded
|
|
# ✓ Mercure subscribe + publish round-trip
|
|
```
|
|
|
|
Run this after every `php-qml-init` or whenever something looks off. It catches ~80% of the "why doesn't dev mode work?" failures before they hit the terminal.
|
|
|
|
## Editor configs
|
|
|
|
Both the skeleton and `examples/todo` ship `.vscode/` and `.idea/runConfigurations/`.
|
|
|
|
### VSCode
|
|
|
|
`.vscode/launch.json`:
|
|
- **Listen for Xdebug (Symfony / FrankenPHP)** — attaches the debugger on port 9003. Set `XDEBUG_MODE=debug` for the FrankenPHP child:
|
|
```bash
|
|
XDEBUG_MODE=debug make dev
|
|
```
|
|
- **Run skeleton (Qt host)** — gdb-launches the host with dev-mode env vars. Use this when FrankenPHP is already running (e.g. `make dev` started in another terminal).
|
|
- **Compound: Dev: Xdebug + Qt host** — runs both at once.
|
|
|
|
`.vscode/tasks.json` — `make build`, `make dev`, `make doctor`, `make quality` (the todo example also has `make integration`, `make appimage`).
|
|
|
|
`.vscode/settings.json` — file-explorer excludes for `build/`, `vendor/`, `.qt/`, `.rcc/`, etc. and per-language tab sizes. The skeleton sets `intelephense.environment.phpVersion: "8.4.0"` so PHP IntelliSense doesn't flag 8.4-only syntax.
|
|
|
|
### PhpStorm / IntelliJ
|
|
|
|
`.idea/runConfigurations/`:
|
|
- `make dev`, `make doctor`, `make quality` (and `make appimage` in the todo example) as Shell run configs that execute in PhpStorm's terminal.
|
|
|
|
PhpStorm's Xdebug listener is global, not per-project. Toggle it via the toolbar's **Start Listening for PHP Debug Connections** button.
|
|
|
|
`.idea/.gitignore` is set up so per-IDE state (`workspace.xml`, etc.) is ignored while shared run configs are tracked.
|
|
|
|
### Why both VSCode and PhpStorm
|
|
|
|
A php-qml app is multi-language: Symfony controllers (PHP), QML, C++ host code. Different editors win at different parts:
|
|
- **VSCode** with Qt+PHP extensions: best PHP↔QML cross-edit experience.
|
|
- **PhpStorm**: superior PHP refactoring, debugger, Symfony plugin.
|
|
|
|
Ship both so people start with a working setup either way; let them remove the one they don't use.
|
|
|
|
## Snapshot test loop
|
|
|
|
Tweaking a maker template? The snapshot test in CI catches accidental drift. Locally:
|
|
|
|
```bash
|
|
cd framework/php
|
|
vendor/bin/phpunit tests/Maker/
|
|
# diff against tests/snapshot/
|
|
```
|
|
|
|
If you intentionally changed the template, regenerate the snapshot and commit it:
|
|
|
|
```bash
|
|
./tests/snapshot/run.sh
|
|
git add tests/snapshot/
|
|
```
|
|
|
|
## Integration test loop
|
|
|
|
`examples/todo/tests/integration.sh` boots the example app in dev mode, fires a real HTTP+SSE round-trip plus a crash-recover, and asserts the output. Run it after touching anything in `BackendConnection`, `MercureClient`, or `ReactiveListModel`:
|
|
|
|
```bash
|
|
cd examples/todo
|
|
make integration
|
|
```
|
|
|
|
It takes ~15 seconds and runs in CI on every push to `main`.
|
|
|
|
## Performance smoke
|
|
|
|
After packaging:
|
|
|
|
```bash
|
|
cd examples/todo
|
|
make appimage
|
|
make perf # asserts §11 budgets against build/Todo-x86_64.AppImage
|
|
```
|
|
|
|
Budgets:
|
|
- Bundle ≤ 200 MB
|
|
- Cold start ≤ 2 s (4 s on shared CI runners)
|
|
- Idle RSS ≤ 200 MB
|
|
|
|
Fail-loud: any breach exits non-zero and CI flags it. See [Linux packaging](packaging-linux.md#performance-smoke).
|
|
|
|
## See also
|
|
|
|
- [Getting started](getting-started.md) — first-time setup including PHP/Qt by distro.
|
|
- [Bundled mode](bundled-mode.md) — what `make dev` *doesn't* exercise (token rotation, supervisor).
|
|
- [Linux packaging](packaging-linux.md) — when to switch from `make dev` to `make appimage`.
|