6 Commits

Author SHA1 Message Date
fcf7dc26cf qml: silence skeleton + todo Main.qml qmllint warnings
Some checks failed
CI / Quality (push) Successful in 5m3s
Release / Linux AppImage (push) Failing after 4m50s
Two warnings, two distinct kinds of fix:

1. `Item { width: 12 }` (skeleton:77) — explicit width on a
   layout-managed Item is undefined behaviour per qmllint. Replaced
   with `Item { Layout.preferredWidth: 12 }`, matching the pattern
   already used at line 85 (`Item { Layout.fillWidth: true }`). Real
   fix, not a suppression.

2. `target: SingleInstance` (skeleton:48, todo/Main.qml:220) — false
   positive. SingleInstance is intentionally a context property set
   by main() before the QML engine boots (see SingleInstance.h
   doc comment), so qmllint can't see it via static analysis.
   Disabled the `unqualified` warning at the call site with an inline
   `// qmllint disable unqualified` directive plus a one-line
   explanation comment above. (Note: the disable directive parses
   every word after `disable` as a category name, so the prose has
   to live on the previous line — found the hard way after qmllint
   complained about the "unknown category" of every English word in
   the explanation.)

Verified `make quality` from framework/skeleton green (qmllint clean
across both targets — `php_qml_bridge_qmllint` and `skeleton_qmllint`
both build with zero warnings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:05:17 +02:00
4c15ac281c Phase 5 sub-commit 1: DevConsole + child-output capture + Ctrl+` toggle
BackendConnection now captures the bundled FrankenPHP child's merged
stdout+stderr into a 500-line ring buffer, mirrors each line through
qCInfo(lcBundled) so terminal users still see logs, and exposes
childLogTail() / childLogLine for QML.

DevConsole.qml is an opt-in monospaced viewer with auto-scroll + clear
that the skeleton and the todo example bind to Ctrl+`. Dev mode (when
BRIDGE_URL is set, no bundled child) renders an explanatory hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:58:53 +02:00
1964a52f99 Phase 2 sub-commit 5: convention test passes, skeleton walkthrough, phase 2 closed
Some checks failed
CI / Quality (push) Failing after 1m50s
Runs `make:bridge:resource Todo` against the skeleton, then `make:migration`
+ `doctrine:migrations:migrate`, and verifies the round-trip end-to-end:

  - POST /api/todos creates a row with a UUIDv7 id
  - GET /api/todos returns the row
  - Mercure dual-publishes:
    - app://model/todo (collection topic)
    - app://model/todo/{uuid} (entity topic)
  - The published envelope shape matches PLAN.md §4 exactly:
    {op:"upsert", id:..., version:..., data:{...}, correlationKey:"..."}
  - correlationKey echoes the request's Idempotency-Key, ready to be
    matched by ReactiveListModel's pending state on the QML side.

Generated files committed as the regression baseline (Phase 3 will add
a CI check that re-running the maker reproduces these byte-for-byte):

  - framework/skeleton/symfony/src/Entity/Todo.php
  - framework/skeleton/symfony/src/Controller/TodoController.php
  - framework/skeleton/symfony/migrations/Version20260502004612.php
  - framework/skeleton/qml/TodoList.qml

framework/skeleton/README.md captures the three-command flow plus a
curl walkthrough so future readers can reproduce. Phase 2 done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 02:49:23 +02:00
030502ca38 Phase 2 sub-commit 3: full Update Semantics + ReactiveListModel + AppShell
Some checks failed
CI / Quality (push) Failing after 1m45s
BackendConnection's ConnectionState enum is now Connecting / Online /
Reconnecting / Offline (PLAN.md §5). The probe loop tracks the first
failure since the last Online and transitions to Reconnecting on any
failed probe, then to Offline once the configurable threshold (30 s
default) is exceeded. The Error state is gone; Reconnecting + the
exposed `error` string subsume its UI role.

ReactiveListModel is the headline QML type:

- QAbstractListModel that GETs `baseUrl + source` for an initial JSON
  array and then keeps in sync via an internal MercureClient subscribed
  to `topic`.
- Role names are derived dynamically from the first row's keys plus an
  internal `pending` boolean role used by optimistic mutations.
- Diff application: upsert (insert-or-update), delete, replace; gap
  detection via the envelope `version` field with auto re-fetch.
- `invoke(method, path, body, optimistic)` is the optimistic command
  primitive. Generates an Idempotency-Key, applies the local diff,
  POST/PATCH/DELETEs with that key, and resolves on the matching
  Mercure echo (correlation-key matched in ModelPublisher's envelope).
  Rolls back and emits commandFailed on 4xx/5xx, commandTimedOut after
  10 s without an echo. Phase 4 packaging will surface configuration
  for the timeout.

AppShell.qml is the optional convenience root:

- Reads BackendConnection.connectionState.
- Reconnecting → top banner.
- Offline → modal overlay with the error string and a Retry button
  (calls BackendConnection.restart()).
- Wraps user content via `default property alias content`.

Apps that want full chrome control can skip AppShell entirely; the
skeleton's Main.qml keeps its own status display for demonstration
and is unaffected.

ReactiveObject (single-entity twin of ReactiveListModel) is intentionally
deferred — same envelope handling, smaller surface; will land in Phase 2
follow-up or Phase 3 alongside the todo example. Cursor pagination is
similarly deferred (the Phase 2 done criterion uses small lists).

Smoke tested: /healthz + /api/ping round-trip cleanly, zero Mercure 401s,
clean shutdown. composer quality stays green (16 tests, 45 assertions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 02:40:12 +02:00
7323b9affe Phase 1 sub-commit 7: CI quality job
Some checks failed
CI / Quality (push) Has been cancelled
PHPStan (level 6 + symfony extension) and PHP CS Fixer (Symfony +
PHP83Migration ruleset) configs at framework/php/. composer.json
exposes phpstan / cs:check / cs:fix / phpunit / quality scripts.
PHPStan-clean across the bundle; cs:check is happy after auto-fix
applied @Symfony idioms (yoda, leading-backslash JSON_*, blank-line
before return). Test mocks consolidated into a HubSpy helper to keep
PHPStan happy about by-ref captures.

Skeleton's Makefile target `quality` chains `composer quality` (in
framework/php/) with cmake's all_qmllint target. Local run is green —
11 tests / 32 assertions, no PHPStan errors, cs-fixer clean, qmllint
emits advisory warnings only.

Layout fix in skeleton's Main.qml: status-dot Rectangles inside
RowLayout now use Layout.preferredWidth/Height instead of width/height
to satisfy Quick.layout-positioning checks.

.gitea/workflows/ci.yml replaces the placeholder with a real `quality`
job: setup-php, composer install (cached), the four PHP checks, Qt 6
via install-qt-action (cached), QML module build, qmllint via the
all_qmllint CMake target. Workflow exists from this commit onward
even if a runner isn't provisioned yet.

bridge:doctor lost the Publisher dependency since it was only used as
a "service is wired" marker — the command being injectable already
proves that.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 02:15:06 +02:00
d671b26cac Phase 1 sub-commit 6: skeleton wiring — make dev runs end-to-end
All checks were successful
CI / Quality (push) Successful in 5s
Symfony app under framework/skeleton/symfony/: minimal bin/console,
public/index.php, MicroKernel-based src/Kernel.php, services.yaml,
framework/security/mercure config, and a demo App\Controller\PingController
that GETs /api/ping (returning JSON pong) and republishes the same
payload to the Mercure topic app://ping. composer.json uses a path
repository to symlink the bundle from ../../php so local edits are
picked up live.

QML app under framework/skeleton/qml/: top-level CMake that
add_subdirectory's framework/qml, a main.cpp that creates the Qt
process, runs SingleInstance.acquireOrForward before any QML loads,
exposes SingleInstance via context property, and loadFromModule's
Skeleton.Main. Main.qml uses BackendConnection / RestClient /
MercureClient from PhpQml.Bridge and renders status dots, a Ping
button, and an event log.

Caddyfile binds 127.0.0.1:8765, enables in-memory Mercure with a
256-bit dev JWT (matches symfony/.env, lcobucci/jwt requires this).
Makefile wraps build / dev / doctor / clean; scripts/dev.sh starts
FrankenPHP --watch and the Qt host together with explicit PID-based
teardown (process-group `kill 0` proved unreliable when frankenphp's
watch fork reparented).

Bug fixes uncovered in this sub-commit:
- SingleInstance.acquireOrForward: probe-first, then removeServer +
  retry-listen. The original loop-with-removeServer-after-failed-bind
  silently exited on stale sockets from prior runs.
- Main.qml: MercureClient does NOT inherit BackendConnection.token —
  Mercure subscribes anonymously in dev (Caddyfile), and forwarding
  the bridge bearer made it 401-loop.
- /api/ping was 500ing because the dev MERCURE_JWT_SECRET was 144 bits;
  bumped to 64-char (>=256 bit) to satisfy lcobucci/jwt.
- Linked the framework lib (php_qml_bridge) explicitly in addition to
  the QML plugin so SingleInstance.h resolves.
- Auto-generated config/reference.php gitignored.

Smoke verified offscreen: /healthz 200, /api/ping 200, 1 publish, 1
subscriber, zero 401s, clean shutdown with no zombies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 01:50:16 +02:00