"""Persisted user settings: which quads are enabled, per-module feature toggles, and quad ordering. Single JSON file, read at startup, written on every change. Modules never import this directly for their own feature flags; they receive a scoped view via ctx.feature(...) so the persistence format stays centralised. """ from __future__ import annotations import json from typing import Callable from paths import CONFIG_DIR, SETTINGS_FILE, ensure_dirs class Settings: def __init__(self) -> None: self._data: dict = {"quads": {}, "features": {}, "order": None, "favorites": []} self._listeners: list[Callable[[], None]] = [] self.load() # -- persistence ------------------------------------------------------- def load(self) -> None: path = SETTINGS_FILE if not path.exists(): # one-time migration from the old CONFIG_DIR location (pre state-dir) legacy = CONFIG_DIR / "settings.json" if legacy.exists(): path = legacy try: self._data.update(json.loads(path.read_text())) except (FileNotFoundError, json.JSONDecodeError): pass def save(self) -> None: ensure_dirs() SETTINGS_FILE.write_text(json.dumps(self._data, indent=2)) for cb in list(self._listeners): cb() def subscribe(self, cb: Callable[[], None]) -> None: self._listeners.append(cb) # -- quad enable/disable ---------------------------------------------- def quad_enabled(self, module_id: str, default: bool = True) -> bool: return bool(self._data["quads"].get(module_id, default)) def set_quad_enabled(self, module_id: str, value: bool) -> None: self._data["quads"][module_id] = bool(value) self.save() # -- per-module feature toggles --------------------------------------- def feature(self, module_id: str, feature_id: str, default: bool = True) -> bool: return bool(self._data["features"].get(module_id, {}).get(feature_id, default)) def set_feature(self, module_id: str, feature_id: str, value: bool) -> None: self._data["features"].setdefault(module_id, {})[feature_id] = bool(value) self.save() # -- favorites --------------------------------------------------------- def favorites(self) -> list[str]: return list(self._data.get("favorites", [])) def is_favorite(self, entry: str) -> bool: return entry in self._data.get("favorites", []) def toggle_favorite(self, entry: str) -> None: if not entry: return favs = self._data.setdefault("favorites", []) if entry in favs: favs.remove(entry) else: favs.append(entry) self.save() # -- ordering ---------------------------------------------------------- def order(self) -> list[str] | None: return self._data.get("order") def set_order(self, order: list[str]) -> None: self._data["order"] = order self.save()