Public API exposed by the `PhpQml.Bridge` QML module. Internal helpers and slots aren't documented here — the source is at [`framework/qml/src/`](../framework/qml/src/) if you need to read them.
| [`SingleInstance`](#singleinstance) | C++ object exposed via context | QLocalServer-backed lock + arg forwarding. |
---
## `BackendConnection`
QML singleton. Lifecycle owner: detects dev vs bundled mode at construction, supervises the FrankenPHP child in bundled mode, drives the connection state machine, and brokers the auto-update sidecar.
| `checkForUpdates()` | Bundled mode: invoke AppImageUpdate sidecar `--check-for-update`. The supervisor also calls this automatically on launch (10 s after `Online`) and every 6 h thereafter — see [Bundled mode §periodic check](bundled-mode.md#periodic-check). |
| `applyUpdate()` | Bundled mode: invoke AppImageUpdate sidecar `--remove-old`. Never auto-restarts the app. |
| `exportDatabase(path)` | `Q_INVOKABLE bool`. Copies the active SQLite database to `path`; returns success synchronously and emits `databaseExported(path)` / `databaseExportFailed(reason)` for async UX. Mirrors the `bridge:export` console command. See below. |
| `tokenRotated(QString newToken)` | Bundled mode: emitted when supervisor restarts FrankenPHP with a fresh secret. `RestClient` and `MercureClient` are wired to swap. |
| `updatesAvailable()` | AppImageUpdate sidecar reported a newer version. |
`exportDatabase()` returns synchronously (`true` on success, `false` on failure) — the signals exist for cases where the caller is decoupled from the click handler. See [PHP API §bridge:export](php-api.md#bridgeexport) for the equivalent CLI command.
Promise-style HTTP wrapper backed by `XMLHttpRequest`. Auto-attaches `Idempotency-Key` to every non-GET request. Maps `application/problem+json` error bodies into structured rejections.
### Properties
| Property | Type | Notes |
| --- | --- | --- |
| `baseUrl` | string | Joined with the path on every call. |
| `token` | string | If set, sent as `Authorization: Bearer <token>`. |
SSE subscriber for a single Mercure topic. Reconnects automatically with exponential backoff and `Last-Event-ID` to replay events that fired during the gap.
### Properties
| Property | Type | Notes |
| --- | --- | --- |
| `hubUrl` | string | E.g. `BackendConnection.url + "/.well-known/mercure"`. |
| `topic` | string | Single topic per client. Multi-topic apps spawn multiple `MercureClient`s. |
| `token` | string | Optional bearer (Mercure JWT). Empty in dev mode where Caddy allows anonymous subscribers. |
| `active` | bool | Currently subscribed? |
| `lastEventId` | string | Highest event id seen. Used as `Last-Event-ID` on reconnect. |
### Methods
| Method | Description |
| --- | --- |
| `start()` | Open the subscription. |
| `stop()` | Close it. |
### Signals
| Signal | Notes |
| --- | --- |
| `update(QString data, QString id)` | Per-event payload + id. `data` is the raw SSE `data:` field (typically JSON). |
| `error(QString detail)` | Transport-level error; auto-reconnect handles it but apps may want to log. |
if (BackendConnection.connectionState === BackendConnection.Online) {
if (!mercure.active) mercure.start();
} else {
if (mercure.active) mercure.stop();
}
}
}
```
In practice you rarely instantiate `MercureClient` directly — `ReactiveListModel` and `ReactiveObject` own one each.
---
## `ReactiveListModel`
`QAbstractListModel` subclass that does the initial GET, subscribes to Mercure, and applies events. See [Reactive models](reactive-models.md) for the conceptual writeup.
### Properties
| Property | Type | Notes |
| --- | --- | --- |
| `baseUrl` / `token` / `source` / `topic` | string | Same as documented in [Reactive models](reactive-models.md#reactivelistmodel). |
| `ready` | bool | `true` after initial GET completes. |
| `error` | string | Last error, or empty. |
### Roles
Every JSON field on the entity becomes a role of the same name. Plus:
| Role | Type | Notes |
| --- | --- | --- |
| `pending` | bool | `true` while an optimistic mutation against this row is in flight. |
### Methods
| Method | Description |
| --- | --- |
| `refresh()` | Re-do the initial GET. Useful after a long offline window. |
| `pending` | bool | Optimistic mutation in flight. |
| `exists` | bool | False after the entity was deleted. |
| `error` | string | Last error or empty. |
### Methods
| Method | Description |
| --- | --- |
| `refresh()` | Re-fetch. |
| `invoke(method, urlSuffix, body, optimistic)` | Same shape as `ReactiveListModel.invoke`. |
### Signals
`commandSucceeded` / `commandFailed` / `commandTimedOut` — same contract as `ReactiveListModel`. Also `existsChanged()` so detail UIs can react to a delete arriving from another window.
---
## `AppShell`
Optional convenience root component that surfaces the [Update Semantics](update-semantics.md) state machine as default UI:
-`Reconnecting` → orange banner across the top.
-`Offline` → modal overlay with the last error and a Retry button.
### Default property
`content` is a default property alias, so children of `AppShell` populate the inner content slot:
```qml
AppShell {
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
// your UI
}
}
```
Skip `AppShell` if you want full control over the chrome — `BackendConnection.connectionState` is the source of truth.
---
## `DevConsole`
Optional in-window log viewer for the bundled FrankenPHP child's stdout+stderr. Captures passively in `BackendConnection`, so opening the console is free.
### Properties
| Property | Type | Notes |
| --- | --- | --- |
| `maxLines` | int | Default 500. The model trims to this. |
| Standard `Item`/`Rectangle` properties | — | Anchors, sizing, etc. |
| `launchArgsReceived(QStringList args)` | Fired in the *running* instance when a new launch forwards its `argv`. |
### Usage from QML
```qml
Connections {
target: SingleInstance
function onLaunchArgsReceived(args) {
window.requestActivate(); // show the window
if (args.length > 1) openFile(args[1]);
}
}
```
The lock socket lives at `~/.local/share/<name>/<name>.sock`. If the lock can't be acquired and the existing instance doesn't respond on the socket (stale file), `SingleInstance` removes it and retries — handles the typical "host crashed without cleanup" case.
---
## See also
- [PHP API reference](php-api.md)
- [Configuration reference](configuration.md)
- [Update semantics](update-semantics.md) for what these primitives implement.