# examples/todo The framework's POC application: a real Qt/QML todo app whose backend is a Symfony service generated entirely by the `make:bridge:*` makers. Demonstrates every architectural primitive in PLAN.md §13 Phase 3. ## What's here that wasn't in the skeleton - `Todo` resource (entity + controller + QML snippet) generated via `make:bridge:resource Todo`. - `MarkAllDone` command generated via `make:bridge:command MarkAllDone`, with a body that flips `done = true` on every todo. - `TodoWindow.qml` second-window scaffold generated via `make:bridge:window Todo`, customised to embed a read-only mirror of the same `ReactiveListModel`. - `Main.qml` rewritten as a real list UI with add input, per-row toggle/delete, "mark all done", and "open second window". Everything cross-side (PHP ↔ QML) is maker-generated. There is no handwritten bridge glue in this example. ## Run it ```bash make install # composer install make build # cmake + qt host make doctor # bridge:doctor — readiness check make dev # FrankenPHP --watch + Qt host ``` Add a few todos, toggle them, click "Mark all done", click "Open second window" — both windows stay in sync. ## Multi-window test 1. `make dev` starts the app. 2. Add three todos via the input field. 3. Click **Open second window**. A `Todos (mirror)` window opens. 4. In the main window, toggle one of the todos. The mirror flips its row within ~50 ms (Mercure SSE). 5. Add a new todo in the main window. It appears in the mirror within ~50 ms. 6. Click **Mark all done** in the main window. Both windows show all rows ticked simultaneously. Each window has its own `ReactiveListModel` instance subscribed to the same `app://model/todo` topic; the framework keeps them coherent without per-window glue. ## Crash-and-recover test 1. `make dev` is running. 2. Add a todo so the list is non-empty. 3. From another terminal: `pkill -f 'examples/todo/symfony.*frankenphp'`. 4. The app's `AppShell` shows the **Reconnecting** banner (visible on the second window; the main window keeps its own status display). 5. Restart the FrankenPHP child: from the example dir, `cd symfony && frankenphp run --watch --config ../Caddyfile` — or just re-run `make dev`. 6. The app flips back to **Online** and re-fetches the list. No row corruption. ## Layout ```text todo/ Caddyfile # FrankenPHP / Mercure config (port 8765, anonymous SSE) Makefile # build / dev / doctor / quality scripts/dev.sh symfony/ composer.json # path repo points one level deeper at framework/php config/ src/Entity/Todo.php # generated by make:bridge:resource src/Controller/TodoController.php # generated, CRUD on /api/todos src/Controller/MarkAllDoneController.php # generated, body filled in public/index.php bin/console .env migrations/ qml/ CMakeLists.txt main.cpp Main.qml # real todo UI (add / toggle / delete / mark-all / 2nd-window) TodoList.qml # generated; also reused by the second window TodoWindow.qml # generated, customised to embed mirror list ``` ## What this example proves - The headline workflow scales: a non-trivial app's PHP/QML wiring is **all generated**. - `ReactiveListModel` handles in-flight optimistic mutations (`pending` role drops opacity on toggled rows until the Mercure echo lands). - Two windows of the same QML app stay coherent under mutations from either side. - Stopping FrankenPHP mid-session triggers `Reconnecting` → `Offline` UI; restart restores `Online` and re-fetches without dupes. - `Idempotency-Key` round-trips through to Mercure as `correlationKey` — visible in `dev.log` if you `grep correlationKey`. ## Out of scope here - A CI-driven version of the multi-window / crash-recover tests lives in Phase 3 sub-commit 4 as a bridge-integration suite. - No persistence options (export, backup) — same SQLite `var/data.sqlite` as the skeleton. Apps move to Postgres by overriding `DATABASE_URL`.