Skip to content

ledger: four QML bridges publish 15 members no gui/qml file binds #239

Description

@Yaraslaut

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ladderSubsystem: ladderarea: qtSubsystem: qtbugSomething isn't workingtriage: validWell-framed; implement as written

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions