Phase 5 sub-commit 3: hot-reload docs + .vscode/.idea editor configs

Skeleton README documents the hot-reload story end-to-end:
- PHP-side: frankenphp run --watch (already what `make dev` uses).
- QML-side: Qt Creator Reload, qmlls live preview, run-from-source.
- Dev console: Ctrl+` toggle from sub-commit 1.

Both skeleton and todo example ship .vscode/ (launch.json with Xdebug
attach + Qt-host gdb launch + a compound config, tasks.json for the
make targets, settings.json) and .idea/runConfigurations/ shell run
configs for `make dev`, `make doctor`, `make quality` (and `make
appimage` in the todo example). PhpStorm's Xdebug listener is global
so we don't ship a project-level run config for it; the README
points users at the toolbar toggle.

php-qml-init also rewrites .vscode/launch.json's binary path and
config label so a fresh scaffold's debugger configs point at the
new project's binary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 21:28:02 +02:00
parent 975add1760
commit b925774eea
17 changed files with 395 additions and 1 deletions

View File

@@ -151,6 +151,14 @@ sed -i \
-e "s|\$(BUILD_DIR)/skeleton|\$(BUILD_DIR)/$NAME|g" \ -e "s|\$(BUILD_DIR)/skeleton|\$(BUILD_DIR)/$NAME|g" \
"$TARGET/Makefile" "$TARGET/Makefile"
# .vscode/launch.json: binary path + config label both mention `skeleton`.
if [ -f "$TARGET/.vscode/launch.json" ]; then
sed -i \
-e "s|build/qml/skeleton|build/qml/$NAME|g" \
-e "s|Run skeleton (Qt host)|Run $NAME (Qt host)|g" \
"$TARGET/.vscode/launch.json"
fi
# ── Path-repo: absolute reference, or vendor a copy ────────────────── # ── Path-repo: absolute reference, or vendor a copy ──────────────────
COMPOSER_JSON="$TARGET/symfony/composer.json" COMPOSER_JSON="$TARGET/symfony/composer.json"
[ -f "$COMPOSER_JSON" ] || die "skeleton missing symfony/composer.json (corrupt copy?)" [ -f "$COMPOSER_JSON" ] || die "skeleton missing symfony/composer.json (corrupt copy?)"

5
examples/todo/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Track only the shared run configs; ignore per-user IDE state.
*
!.gitignore
!runConfigurations/
!runConfigurations/*.xml

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="make appimage" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="make appimage" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="make dev" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="make dev" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="make doctor" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="make doctor" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="make quality" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="make quality" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

41
examples/todo/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug (Symfony / FrankenPHP)",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"${workspaceFolder}/symfony": "${workspaceFolder}/symfony"
},
"log": false
},
{
"name": "Run todo (Qt host)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/qml/todo",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{ "name": "BRIDGE_URL", "value": "http://127.0.0.1:8765" },
{ "name": "BRIDGE_TOKEN", "value": "devtoken" }
],
"preLaunchTask": "make build",
"MIMode": "gdb",
"linux": { "MIMode": "gdb" },
"osx": { "MIMode": "lldb" }
}
],
"compounds": [
{
"name": "Dev: Xdebug + Qt host",
"configurations": [
"Listen for Xdebug (Symfony / FrankenPHP)",
"Run todo (Qt host)"
]
}
]
}

23
examples/todo/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"files.exclude": {
"**/build": true,
"**/.qt": true,
"**/.rcc": true,
"**/var/cache": true,
"**/var/log": true,
"**/vendor": true,
"**/packaging/linux/tools": true
},
"search.exclude": {
"**/build": true,
"**/vendor": true,
"**/.qt": true,
"**/.rcc": true,
"**/packaging/linux/tools": true
},
"[php]": { "editor.tabSize": 4 },
"[qml]": { "editor.tabSize": 4 },
"[cpp]": { "editor.tabSize": 4 },
"intelephense.environment.phpVersion": "8.4.0",
"qt-qml.qmlls.useQmlImportPathEnvVar": true
}

50
examples/todo/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "make build",
"type": "shell",
"command": "make",
"args": ["build"],
"group": { "kind": "build", "isDefault": true },
"problemMatcher": ["$gcc"]
},
{
"label": "make dev",
"type": "shell",
"command": "make",
"args": ["dev"],
"isBackground": true,
"presentation": { "reveal": "always", "panel": "dedicated" },
"problemMatcher": []
},
{
"label": "make doctor",
"type": "shell",
"command": "make",
"args": ["doctor"],
"problemMatcher": []
},
{
"label": "make quality",
"type": "shell",
"command": "make",
"args": ["quality"],
"problemMatcher": ["$gcc"]
},
{
"label": "make integration",
"type": "shell",
"command": "make",
"args": ["integration"],
"problemMatcher": []
},
{
"label": "make appimage",
"type": "shell",
"command": "make",
"args": ["appimage"],
"problemMatcher": ["$gcc"]
}
]
}

5
framework/skeleton/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Track only the shared run configs; ignore per-user IDE state.
*
!.gitignore
!runConfigurations/
!runConfigurations/*.xml

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="make dev" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="make dev" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="make doctor" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="make doctor" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="make quality" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="make quality" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

41
framework/skeleton/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug (Symfony / FrankenPHP)",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"${workspaceFolder}/symfony": "${workspaceFolder}/symfony"
},
"log": false
},
{
"name": "Run skeleton (Qt host)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/qml/skeleton",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [
{ "name": "BRIDGE_URL", "value": "http://127.0.0.1:8765" },
{ "name": "BRIDGE_TOKEN", "value": "devtoken" }
],
"preLaunchTask": "make build",
"MIMode": "gdb",
"linux": { "MIMode": "gdb" },
"osx": { "MIMode": "lldb" }
}
],
"compounds": [
{
"name": "Dev: Xdebug + Qt host",
"configurations": [
"Listen for Xdebug (Symfony / FrankenPHP)",
"Run skeleton (Qt host)"
]
}
]
}

View File

@@ -0,0 +1,22 @@
{
"files.exclude": {
"**/build": true,
"**/.qt": true,
"**/.rcc": true,
"**/var/cache": true,
"**/var/log": true,
"**/vendor": true
},
"search.exclude": {
"**/build": true,
"**/vendor": true,
"**/.qt": true,
"**/.rcc": true
},
"[php]": { "editor.tabSize": 4 },
"[qml]": { "editor.tabSize": 4 },
"[cpp]": { "editor.tabSize": 4 },
"intelephense.environment.phpVersion": "8.4.0",
"intelephense.files.associations": ["*.php", "*.phtml"],
"qt-qml.qmlls.useQmlImportPathEnvVar": true
}

36
framework/skeleton/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "make build",
"type": "shell",
"command": "make",
"args": ["build"],
"group": { "kind": "build", "isDefault": true },
"problemMatcher": ["$gcc"]
},
{
"label": "make dev",
"type": "shell",
"command": "make",
"args": ["dev"],
"isBackground": true,
"presentation": { "reveal": "always", "panel": "dedicated" },
"problemMatcher": []
},
{
"label": "make doctor",
"type": "shell",
"command": "make",
"args": ["doctor"],
"problemMatcher": []
},
{
"label": "make quality",
"type": "shell",
"command": "make",
"args": ["quality"],
"problemMatcher": ["$gcc"]
}
]
}

View File

@@ -6,7 +6,7 @@ The framework's reference application: a minimal Symfony backend plus a Qt/QML h
- Linux (other platforms land in Phase 4) - Linux (other platforms land in Phase 4)
- Qt 6.5+ dev packages (`qt6-base-devel`, `qt6-declarative-devel`, `qt6-quickcontrols2-devel`, `qt6-tools-devel`), CMake, gcc-c++ - Qt 6.5+ dev packages (`qt6-base-devel`, `qt6-declarative-devel`, `qt6-quickcontrols2-devel`, `qt6-tools-devel`), CMake, gcc-c++
- PHP 8.3+ - PHP 8.4+ (Symfony 8 enforces this)
- [FrankenPHP](https://frankenphp.dev/) on PATH (or set `FRANKENPHP=/path/to/frankenphp`) - [FrankenPHP](https://frankenphp.dev/) on PATH (or set `FRANKENPHP=/path/to/frankenphp`)
- Composer - Composer
@@ -63,6 +63,50 @@ curl -X POST http://127.0.0.1:8765/api/todos \
The Mercure SSE stream receives a `correlationKey: my-key-1` envelope which the Qt host's `ReactiveListModel` matches against any in-flight optimistic mutation (PLAN.md §5). The Mercure SSE stream receives a `correlationKey: my-key-1` envelope which the Qt host's `ReactiveListModel` matches against any in-flight optimistic mutation (PLAN.md §5).
## Hot reload
Both halves of the app reload without re-running `make dev`.
### PHP-side (Symfony / FrankenPHP)
`make dev` runs `frankenphp run --watch` (see `Caddyfile` and `scripts/dev.sh`). FrankenPHP rebuilds the worker on any change under `symfony/` — controllers, services, entities, templates, configuration. Just save the file; the next request through the Qt host hits the new code. There is no opcache to clear and no service to restart.
If you change something Doctrine-mapped, run a fresh migration in another terminal:
```bash
cd symfony
bin/console make:migration
bin/console doctrine:migrations:migrate -n
```
The Qt host stays up across all of this.
### QML-side
The Qt host loads QML from a compiled-in resource bundle, so saving a `.qml` file does **not** flip the running window automatically. Three workflows that do:
- **Qt Creator → File → Reload** (or `Ctrl+R` with focus on a QML file). Rebuilds the QML cache and reloads the window in place.
- **`qmlls` live preview** — the QML language server bundled with Qt 6.5+ runs a live preview connected to your editor (VSCode + the Qt extension, neovim, helix). Edits show up instantly in the preview window without rebuilding.
- **Run from source** — start the host with `QT_QUICK_CONTROLS_CONF=` and `QML_IMPORT_TRACE=1` set, and pass `-DQT_QML_DEBUG` so the running engine accepts a hot-reload connection from Qt Creator. PLAN.md §6 captures the long-term plan to gate this behind `BRIDGE_DEV=1`.
For most edits, Qt Creator's *Reload* is the lowest-friction option. The `.qmlls.ini` file (auto-generated when `qmlls` first runs) configures completion + live preview against this project's QML import paths.
### Editor configs
Both `.vscode/` and `.idea/runConfigurations/` ship with the skeleton.
VSCode (`.vscode/launch.json`):
- **Listen for Xdebug** — attaches the debugger on port 9003 once you set `XDEBUG_MODE=debug` for the FrankenPHP child (e.g. `XDEBUG_MODE=debug make dev`).
- **Run skeleton (Qt host)** — gdb-launches the built binary with `BRIDGE_URL=http://127.0.0.1:8765` so it talks to the dev mode FrankenPHP started elsewhere by `make dev`.
- **Compound: Dev: Xdebug + Qt host** — runs both at once.
PhpStorm (`.idea/runConfigurations/`): `make dev`, `make doctor`, `make quality` shell run configs. PHP debugging is via the toolbar's **Start Listening for PHP Debug Connections** toggle (PhpStorm's Xdebug listener is global, not per-project).
### Dev console
`Ctrl+`` toggles an in-window console showing the bundled FrankenPHP child's stdout + stderr (PLAN.md §8). It's a passive ring buffer (~500 lines) — opening it has no IPC cost. Use it when you don't have a separate terminal to read the dev log.
## Quality checks ## Quality checks
```bash ```bash