Skip to content

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