2026-05-02 00:59:06 +02:00
|
|
|
# php-qml framework — Qt module (PhpQml.Bridge).
|
|
|
|
|
#
|
2026-05-02 01:18:43 +02:00
|
|
|
# Designed to be add_subdirectory()'d from the consuming application's
|
|
|
|
|
# top-level CMakeLists.txt (see framework/skeleton/qml/CMakeLists.txt
|
|
|
|
|
# arriving in Phase 1 sub-commit 6). Standalone configuration also
|
|
|
|
|
# works for module-only sanity builds.
|
2026-05-02 00:59:06 +02:00
|
|
|
|
|
|
|
|
cmake_minimum_required(VERSION 3.21)
|
|
|
|
|
|
2026-05-02 01:18:43 +02:00
|
|
|
if(NOT DEFINED PROJECT_NAME)
|
|
|
|
|
project(php_qml_bridge LANGUAGES CXX)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
set(CMAKE_CXX_STANDARD 20)
|
|
|
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
|
|
|
set(CMAKE_AUTOMOC ON)
|
|
|
|
|
|
2026-05-02 00:59:06 +02:00
|
|
|
if(NOT TARGET Qt6::Core)
|
|
|
|
|
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Gui Quick Qml Network)
|
2026-05-02 01:18:43 +02:00
|
|
|
qt_standard_project_setup(REQUIRES 6.5)
|
2026-05-02 00:59:06 +02:00
|
|
|
endif()
|
2026-05-02 01:18:43 +02:00
|
|
|
|
|
|
|
|
qt_add_qml_module(php_qml_bridge
|
|
|
|
|
URI PhpQml.Bridge
|
|
|
|
|
VERSION 1.0
|
|
|
|
|
STATIC
|
qml: pin OUTPUT_DIRECTORY of PhpQml.Bridge to match its URI path
qmllint resolves QML modules by walking the import path looking for a
directory layout that mirrors the URI (PhpQml.Bridge → PhpQml/Bridge/).
qt_add_qml_module's OUTPUT_DIRECTORY defaults to CMAKE_CURRENT_BINARY_DIR
— which, when consumers add_subdirectory() this with their own binary_dir
(skeleton: build/qml/php_qml_bridge, todo: build/qml/php_qml_bridge),
ends in `php_qml_bridge` instead of `PhpQml/Bridge`. cmake configure
warns about the mismatch:
The php_qml_bridge target is a QML module with target path
PhpQml/Bridge. It uses an OUTPUT_DIRECTORY of .../php_qml_bridge,
which should end in the same target path, but doesn't. Tooling
such as qmllint may not work correctly.
…and at lint time, qmllint can't find the module, so every file that
`import PhpQml.Bridge` (AppShell.qml, DevConsole.qml) fails with
"Failed to import PhpQml.Bridge", which cascades into bogus
"Unqualified access" warnings for every BackendConnection reference.
The cascade exits 255 in Qt 6.5.3's qmllint (CI), even when an older
local qmllint would only warn.
Fix: pin OUTPUT_DIRECTORY in the framework's own qt_add_qml_module so
the layout is correct regardless of how consumers wire up the
add_subdirectory binary_dir. Single source of truth in the framework,
no consumer-side change needed.
Verified locally: rebuild from scratch + `make quality` green
(qmllint clean of the cascade — only the pre-existing
DevConsole/Main.qml warnings remain, all non-fatal). PHPStan +
cs-fixer + 16 tests + maker snapshots also still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:48:47 +02:00
|
|
|
# OUTPUT_DIRECTORY must mirror the URI for qmllint to resolve the module.
|
|
|
|
|
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/PhpQml/Bridge
|
2026-05-02 01:18:43 +02:00
|
|
|
SOURCES
|
|
|
|
|
src/BackendConnection.h
|
|
|
|
|
src/BackendConnection.cpp
|
|
|
|
|
src/SingleInstance.h
|
|
|
|
|
src/SingleInstance.cpp
|
2026-05-02 01:21:59 +02:00
|
|
|
src/MercureClient.h
|
|
|
|
|
src/MercureClient.cpp
|
Phase 2 sub-commit 3: full Update Semantics + ReactiveListModel + AppShell
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
|
|
|
src/ReactiveListModel.h
|
|
|
|
|
src/ReactiveListModel.cpp
|
2026-05-02 15:12:50 +02:00
|
|
|
src/ReactiveObject.h
|
|
|
|
|
src/ReactiveObject.cpp
|
2026-05-02 01:21:59 +02:00
|
|
|
QML_FILES
|
|
|
|
|
qml/RestClient.qml
|
Phase 2 sub-commit 3: full Update Semantics + ReactiveListModel + AppShell
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
|
|
|
qml/AppShell.qml
|
2026-05-02 20:58:53 +02:00
|
|
|
qml/DevConsole.qml
|
2026-05-02 01:18:43 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
target_include_directories(php_qml_bridge PUBLIC src/)
|
|
|
|
|
|
|
|
|
|
target_link_libraries(php_qml_bridge PUBLIC
|
|
|
|
|
Qt6::Core
|
|
|
|
|
Qt6::Gui
|
|
|
|
|
Qt6::Network
|
|
|
|
|
Qt6::Qml
|
|
|
|
|
Qt6::Quick
|
|
|
|
|
)
|
2026-05-03 21:02:30 +02:00
|
|
|
|
|
|
|
|
# QML unit tests — opt-in. Only built when configuring with
|
|
|
|
|
# -DBUILD_TESTING=ON or invoking ctest as part of a top-level project
|
|
|
|
|
# that enable_testing()'d. Skipped by the skeleton + example app
|
|
|
|
|
# release builds so production AppImages don't carry the test exe.
|
|
|
|
|
if(BUILD_TESTING AND CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
|
|
|
|
enable_testing()
|
|
|
|
|
add_subdirectory(tests)
|
|
|
|
|
endif()
|