Files

2.2 KiB

Config I/O State Ownership

Rule: The AppController is the single source of truth for the in-memory config (self.config) and the only authorized caller of the file I/O primitives (the config-load/save helpers, which moved out of src/models.py to src/project_manager.py per module_taxonomy_refactor_20260627).

Why

  1. The controller owns the in-memory state. If other modules write to config.toml directly, the controller's self.config silently drifts from disk. Tests can corrupt the user's TOML files; users lose data without warning.
  2. Test isolation breaks. When models.save_config(...) is called from anywhere in src/, tests cannot intercept the write without patching the I/O primitive. The test then couples to the file format, not the controller's behavior.
  3. Path resolution can't be enforced. The controller respects SLOP_CONFIG env var at call time. Direct calls to models.save_config would only respect it if the path is re-resolved (which it is in _save_config_to_disk, but only because someone remembered).

What is Forbidden in src/

  • models.load_config(...) (legacy public function)
  • models.save_config(...) (legacy public function)
  • models._load_config_from_disk(...) (private I/O primitive)
  • models._save_config_to_disk(...) (private I/O primitive)

The only allowed call sites are inside AppController itself (load_config() and save_config() methods).

The Public API

# In AppController:
def load_config(self) -> Dict[str, Any]:
    """Re-read the global config.toml from disk and update self.config."""
    self.config = models._load_config_from_disk()
    return self.config

def save_config(self) -> None:
    """Flush self.config to disk."""
    models._save_config_to_disk(self.config)

Callers (including gui_2.py, commands.py, etc.) go through the controller:

# In App class methods (gui_2.py): __getattr__ delegates to controller
self.save_config()      # -> controller.save_config()
app.save_config()       # -> controller.save_config() (via __getattr__)
app.load_config()       # -> controller.load_config() (via __getattr__)

# In AppController:
self.save_config()      # direct
self.load_config()      # direct