plugin.py is 3,038 lines, the largest file in the repo by 1.7×
(export_bridge.py is next at 1,766). Every other module has one job; this one
has six. Filing the shape now, while the seams are fresh, rather than
discovering them again later.
What is actually in there
Measured by line band, not by impression:
| Lines |
Band |
Roughly |
| 246 |
Module-level helpers |
server_location, sanitize_host, _escape, _pairing_html (a 70-line HTML template living in the plugin module) |
| 914 |
Lifecycle, health, device sync |
startup/shutdown/runConcurrentThread, the device-subscription machinery, deviceUpdated/deviceDeleted, _health_tick, config-UI validate/close |
| 152 |
IWS HTTP handlers |
The Domio API surface — http_status/http_commission/http_decommission/http_diagnostics and their sync bodies |
| 289 |
matter-server menus |
Install/reinstall/restart/logs, manual commissioning, decommission, folder + node pickers |
| 574 |
Export dialog callbacks |
The whole Manage Matter Exports master-detail: candidates, roles, add/update/remove, summaries, warnings |
| 406 |
Export recovery + bridge agent |
Rebuild map, reset pairings, agent start/stop/diagnose, bridge install |
| 457 |
Pairing + fabric management |
Pair/unpair, fabric cache, the IWS pairing page, backup/restore |
The last four bands — 1,726 lines, 57% of the file — are menu and dialog
callbacks. They are in plugin.py only because Indigo resolves
<CallbackMethod> against the Plugin class, not because they belong beside
startup.
The constraint that shapes any split
Indigo resolves XML callbacks by name, as attributes on the Plugin class.
MenuItems.xml, Actions.xml, Devices.xml and PluginConfig.xml between
them name every menu*, get*, export*, validate* and actionControl*
method. Anything extracted must stay reachable as Plugin.<name> or the menu
silently breaks at runtime — Indigo logs a missing-callback error, no test
fails, and nothing at import time complains.
tests/test_plugin_module.py already guards exactly this
(test_menu_callbacks_exist_on_plugin, test_action_callbacks_exist_on_plugin,
test_dynamic_list_methods_exist_on_plugin), so the guardrail is in place
before the refactor rather than after. That is the reason to do this now.
So the split is mixins/delegation, not free-standing modules: the callback stays
a thin Plugin method, the body moves. Mixin classes (ExportDialogMixin,
PairingMenuMixin, ServerMenuMixin, HttpApiMixin) keep every name on the
class via MRO and need no change to a single XML file.
Why this is cheap right now
- 421 tests across 5 files already import
plugin and drive these methods
directly (test_plugin_behaviour.py, test_plugin_module.py,
test_export_menu.py, test_export_wiring.py, test_pairing_menu.py).
Behaviour is pinned; a move that changes behaviour fails loudly.
- No module imports
plugin.py back — verified, zero back-imports. The
dependency arrows all point away from it, so extraction cannot create a cycle.
- The repo runs 1.6:1 tests to code overall. This is the file where that
investment pays out.
Suggested order, smallest risk first
_pairing_html → its own module. A 70-line HTML template with no plugin
state; pure function, already tested through http_pairing. Free.
- IWS handlers →
HttpApiMixin (152 lines). Smallest real band, clearest
boundary — it is the Domio contract in docs/API.md, which is versioned
separately anyway.
- Export dialog →
ExportDialogMixin (574 lines). Biggest win. Pairs
naturally with export_store / export_catalog, which it already delegates
to.
- Pairing + fabric →
PairingMenuMixin (457 lines).
- Server + bridge menus →
ServerMenuMixin (695 lines across two bands).
Leaves plugin.py at roughly 900–1,100 lines of genuine lifecycle glue,
which is defensible for an Indigo plugin's entry point.
Explicitly not in scope
- No behaviour change, no renamed callbacks, no XML edits. If any
<CallbackMethod>
string changes, the refactor has gone wrong.
- Not a test reorganisation. The five test files keep importing
plugin; they
are testing the composed class, which is what Indigo instantiates.
endpoints.ts (1,662 lines) is not included. It is one factory per §4.2
role — a cohesive table, not six jobs in a trenchcoat.
plugin.pyis 3,038 lines, the largest file in the repo by 1.7×(
export_bridge.pyis next at 1,766). Every other module has one job; this onehas six. Filing the shape now, while the seams are fresh, rather than
discovering them again later.
What is actually in there
Measured by line band, not by impression:
server_location,sanitize_host,_escape,_pairing_html(a 70-line HTML template living in the plugin module)startup/shutdown/runConcurrentThread, the device-subscription machinery,deviceUpdated/deviceDeleted,_health_tick, config-UI validate/closehttp_status/http_commission/http_decommission/http_diagnosticsand their sync bodiesThe last four bands — 1,726 lines, 57% of the file — are menu and dialog
callbacks. They are in
plugin.pyonly because Indigo resolves<CallbackMethod>against thePluginclass, not because they belong besidestartup.The constraint that shapes any split
Indigo resolves XML callbacks by name, as attributes on the
Pluginclass.MenuItems.xml,Actions.xml,Devices.xmlandPluginConfig.xmlbetweenthem name every
menu*,get*,export*,validate*andactionControl*method. Anything extracted must stay reachable as
Plugin.<name>or the menusilently breaks at runtime — Indigo logs a missing-callback error, no test
fails, and nothing at import time complains.
tests/test_plugin_module.pyalready guards exactly this(
test_menu_callbacks_exist_on_plugin,test_action_callbacks_exist_on_plugin,test_dynamic_list_methods_exist_on_plugin), so the guardrail is in placebefore the refactor rather than after. That is the reason to do this now.
So the split is mixins/delegation, not free-standing modules: the callback stays
a thin
Pluginmethod, the body moves. Mixin classes (ExportDialogMixin,PairingMenuMixin,ServerMenuMixin,HttpApiMixin) keep every name on theclass via MRO and need no change to a single XML file.
Why this is cheap right now
pluginand drive these methodsdirectly (
test_plugin_behaviour.py,test_plugin_module.py,test_export_menu.py,test_export_wiring.py,test_pairing_menu.py).Behaviour is pinned; a move that changes behaviour fails loudly.
plugin.pyback — verified, zero back-imports. Thedependency arrows all point away from it, so extraction cannot create a cycle.
investment pays out.
Suggested order, smallest risk first
_pairing_html→ its own module. A 70-line HTML template with no pluginstate; pure function, already tested through
http_pairing. Free.HttpApiMixin(152 lines). Smallest real band, clearestboundary — it is the Domio contract in
docs/API.md, which is versionedseparately anyway.
ExportDialogMixin(574 lines). Biggest win. Pairsnaturally with
export_store/export_catalog, which it already delegatesto.
PairingMenuMixin(457 lines).ServerMenuMixin(695 lines across two bands).Leaves
plugin.pyat roughly 900–1,100 lines of genuine lifecycle glue,which is defensible for an Indigo plugin's entry point.
Explicitly not in scope
<CallbackMethod>string changes, the refactor has gone wrong.
plugin; theyare testing the composed class, which is what Indigo instantiates.
endpoints.ts(1,662 lines) is not included. It is one factory per §4.2role — a cohesive table, not six jobs in a trenchcoat.