Manager Pattern¶
Overview¶
codemol uses a manager pattern to organize domain logic. Instead of putting everything in MolbotWindow, each domain concern gets its own manager class. All managers receive a reference to the window (as AppContext) and can access the viewer, console, tool registry, and other shared state through that protocol.
The 8 Domain Managers¶
| Manager | File | Responsibility |
|---|---|---|
UIManager |
app/ui_manager.py |
General UI helpers, dialogs, notifications |
SceneManager |
app/scene_manager.py |
Timeline, scene saving, frame navigation |
AtlasManager |
app/atlas_manager.py |
Atlas/Nomosis API integration |
AgentManager |
app/agent_manager.py |
LLM agent UI and worker thread |
DockingManager |
app/docking_manager.py |
GOLD docking results, panels |
ShareManager |
app/share_manager.py |
WebSocket shared sessions |
MeasurementManager |
app/measurement_manager.py |
Measurement registry and panels |
StructureManager |
app/structure_manager.py |
Multi-structure sidebar, activation |
The 4 Config Managers¶
| Manager | File | Responsibility |
|---|---|---|
AliasManager |
config/alias_manager.py |
Command aliases (~/.codemol/aliases.json) |
ConfigManager |
config/config_manager.py |
User settings (~/.codemol/config.json) |
PresetManager |
config/preset_manager.py |
Visual representation presets |
SessionManager |
config/session_manager.py |
Session save/load |
AppContext Protocol¶
All managers interact with the window through the AppContext protocol, defined in codemol/app/protocols.py:
class AppContext(Protocol):
@property
def viewer(self) -> Viewer: ...
@property
def console(self) -> Console: ...
@property
def session(self) -> StructureSession: ...
@property
def tools(self) -> dict: ...
def scope(self) -> str | None: ...
def handle_command(self, text: str) -> None: ...
def update_status_bar(self, **kwargs) -> None: ...
def update_window_title(self, name: str = "") -> None: ...
Why a Protocol?
Using typing.Protocol instead of a base class avoids circular imports. Managers import AppContext from protocols.py — they never import window.py directly. The window satisfies the protocol structurally (duck typing).
Manager Instantiation¶
In MolbotWindow.__init__():
# Domain managers — each takes self (the AppContext)
self.ui_mgr = UIManager(self)
self.scene_mgr = SceneManager(self)
self.atlas_mgr = AtlasManager(self)
self.agent_mgr = AgentManager(self)
self.docking_mgr = DockingManager(self)
self.share_mgr = ShareManager(self)
self.measurement_mgr = MeasurementManager(self)
self.structure_mgr = StructureManager(self)
# Config managers — standalone, no AppContext needed
self._alias_manager = AliasManager()
self._config_manager = ConfigManager()
self._preset_manager = PresetManager()
self._session_manager = SessionManager()
Single Responsibility¶
Each manager owns one domain. For example:
- StructureManager handles the sidebar panel, structure activation, visibility toggling — but it delegates actual object management to
StructureSession. - MeasurementManager manages the measurement panel UI — but the actual distance/angle calculations happen in tool modules.
- AgentManager manages the agent worker thread and UI — but the agent loop logic lives in
agents/agent.py.
This keeps MolbotWindow as a mediator: it routes commands and connects managers but doesn't implement domain logic itself.
graph TD
W[MolbotWindow<br/>mediator] --> SM[StructureManager]
W --> MM[MeasurementManager]
W --> AM[AgentManager]
W --> DM[DockingManager]
W --> ShM[ShareManager]
W --> ScM[SceneManager]
W --> AtM[AtlasManager]
W --> UM[UIManager]
SM --> SS[StructureSession]
AM --> AG[agents/agent.py]
ShM --> WS[share/server.py]
W -->|AppContext| P[Protocol]
SM -->|AppContext| P
MM -->|AppContext| P