Skip to content

kanban: openBoard() is dispatched without waiting for its handler registration to bind (Remote mode) #305

Description

@Yaraslaut

Summary

BoardBridge::openBoard() is dispatched from QML with no gate on its handler's registration having bound. In Remote/Socket mode a dispatch that lands before registration settles is rejected with "handler not bound", which would surface as a failed() toast on a board that never loads.

Verification status

Inferred from reading, not reproduced. Established by reading Main.qml, board_qml_bridge.cpp and examples/common/gui/presenter.hpp on origin/master @ 9371c1a0. No Socket-mode client was stood up and raced, and no failure was observed. What follows is a structural argument, not a measurement.

What the code says

examples/common/gui/presenter.hpp:40 documents that a BridgeHandler in Remote mode rejects dispatch with "handler not bound" until its registration round trip lands, and that bound() is the settlement signal for it.

ProjectListView.qml:56-61 consumes exactly that for projectAdminBridge, gating its first refreshProjects() on onBound.

BoardBridge relays the same signal (examples/kanban/gui_lib/board_qml_bridge.cpp:232) and nothing consumes it — not QML, not tests. Meanwhile examples/kanban/gui/qml/Main.qml:92-95 dispatches unconditionally:

onProjectOpened: (projectId, myRole) => {
    root.boardBridge.setMyRole(myRole)
    root.boardBridge.openBoard(projectId)
    stack.push(boardPage)
}

So the first dispatch on BoardBridge is user-triggered with no bind gate. In Local mode this is a non-issue — the handler is bound by construction.

Why it is improbable rather than impossible

Both bridges are constructed together at startup (examples/kanban/gui/main.cpp), and reaching onProjectOpened requires a login round trip, a project-list render, and a human click. The registration round trip has, in practice, many seconds of head start.

The ordering is protected by human latency, not by construction. That is the defect: the guarantee is incidental.

What would change the verdict

  • Close it if someone shows the registration is guaranteed to settle before any BoardBridge dispatch can be issued — e.g. if Bridge queues pre-bind dispatches rather than rejecting them. That seems unlikely, since it would also make ProjectListView's own onBound gate unnecessary.
  • Confirm it by driving a Socket-mode client with a stalled registration reply and calling openBoard(). examples/common/testkit/fault_proxy.hpp is the tool.

Interaction with the in-flight #291 work

The branch fixing morph#291 deletes BoardBridge::bound as dead QML surface. That is correct for the audit — the signal is unconsumed and, given the Main.qml:92-95 ordering above, could not be consumed usefully where it stands: a handler on BoardView attaches after openBoard() has already been dispatched. Gating properly means restructuring navigation, not adding a handler.

That deletion neither creates this hazard nor worsens it. BoardPresenter::bound remains, and restoring the relay is one line if the fix turns out to be gating navigation on it.

Recorded separately rather than folded into a QML-wiring change, per AGENTS.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions