Summary
ledger's four QML bridges expose 15 QML-visible members that no file under
examples/ledger/gui/qml/ binds. Each is a Q_PROPERTY, a signal or a
Q_INVOKABLE that the metaobject publishes, that a shell hands to QML, and
that nothing in the shipped UI reads, handles or calls.
Found while generalising the metaobject-surface drift guard for #86 — the
audit was pointed at ledger and this is what it said on the first run.
Verification status
Reproduced, on d6690c5 plus the feat/86-qml-surface-drift-guard branch,
locally: macOS (arm64), Homebrew clang 22.1.8, Qt 6.11.1 (Homebrew) — note CI
pins Qt 6.8.1 — Debug,
QT_QPA_PLATFORM=offscreen.
Command:
cmake --build build --target ladder_ledger_tests
QT_QPA_PLATFORM=offscreen build/examples/ledger/ladder_ledger_tests "[qml-surface]"
Real output (the audit's own findings, verbatim):
ledger::gui::LedgerQmlBridge::busy is a Q_PROPERTY no scanned .qml reads (bound as 'ledgerBridge'/'bridge')
ledger::gui::LedgerQmlBridge::busyChanged is a signal no scanned .qml handles (bound as 'ledgerBridge'/'bridge')
ledger::gui::LedgerQmlBridge::refresh is invokable from QML but no scanned .qml calls it (bound as 'ledgerBridge'/'bridge')
ledger::gui::LedgerQmlBridge::undoTransaction is invokable from QML but no scanned .qml calls it (bound as 'ledgerBridge'/'bridge')
ledger::gui::BudgetQmlBridge::busy is a Q_PROPERTY no scanned .qml reads (bound as 'budgetBridge'/'bridge')
ledger::gui::BudgetQmlBridge::busyChanged is a signal no scanned .qml handles (bound as 'budgetBridge'/'bridge')
ledger::gui::BudgetQmlBridge::categoryCreated is a signal no scanned .qml handles (bound as 'budgetBridge'/'bridge')
ledger::gui::BudgetQmlBridge::budgetCreated is a signal no scanned .qml handles (bound as 'budgetBridge'/'bridge')
ledger::gui::BudgetQmlBridge::limitSet is a signal no scanned .qml handles (bound as 'budgetBridge'/'bridge')
ledger::gui::BudgetQmlBridge::lastBudgetId is invokable from QML but no scanned .qml calls it (bound as 'budgetBridge'/'bridge')
ledger::gui::BudgetQmlBridge::linkAccount is invokable from QML but no scanned .qml calls it (bound as 'budgetBridge'/'bridge')
ledger::gui::RuleQmlBridge::busy is a Q_PROPERTY no scanned .qml reads (bound as 'ruleBridge'/'bridge')
ledger::gui::RuleQmlBridge::busyChanged is a signal no scanned .qml handles (bound as 'ruleBridge'/'bridge')
ledger::gui::RuleQmlBridge::ruleCreated is a signal no scanned .qml handles (bound as 'ruleBridge'/'bridge')
ledger::gui::RuleQmlBridge::ruleUpdated is a signal no scanned .qml handles (bound as 'ruleBridge'/'bridge')
Cross-checked by hand, independently of the audit — none of these names occurs
anywhere in the rung's QML:
$ grep -rn "refresh\|busy\|undoTransaction\|linkAccount\|lastBudgetId\|ruleCreated\|ruleUpdated\|categoryCreated\|budgetCreated\|limitSet" examples/ledger/gui/qml/
$ # (no output)
ReportQmlBridge is clean: every member it publishes is bound by
ReportView.qml.
Not verified: whether each of the 15 is dead surface that should be deleted
or missing UI that should be added. That is a per-member judgement this issue
does not make. Two shapes are visible from reading, and are stated as
inferences, not measurements:
busy / busyChanged appear on three of the four bridges with no QML
consumer on any of them. That looks like a base-class-shaped convention
applied uniformly and never wired up, not three independent oversights.
undoTransaction, linkAccount and lastBudgetId are single features with
a model and a presenter behind them and no control in the UI.
Why it matters
Each is either surface a reader will believe is live, or a UI gap. Neither is
visible today: QML resolves by string, so an unbound bridge member costs
nothing at run time and produces no warning anywhere.
Note the asymmetry — the other direction was clean. No ledger QML file
binds a name its bridge lacks, so nothing here is a broken screen today.
What would change the verdict
Close this when examples/ledger/tests/test_ledger_qml_surface.cpp passes with
its allowUnbound() list empty. That file exists on the #86 branch and
currently carries exactly these 15 members as exemptions, each citing this
issue. Removing an exemption without either deleting the member or binding it
from QML fails the test, so the list cannot be quietly abandoned.
Re-open if a later rung re-adds unbound surface to these four bridges — but
that should not be possible without deliberately adding an exemption, which is
the point of the guard.
Summary
ledger's four QML bridges expose 15 QML-visible members that no file underexamples/ledger/gui/qml/binds. Each is aQ_PROPERTY, a signal or aQ_INVOKABLEthat the metaobject publishes, that a shell hands to QML, andthat nothing in the shipped UI reads, handles or calls.
Found while generalising the metaobject-surface drift guard for #86 — the
audit was pointed at
ledgerand this is what it said on the first run.Verification status
Reproduced, on
d6690c5plus thefeat/86-qml-surface-drift-guardbranch,locally: macOS (arm64), Homebrew clang 22.1.8, Qt 6.11.1 (Homebrew) — note CI
pins Qt 6.8.1 — Debug,
QT_QPA_PLATFORM=offscreen.Command:
Real output (the audit's own findings, verbatim):
Cross-checked by hand, independently of the audit — none of these names occurs
anywhere in the rung's QML:
ReportQmlBridgeis clean: every member it publishes is bound byReportView.qml.Not verified: whether each of the 15 is dead surface that should be deleted
or missing UI that should be added. That is a per-member judgement this issue
does not make. Two shapes are visible from reading, and are stated as
inferences, not measurements:
busy/busyChangedappear on three of the four bridges with no QMLconsumer on any of them. That looks like a base-class-shaped convention
applied uniformly and never wired up, not three independent oversights.
undoTransaction,linkAccountandlastBudgetIdare single features witha model and a presenter behind them and no control in the UI.
Why it matters
Each is either surface a reader will believe is live, or a UI gap. Neither is
visible today: QML resolves by string, so an unbound bridge member costs
nothing at run time and produces no warning anywhere.
Note the asymmetry — the other direction was clean. No
ledgerQML filebinds a name its bridge lacks, so nothing here is a broken screen today.
What would change the verdict
Close this when
examples/ledger/tests/test_ledger_qml_surface.cpppasses withits
allowUnbound()list empty. That file exists on the #86 branch andcurrently carries exactly these 15 members as exemptions, each citing this
issue. Removing an exemption without either deleting the member or binding it
from QML fails the test, so the list cannot be quietly abandoned.
Re-open if a later rung re-adds unbound surface to these four bridges — but
that should not be possible without deliberately adding an exemption, which is
the point of the guard.