| name | omatrack |
| description | Omatrack-specific Qt 6 / QML working rules, tooling paths, and known deviations. Use whenever touching src/app/*.qml, the C++ Quick items, the CMake build, or the Rust parser workspace in this repository — before applying generic Qt advice from the qt-qml / qt-cpp-review skills. |
Omatrack Qt/QML working rules
Read AGENTS.md for product and layer boundaries. This skill only covers the
Qt-specific mechanics that AGENTS.md does not: which binary to run, which
generic Qt rule this repo deliberately breaks, and the local traps that will
otherwise waste an hour.
Upstream skills
.agents/skills/qt-* are vendored verbatim from
TheQtCompanyRnD/agent-skills
(BSD-3-Clause, pinned commit in .agents/skills/UPSTREAM.md). They are the
authority for generic Qt 6 practice. Route work to them:
| Task | Skill |
|---|
| Writing/refactoring QML | qt-qml |
| Auditing QML before commit | qt-qml-review |
| "the UI feels slow" | qt-qml-profiler |
| CMake targets, QML modules, resources | qt-cmake-project |
| Auditing Qt C++ | qt-cpp-review |
| Writing / running Qt Quick tests | qt-qml-test, qt-qml-test-run |
Qt's own documentation MCP is wired in .mcp.json (qt-docs, tools
qt_documentation_search / qt_documentation_read). Prefer it over web search
for Qt API questions.
Three local traps
/usr/bin/qmllint is the Qt 5 binary. It accepts only -U/-I/-i,
reports nothing useful, and exits 0. Always use /usr/lib/qt6/bin/...
(Qt 6.11.1), or better, the CMake targets below.
qWarning() goes to journald, not stderr. Arch builds Qt with journald
support, so every AUTOTEST … line the acceptance harness prints is
invisible unless you set QT_FORCE_STDERR_LOGGING=1. A "silent" harness is
almost always this, not a broken run.
- A nested function declaration silently disables qmllint. A
Store.…
call inside a function declared inside another function makes qmllint
report zero findings for the entire document while still parsing it. The
file looks perfectly clean and nothing is being checked. Never nest QML
functions; hoist the helper to the object scope.
Tooling
cmake --preset release && cmake --build build --parallel
cmake --build build --target all_qmllint
cmake --build build --target rust_clippy
ctest --test-dir build
/usr/lib/qt6/bin/qmlformat -i src/app/*.qml
clang-format -i src/app/*.cpp src/app/*.h
.qmllint.ini in src/app/ turns on everything Qt disables by default,
including CompilerWarnings — that category is the ahead-of-time compilation
report, and today every remaining entry traces back to QVariantList /
QVariantMap crossing from TelemetryStore into QML. [unqualified],
[missing-property], or [import] appearing there is a regression.
Deterministic rule linters, no build required:
python3 .agents/skills/qt-qml-review/references/lint-scripts/qt_qml_lint.py src/app/*.qml
python3 .agents/skills/qt-cpp-review/references/lint-scripts/qt_review_lint.py src/app/*.cpp src/app/*.h
The C++ linter's HDR-3 (Windows min/max macro safety) and DEP-7
(qMin→std::min) rules do not apply to this Linux-only target. The QML
linter's JS-2 "loose equality" is a false positive — its regex counts !==
as !=; the codebase contains zero real ==/!=. ORD-1 attribute ordering
is arbitrated by qmlformat (NormalizeOrder=true), not by that script.
PRF-3 clip: true is expected on every ListView and on the SplitView panes
that host video and traces. Trust all_qmllint; treat qt_qml_lint.py as a
heuristic pre-filter only.
QQuickWindow::setGraphicsApi(OpenGL) in main.cpp is load-bearing and
must stay before QGuiApplication. MpvVideoItem is a
QQuickFramebufferObject, which Qt documents as OpenGL-only legacy API.
QQuickRhiItem (Qt 6.7+) is the portable replacement, but libmpv's render
API keeps us on OpenGL — do not "modernize" this without a working mpv path.
- Every telemetry surface is scene-graph geometry, not
QPainter.
TraceView, TraceCursorOverlay, DamperStripView and
VideoTelemetryHud are plain QQuickItems that build their frame in
updatePaintNode() through TraceSceneBuilder: one batched
QSGGeometryNode of vertex-coloured triangles per surface plus cached text
textures (TraceTextCache). Lines are quads (core-profile glLineWidth is
clamped to 1) and edges are antialiased by the 4× multisample default format
set in main.cpp. Add drawing through that builder — never a
QQuickPaintedItem, never a Canvas.
Measured on a full-height Sebring lap, seven lanes: geometry build 0.32 ms
average / 0.74 ms worst for ~4900 quads, cursor overlay 0.006 ms, and
QSG_RENDER_TIMING=1 reports sync=0, render=0. The QPainter renderer this
replaced cost 12.6 ms on the same lap.
- 65535 vertices is a cliff, not a slope. The scene graph merges
compatible geometry into 16-bit indexed batches, so a large frame loses
everything past that vertex count with no warning — lanes simply stop
drawing halfway down the workspace.
TraceSceneBuilder emits indexed quads
with QSGGeometry::UnsignedIntType to stay out of the merge path. If traces
vanish below a certain lane, this is why.
- The software adaptation cannot draw geometry nodes.
offscreen falls
back to it, so trace surfaces render blank there (the builder detects it and
emits nothing rather than crashing). Renderer work must be verified with
QT_QPA_PLATFORM=wayland.
- A corner check is a
CornerAnalyzer, never an if in the store.
src/core/CornerAnalysis.* is the port of the corner analysis in
tobi/ac-tracer
() — read that file before adding a check;
it is the spec for what a comparison should say. scans the
corner once into ; analyzers read those scalars and emit
; the registry runs them in order. Registration is
compile-time on purpose: the core stays Qt-free so
and run exactly what the GUI runs. Do not reach for
— it would pull Qt into the core; an app-layer loader
calling is the extension path if one is ever
needed.
Verify a new check on real laps, not just the unit test:
.
A check that fires on every corner is noise; tune the threshold or make it
relative to the reference driver.
Proving a change
cmake --build --preset acceptance
OMATRACK_AUTOTEST=/tmp/omatrack.png \
scripts/autotest.sh ./build-acceptance/omatrack --mute ~/Documents/Telemetry/<event>
Always go through scripts/autotest.sh: it parks the window on a headless
Hyprland output (real GL, full-frame screenshot, no focus steal) and --mute
keeps the onboard audio off. A bare launch pops over the developer's work
and makes the video-sync checks flaky.
OMATRACK_AUTOTEST_WINDOWS=1 writes <base>_window.png,
<base>_channelsWindow.png, and <base>_settingsWindow.png instead of the
base path — a missing base PNG under that flag is expected, not a failure.
_HOVER=1 / _ZOOM=1 print the average scene-graph geometry build time and
quad count. Budget: 16.67 ms is the 60 fps ceiling, 8.33 ms the design target.
Always look at the PNG. Timings do not catch stale comparison state or
illegible density. A native (non-offscreen) run is mandatory for anything
touching MpvVideoItem, palette integration, or fonts.
Treat ~/Documents/Telemetry as read-only source data. omatrack-cli unify
must write through --output to a copied fixture or disposable destination.