The SignalBus in src/cluster_erp/desktop/core/signal_bus.py is the desktop client's only channel between screens. A mutation sent through ApiClient with an entity descriptor emits a change signal, and every screen that declared a dependency with watch_entity refreshes. This page explains the signals, the mutation-to-signal path, and the subscription rules. The server-side counterpart for modules is the event bus; the web client's counterpart is on data refresh and change signals.
In short¶
SignalBusis aQObjectwith 12 signals, counted as theSignal(class attributes insrc/cluster_erp/desktop/core/signal_bus.py:18-44.ApiClient.post,put,patch,delete,post_multipart, andput_multipartemitentity_created,entity_updated, orentity_deletedafter a successful call when anentitydescriptor is passed (src/cluster_erp/desktop/core/api_client.py:213-294).watch_entity(entity, callback, owner=...)callscallbackfor any change of that module and entity type, and disconnects itself when the owner widget is destroyed (src/cluster_erp/desktop/core/signal_bus.py:55-85).FkCachelistens to the same three signals and drops the cached option lists of the changed module (src/cluster_erp/desktop/core/fk_cache.py:51-77).
How it works¶
Signals¶
| Signal | Arguments | Emitted by | Received by |
|---|---|---|---|
navigate_to |
module, section, params | GenericDetailView._follow_link, the warehouse and MRP dashboards (src/cluster_erp/desktop/widgets/generic/generic_detail_view.py:767, src/cluster_erp/desktop/modules/inventory/warehouse_dashboard.py:91, src/cluster_erp/desktop/modules/procurement/mrp/dashboard.py:94) |
MainWindow._on_navigate_to (src/cluster_erp/desktop/widgets/main_window.py:66) |
open_entity |
module, entity type, id, title | the scheduling board (src/cluster_erp/desktop/modules/production/scheduling_board.py:2220) |
MainWindow._on_open_entity (src/cluster_erp/desktop/widgets/main_window.py:67) |
entity_created |
module, entity type, id | ApiClient.emit_entity_change (src/cluster_erp/desktop/core/api_client.py:213-218) |
watch_entity callbacks, FkCache, two panels |
entity_updated |
module, entity type, id | ApiClient.emit_entity_change |
watch_entity callbacks, FkCache, two panels |
entity_deleted |
module, entity type, id | ApiClient.emit_entity_change |
watch_entity callbacks, FkCache, one panel |
theme_changed |
theme name | no emitter in the desktop folder | no receiver in the desktop folder |
language_changed |
language code | no emitter in the desktop folder | no receiver in the desktop folder |
connection_status_changed |
connected flag | StatusBarWidget._set_status (src/cluster_erp/desktop/widgets/status_bar.py:65-70) |
no receiver in the desktop folder |
user_logged_in |
user dictionary | main() and MainWindow._on_session_expired (src/cluster_erp/desktop/main.py:192, src/cluster_erp/desktop/widgets/main_window.py:264) |
StatusBarWidget (src/cluster_erp/desktop/widgets/status_bar.py:44) |
user_logged_out |
none | MainWindow._do_logout and _on_session_expired (src/cluster_erp/desktop/widgets/main_window.py:241, src/cluster_erp/desktop/widgets/main_window.py:267) |
StatusBarWidget (src/cluster_erp/desktop/widgets/status_bar.py:45) |
session_expired |
none | ApiClient._emit_session_expired (src/cluster_erp/desktop/core/api_client.py:207-211) |
MainWindow._on_session_expired (src/cluster_erp/desktop/widgets/main_window.py:65) |
accessibility_changed |
colour-blind mode | a lambda in main() on AccessibilityManager.settings_changed (src/cluster_erp/desktop/main.py:173-175) |
BomExplorer (src/cluster_erp/desktop/modules/bom/bom_explorer.py:1118) |
The rows were found by searching src/cluster_erp/desktop/ for each signal name. The comments in src/cluster_erp/desktop/core/signal_bus.py:31-35 state that theme and language changes go through ThemeEngine.theme_changed and Translator.language_changed directly.
Mutation-to-signal path¶
The diagram shows a save from a generic form until a list in another tab refreshes.
sequenceDiagram participant F as GenericFormDialog participant W as ApiWorker participant G as GenericApi participant A as ApiClient participant B as SignalBus participant C as FkCache participant L as GenericListView F->>W: call_api(_do_save) W->>G: create(data) G->>A: post(path, json, entity=descriptor) A-->>G: result with id A->>B: entity_created(module, entity_type, id) B->>C: _on_entity_change drops module entries B->>L: _on_change filter matches L->>L: refresh
In words:
- The form runs
_do_savein a worker (src/cluster_erp/desktop/widgets/generic/generic_form_dialog.py:230-237)._do_savecallsGenericApi.createorGenericApi.update(src/cluster_erp/desktop/widgets/generic/generic_form_dialog.py:262-269). GenericApipasses itsEntityDescriptortoApiClient(src/cluster_erp/desktop/core/generic_api.py:67-84).- After the server answers,
ApiClient.postreadsidfrom the result and callsemit_entity_changewith the actioncreated(src/cluster_erp/desktop/core/api_client.py:230-234).emit_entity_changelooks up the signalentity_<action>and emits module, entity type, and id (src/cluster_erp/desktop/core/api_client.py:213-218). FkCache._on_entity_changeremoves every cached option list whose key starts with the module name (src/cluster_erp/desktop/core/fk_cache.py:65-77).- Each
watch_entityclosure compares module and entity type and calls its callback on a match (src/cluster_erp/desktop/core/signal_bus.py:69-71). A list view refreshes its rows (src/cluster_erp/desktop/widgets/generic/generic_list_view.py:333-334).
Subscribing with watch_entity¶
watch_entity connects one closure to all three change signals. With an owner, _track stores the connections and connects the owner's destroyed signal to _disconnect_owner, which disconnects them (src/cluster_erp/desktop/core/signal_bus.py:77-110). unwatch_entity(owner) does the same on demand (src/cluster_erp/desktop/core/signal_bus.py:87-89).
Eleven call sites use watch_entity, counted by searching src/cluster_erp/desktop/ for the name outside signal_bus.py and a comment in tab_manager.py. They are the generic list, tree, settings, sub-panel, and detail views, the capability panel, the BOM explorer and builder, and the scheduling board (for example src/cluster_erp/desktop/widgets/generic/generic_list_view.py:334, src/cluster_erp/desktop/widgets/generic/generic_detail_view.py:455-459, src/cluster_erp/desktop/modules/production/scheduling_board.py:1521).
GenericDetailView reloads on a change only when its form has no unsaved edits. The comment states that a save of another record of the same type would otherwise overwrite the user's edits (src/cluster_erp/desktop/widgets/generic/generic_detail_view.py:452-459, src/cluster_erp/desktop/widgets/generic/generic_detail_view.py:564-569).
Session expiry¶
A failed token refresh emits session_expired (src/cluster_erp/desktop/core/api_client.py:173-211). MainWindow._on_session_expired handles only the first emission until the user signs in again. It shows a warning toast, clears the session, and opens the sign-in dialog. A successful sign-in emits user_logged_in; a cancel emits user_logged_out and closes the window (src/cluster_erp/desktop/widgets/main_window.py:244-268).
Why it is built this way¶
- The project's written engineering rules state that screens declare their data dependencies with
watch_entity, that the API client emits the signals automatically, and that this replaces direct calls between modules. - Owner tracking: the docstring in
src/cluster_erp/desktop/core/signal_bus.py:62-67states that automatic disconnection prevents signal leaks and crashes. - Option-list invalidation: the comment in
src/cluster_erp/desktop/core/fk_cache.py:51-54states that a new record appears in drop-downs at once instead of after the cache lifetime.
Rules and boundaries¶
- A screen passes
entity=toApiClientfor a mutation that other screens may display.GenericApidoes this for create, update and delete, and emits the signal itself throughemit_entity_changefor bulk delete andPOSTactions (src/cluster_erp/desktop/core/generic_api.py:67-121). - When a duplicate tab is detected,
TabManager.open_tabdeletes the newly built widget, so itswatch_entityregistration ends with it (src/cluster_erp/desktop/core/tab_manager.py:97-107). See tabs, panes, and layout.
Interaction with other parts¶
MainWindowturnsnavigate_toandopen_entityinto tabs; see tabs, panes, and layout.FkCachesubscribes in its constructor whenmain()passes the bus (src/cluster_erp/desktop/main.py:224); see metadata store and foreign-key cache.- The signals live only inside one client process. Other clients see changes when they reload data. Server-side events between modules use the event bus.
Limits and open points¶
CompletenessPanelandPurchaseRfqComparisonPanelconnect to the raw change signals without a module filter and without owner tracking (src/cluster_erp/desktop/widgets/generic/completeness_panel.py:65-67,src/cluster_erp/desktop/widgets/generic/purchase_rfq_comparison_panel.py:92-93).- MRP screens post bulk actions without an
entitydescriptor, so they emit no change signal (src/cluster_erp/desktop/modules/procurement/mrp/planned_orders.py:411-417,src/cluster_erp/desktop/modules/procurement/mrp/exception_workbench.py:527-533). - The comment above
navigate_todescribes it as the navigation request thatMainWindowanswers by opening the module and section (src/cluster_erp/desktop/core/signal_bus.py:19-20). The signal is connected and emitted, as the table above shows. - Mutations started by
call_apirun in a worker thread, soemit_entity_changeis called on that thread (src/cluster_erp/desktop/core/api_worker.py:43-46). On which thread each connected callback runs: Not determined from code. No connection type is set insrc/cluster_erp/desktop/, found by searching forConnectionType.
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| signal bus | SignalBus |
The desktop QObject that carries change, session, navigation, and accessibility signals between screens. |
sinyal veri yolu | src/cluster_erp/desktop/core/signal_bus.py:18 |
| entity watch | watch_entity |
A subscription that calls a callback when a record of one module and entity type changes. | varlık izleme | src/cluster_erp/desktop/core/signal_bus.py:55 |
| change signal | entity_created, entity_updated, entity_deleted |
The signals that ApiClient emits after a successful mutation. |
değişiklik sinyali | src/cluster_erp/desktop/core/signal_bus.py:27-29 |
Related pages¶
- Desktop client
- API client and workers
- Metadata store and foreign-key cache
- Event bus
- Data refresh and change signals
Verified against commit 92f8051 on 2026-11-13.