import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window import PhpQml.Bridge ApplicationWindow { id: window width: 760 height: 540 visible: true title: "php-qml — skeleton" RestClient { id: rest baseUrl: BackendConnection.url token: BackendConnection.token } MercureClient { id: mercure hubUrl: BackendConnection.url + "/.well-known/mercure" topic: "app://ping" // No token in Phase 1 dev mode — Caddyfile enables `anonymous` // for Mercure subscribers. Phase 4 swaps in a Mercure-issued JWT // when the bundled mode tightens auth. onUpdate: function(data, id) { log.append("← mercure " + (id ? id.split(":").pop() : "") + " " + data) } onError: function(detail) { log.append("× mercure error: " + detail) } } Connections { target: BackendConnection function onConnectionStateChanged() { if (BackendConnection.connectionState === BackendConnection.Online) { if (!mercure.active) mercure.start() } else { if (mercure.active) mercure.stop() } } } Connections { // SingleInstance is a context property set in main(); qmllint can't see it. target: SingleInstance // qmllint disable unqualified function onLaunchArgsReceived(args) { window.requestActivate() log.append("· launch args from peer: " + args.join(" ")) } } ColumnLayout { anchors.fill: parent anchors.margins: 16 spacing: 12 RowLayout { spacing: 10 Layout.fillWidth: true Rectangle { Layout.preferredWidth: 12; Layout.preferredHeight: 12; radius: 6 color: { switch (BackendConnection.connectionState) { case BackendConnection.Online: return "#3ab36c" case BackendConnection.Reconnecting: return "#d89614" case BackendConnection.Offline: return "#d8503c" default: return "#888" } } } Label { text: "Backend: " + window._stateName(BackendConnection.connectionState) } Item { Layout.preferredWidth: 12 } Rectangle { Layout.preferredWidth: 12; Layout.preferredHeight: 12; radius: 6 color: mercure.active ? "#3ab36c" : "#666" } Label { text: "Mercure: " + (mercure.active ? "subscribed" : "off") } Item { Layout.fillWidth: true } Button { text: "Ping" enabled: BackendConnection.connectionState === BackendConnection.Online onClicked: { const t0 = Date.now() rest.get("/api/ping").then(function(r) { const dt = Date.now() - t0 log.append("→ ping " + dt + "ms " + JSON.stringify(r.body)) }).catch(function(e) { log.append("× ping " + e.status + " " + JSON.stringify(e.problem || e.raw)) }) } } } Label { text: "URL: " + BackendConnection.url + (BackendConnection.error ? " error: " + BackendConnection.error : "") color: BackendConnection.error ? "#d8503c" : "#888" font.pixelSize: 12 elide: Text.ElideRight Layout.fillWidth: true } Frame { Layout.fillWidth: true Layout.fillHeight: true padding: 0 ScrollView { anchors.fill: parent TextArea { id: log readOnly: true wrapMode: TextArea.Wrap font.family: "monospace" font.pixelSize: 12 placeholderText: "events will appear here…" } } } DevConsole { id: devConsole visible: false Layout.fillWidth: true Layout.preferredHeight: 220 } } // Ctrl+` toggles the FrankenPHP child output console (Phase 5 §13). Shortcut { sequences: ["Ctrl+`", "Ctrl+~"] onActivated: devConsole.visible = !devConsole.visible } function _stateName(s) { switch (s) { case BackendConnection.Connecting: return "connecting" case BackendConnection.Online: return "online" case BackendConnection.Reconnecting: return "reconnecting" case BackendConnection.Offline: return "offline" } return "?" } }