Document iteration 1: settings file, HA Dashboard tab, new hooks

README: settings file, --config, the new tab. ARCHITECTURE.md and
CLAUDE.md: prepare() hook, app command-line options, app code layout,
WebEngine notes, the moc raw-string pitfall. THIRD_PARTY_NOTICES: Qt
WebEngine. Ship config.example.json; the package README explains the
set-up. PROGRESS.md and DESIGN.md: phase 3 results and deviations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 22:21:34 +02:00
co-authored by Claude Opus 5.5
parent 0676998033
commit 179316db52
9 changed files with 175 additions and 37 deletions
+34 -8
View File
@@ -29,7 +29,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**FrameAssistant**, "Steam Frame companion for Home Assistant" (in the spirit of HASS.Agent):
it reports headset/environment sensors to Home Assistant, offers services Home Assistant can
call (e.g. notifications in the headset), and shows a Home Assistant dashboard as a SteamVR
dashboard tab. Status: the overlay platform works; the Home Assistant parts are being planned.
dashboard tab. Status: the overlay platform works; the Home Assistant parts are being built
(implementation iteration 1: settings file and the "HA Dashboard" web UI tab).
**Start every session by reading `docs/PROGRESS.md`** (current state, next steps, log).
@@ -60,7 +61,7 @@ cmake --preset native-debug && cmake --build --preset native-debug # system Qt
ctest --preset native-debug # all tests
build/native-debug/bin/tst_inputbridge clickUsesGlCoordinates # one QtTest function
build/native-debug/bin/frame-assistant --desktop # UI preview in a window, no SteamVR
build/native-debug/bin/frame-assistant --snapshot x.png # one frame via the real offscreen path
build/native-debug/bin/frame-assistant --snapshot x.png # x-0.png (main tab), x-dashboard.png
scripts/container-build.sh cross # x86_64 → aarch64 against the Frame's system Qt (default)
scripts/container-build.sh native # same for the host arch (use on the Frame)
scripts/container-build.sh cross-bundle # fallback: self-contained, Qt bundled (also native-bundle)
@@ -70,7 +71,11 @@ VK_DRIVER=lvp scripts/container-test.sh # same on lavapipe (software Vulkan)
build/native-debug/bin/frame-assistant --graphics-api vulkan --benchmark 600 # frame ms incl. GPU
```
- Use `--snapshot` and then read the PNG to check UI or rendering changes without a headset.
- Use `--snapshot` and then read the PNGs to check UI or rendering changes without a headset.
With several surfaces it writes `<name>-<id>.png` per surface (`-0` for the main tab's
empty id). Snapshot and benchmark don't load the settings, so the "HA Dashboard" tab shows
its "no URL" hint there.
- Overlay and desktop mode need a settings file; pass a test one with `--config <path>`.
- `tst_render` runs once per backend (`VRDASH_TEST_API`, separate CTest entries, because the
Qt graphics API is process-global). It QSKIPs without a GPU context or when Vulkan isn't
compiled in.
@@ -106,11 +111,14 @@ into Qt events.
modes (overlay, `--desktop`, `--snapshot`, `--benchmark`, manifest install).
- **Inheritance for the app, components for the surfaces** (the user's decision, after
trying composition): the app derives from `DashboardApplication`. In its constructor it
configures (`addSurface`, optional `setAppKey`/`setDisplayName`) and it
overrides the hooks `loading()` (backends + shared engine exist, QML not loaded) and
`started()` (everything runs). In the hooks, protected accessors give the running state:
`mode()`, `appKey()`, `appName()`, `engine()`, `surfaces()`, `surface()` (primary),
`surface(id)`, `vrRuntime()`, `navigation()`. The app is `src/Dashboard.h/.cpp`.
configures (`addSurface`, optional `setAppKey`/`setDisplayName`) and passes its own
command-line options (`QCommandLineOption` list) to the base constructor. It overrides
the hooks `prepare(QString*)` (first; mode and app key known, no engine/surfaces/SteamVR
yet; returning false ends `exec()` with exit code 1), `loading()` (backends + shared
engine exist, QML not loaded) and `started()` (everything runs). In the hooks, protected
accessors give the running state: `mode()`, `appKey()`, `appName()`, `isOptionSet()`,
`optionValue()`, `engine()`, `surfaces()`, `surface()` (primary), `surface(id)`,
`vrRuntime()`, `navigation()`. The app is `src/Dashboard.h/.cpp`.
- The base constructor creates the `QGuiApplication` (`QCoreApplication` for manifest
commands), so derived members can be QObjects with timers.
- All QML and surfaces are torn down **at the end of `exec()`**, while the derived object
@@ -188,6 +196,22 @@ into Qt events.
- **QML singletons owned by the app:** keep them as members of the app class. A singleton
must not be default-constructible, or QML builds its own instance instead of calling
`create()` (e.g. give the constructor a required `QObject* parent` and init it `{nullptr}`).
- **App code layout:** logic without SteamVR or rendering goes into the static library
`frameassistant_lib` (`src/config/`, …), which the app and its tests link. QML types
(`QML_ELEMENT`, e.g. `src/ha/HaWebUi`) go into `QML_SOURCES` of `vrdash_add_dashboard()`:
they must be part of the QML module. The generated type registration includes their
headers by file name only, so their directory is on the app's include path.
- **Settings** (`src/config/Config`) are loaded once in `Dashboard::prepare()` (overlay and
desktop mode) and passed on as an immutable value (DESIGN.md, D19).
- **Qt WebEngine** ("HA Dashboard" tab): `QtWebEngineQuick::initialize()` runs in `main()`
before the app object, because it must precede the `QGuiApplication`. `HaWebUi` creates a
persistent profile under `~/.local/share/frameassistant/webengine` with the Qt 6.8 API
(setters on `QQuickWebEngineProfile`). Qt ≥ 6.9 logs a hint to use
`WebEngineProfilePrototype` instead, which the Frame's Qt 6.8 doesn't have.
- **moc and raw string literals:** a raw string containing `//` (e.g. a URL) makes moc treat
the rest as a comment; it then misses `Q_OBJECT` classes in that file (link error "vtable
for …"). In moc'ed files, write such text as normal string literals (`tst_config` uses
JSON with `'` quotes and converts them).
- Publish per-frame values (frame stats etc.) to QML at most about once per second. Binding
per-frame values in QML re-triggers the on-demand renderer forever.
- **The executable is `frame-assistant` (`VRDASH_EXECUTABLE_NAME`), never `vrdashboard`.**
@@ -307,3 +331,5 @@ Check these when testing on the device, and update this list:
- The restructured app code (base class `DashboardApplication`, `Surface`, `Overlay` base,
active-surface navigation) has only been tested off the device so far.
- Several dashboard tabs from one process (works in `tst_surfaces`, not tried in SteamVR).
- Qt WebEngine in the off-screen renderer (Vulkan and OpenGL), the SteamVR keyboard in web
page input fields, and the persistent login ("HA Dashboard" tab, DSH-1).