import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window ApplicationWindow { id: window width: 720 height: 520 visible: true title: "php-qml — Phase 0 spike" readonly property string baseUrl: "http://127.0.0.1:8765" readonly property string topic: "app://ping" property string status: "starting…" property string lastResponse: "" property string mercureState: "disconnected" Mercure { id: mercure url: window.baseUrl + "/.well-known/mercure?topic=" + encodeURIComponent(window.topic) onEventReceived: function(data, id) { log.append("← event " + id.split(":").pop() + " " + data) } onStateChanged: function(s) { window.mercureState = s log.append("· mercure → " + s) } } Timer { id: bootProbe interval: 250 running: window.status !== "online" repeat: true onTriggered: { const xhr = new XMLHttpRequest() xhr.open("GET", window.baseUrl + "/api/ping") xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { if (window.status !== "online") { window.status = "online" log.append("· backend ready") mercure.connect() } } } xhr.send() } } ColumnLayout { anchors.fill: parent anchors.margins: 16 spacing: 12 RowLayout { spacing: 10 Layout.fillWidth: true Rectangle { width: 12; height: 12; radius: 6 color: window.status === "online" ? "#3ab36c" : "#d89614" } Label { text: "Backend: " + window.status } Item { width: 12 } Rectangle { width: 12; height: 12; radius: 6 color: window.mercureState === "connected" ? "#3ab36c" : "#666" } Label { text: "Mercure: " + window.mercureState } Item { Layout.fillWidth: true } Button { text: "Ping" enabled: window.status === "online" onClicked: { const t0 = Date.now() const xhr = new XMLHttpRequest() xhr.open("GET", window.baseUrl + "/api/ping") xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { const dt = Date.now() - t0 if (xhr.status === 200) { window.lastResponse = xhr.responseText log.append("→ ping " + dt + "ms " + xhr.responseText) } else { log.append("× ping failed status=" + xhr.status) } } } xhr.send() } } } Label { text: "Last response: " + (window.lastResponse || "—") color: "#888" font.pixelSize: 12 Layout.fillWidth: true elide: Text.ElideRight } 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…" } } } } }