The README still framed the project as "Phase 5 / pre-v0.1.0" and the docs predated the v0.2.0 surface (typed BridgeOp, public service interfaces, port negotiation, pre-migration auto-backup, bridge:export, periodic auto-update, two new makers, qmltestrunner). Bring them in line with what's actually shipped, and add badges (release, license, PHP, Symfony, Qt, FrankenPHP, CI, platform) to the README so the status is legible at a glance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
220 lines
8.7 KiB
Markdown
220 lines
8.7 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/
|
|
```
|
|
|
|
## QML unit tests (`make qmltest`)
|
|
|
|
`framework/qml/tests/` ships a [Qt Quick Test](https://doc.qt.io/qt-6/qtquicktest-index.html) executable target (`qml_unit_tests`) discovered by CTest. Built only when CMake is configured with `-DBUILD_TESTING=ON`, so production AppImages don't carry it.
|
|
|
|
Locally:
|
|
|
|
```bash
|
|
cd framework/skeleton # or examples/todo / a php-qml-init'd project
|
|
make qmltest
|
|
# → cmake -DBUILD_TESTING=ON -S qml -B build/qml
|
|
# → cmake --build build/qml --target qml_unit_tests
|
|
# → ctest --test-dir build/qml --output-on-failure
|
|
# ✓ tst_smoke.qml passed
|
|
```
|
|
|
|
Add per-feature tests next to `tst_smoke.qml` as `tst_<feature>.qml` — Quick Test auto-discovers them. Tests run under the `offscreen` Qt platform plugin so CI doesn't need `xvfb`.
|
|
|
|
This is wired into `make quality` (skeleton + todo example) and into the Gitea Actions `Quality` job after qmllint, so QML regressions fail the build alongside PHP regressions.
|
|
|
|
## 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`.
|