From 5379312bc748164d2becacd5c18e3aafcfaed82a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 2 Jun 2026 19:55:08 -0400 Subject: [PATCH] docs(workspace-profiles): new guide covering profile schema, manager, scope inheritance, and auto-switch --- docs/guide_workspace_profiles.md | 320 +++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 docs/guide_workspace_profiles.md diff --git a/docs/guide_workspace_profiles.md b/docs/guide_workspace_profiles.md new file mode 100644 index 00000000..ad2dc4cb --- /dev/null +++ b/docs/guide_workspace_profiles.md @@ -0,0 +1,320 @@ +# Workspace Profiles (Docking Layouts) + +[Top](../README.md) | [Architecture](guide_architecture.md) | [MMA](guide_mma.md) | [Simulations](guide_simulations.md) + +--- + +## Overview + +Workspace Profiles are named, persistent snapshots of the ImGui docking layout, window visibility, and panel state. Users can save their current layout as a profile, switch between profiles on demand, and have profiles auto-apply based on context (active project, active MMA tier, or current task). + +This is essential for multi-monitor workflows and for users who frequently switch between "focused work" (one or two panels visible) and "broad context" (all panels visible across multiple monitors). + +This guide covers: + +1. **Architecture** — Where WorkspaceProfile fits in the rendering system +2. **Data Model** — The `WorkspaceProfile` schema +3. **WorkspaceManager** — CRUD and activation +4. **Scope Inheritance** — Global vs Project profiles +5. **Contextual Auto-Switch** — Experimental feature for binding profiles to context +6. **Testing** — Test patterns + +--- + +## Architecture + +Workspace Profiles live at the boundary between the ImGui render loop and the persistent configuration system. The render loop reads the active profile; the WorkspaceManager updates the active profile and persists changes. + +``` +┌────────────────────────────────────────────────┐ +│ ImGui Render Loop │ +│ - Applies docking layout from active profile │ +│ - Reads window visibility / position from │ +│ active profile at startup │ +│ - Updates active profile when user resizes │ +│ / moves / shows / hides windows │ +└─────────────────┬──────────────────────────────┘ + │ reads / writes + ▼ +┌────────────────────────────────────────────────┐ +│ WorkspaceManager (src/workspace_manager.py) │ +│ - Profiles dict (active + saved) │ +│ - save_profile(name, scope) │ +│ - load_profile(name) │ +│ - delete_profile(name, scope) │ +│ - bind_to_context(context_id, profile_name) │ +│ - auto_switch(context_id) │ +└─────────────────┬──────────────────────────────┘ + │ persists to + ▼ +┌────────────────────────────────────────────────┐ +│ TOML Files │ +│ - Global: /workspace_profiles.toml│ +│ - Project: /workspace_profiles.toml│ +└────────────────────────────────────────────────┘ +``` + +**Lifecycle**: +- On application startup, the last-active profile is loaded (per-project or global, depending on the loaded project). +- On user action (resize, move, show, hide), the active profile is updated in memory. +- On explicit "Save Profile" or application exit, the active profile is persisted to disk. +- Profile activation (manual or auto-switch) triggers a layout re-apply. + +--- + +## Data Model + +### `WorkspaceProfile` (`src/models.py`) + +A snapshot of window state at a moment in time. + +```python +@dataclass +class WorkspaceProfile: + name: str + # Window state (ImGui docking layout) + docking_layout: bytes # Serialized ImGui docking configuration + # Window visibility per window name + window_visibility: Dict[str, bool] # {"mma_dashboard": True, "context_panel": True, ...} + # Active theme + theme: str = "dark" # "dark" | "light" | "nerv" | ... + # Active theme FX state + theme_fx_enabled: bool = True + # Capture metadata + captured_at: str = "" # ISO 8601 timestamp + description: str = "" # Optional human description +``` + +| Field | Type | Purpose | +|---|---|---| +| `name` | `str` | Unique identifier within the scope. | +| `docking_layout` | `bytes` | Serialized ImGui docking configuration. Stored as a base64-encoded byte string in TOML. | +| `window_visibility` | `Dict[str, bool]` | Maps window name to visibility. Windows not in the dict use the default (visible). | +| `theme` | `str` | Active theme name. See [guide_nerv_theme.md](guide_nerv_theme.md). | +| `theme_fx_enabled` | `bool` | Whether theme FX (e.g., NERV scanlines) are enabled. | +| `captured_at` | `str` | ISO 8601 timestamp of when the profile was captured. | +| `description` | `str` | Optional human-readable description. | + +### Serialization + +The `docking_layout` is captured via ImGui's `SaveIniSettingsToMemory()` and serialized as bytes. On load, `LoadIniSettingsFromMemory()` restores the layout. + +The TOML representation uses base64 for the binary layout: + +```toml +[profiles.focused_work] +docking_layout = "BASE64_ENCODED_BYTES" +window_visibility = { mma_dashboard = false, context_panel = true, ai_settings = false } +theme = "nerv" +theme_fx_enabled = true +captured_at = "2026-05-15T14:32:05" +description = "Minimal layout for focused code writing" +``` + +--- + +## WorkspaceManager + +```python +from src.workspace_manager import WorkspaceManager + +manager = WorkspaceManager(project_root=Path("/path/to/project")) +``` + +### Methods + +```python +def load_all_profiles(self) -> Dict[str, WorkspaceProfile]: + """Merges global and project profiles into a single dict.""" + +def save_profile(self, profile: WorkspaceProfile, scope: str = "project") -> None: + """Saves a profile to the specified scope (global or project).""" + +def delete_profile(self, name: str, scope: str = "project") -> None: + """Removes a profile from the specified scope.""" + +def activate_profile(self, name: str) -> None: + """Applies the profile's layout, visibility, and theme to the live GUI.""" + +def capture_current_as(self, name: str, scope: str = "project") -> WorkspaceProfile: + """Captures the current GUI state as a new profile.""" + +def bind_to_context(self, context_id: str, profile_name: str) -> None: + """Binds a profile to a context (active tier, project, etc.) for auto-switching.""" + +def auto_switch(self, context_id: str) -> bool: + """If a profile is bound to this context, activates it. Returns True if switched.""" +``` + +### Profile Activation + +`activate_profile(name)` performs: +1. Read the profile from the loaded dict. +2. Apply `docking_layout` via `ImGui.LoadIniSettingsFromMemory()`. +3. Apply `window_visibility` to the live windows (hide/show as needed). +4. Apply the theme via the theme system. +5. Set the theme FX state. + +Activation is immediate; the next frame renders the new layout. + +### Capturing Current State + +`capture_current_as(name, scope)`: +1. Read the current ImGui state via `ImGui.SaveIniSettingsToMemory()`. +2. Snapshot window visibility. +3. Read the active theme from the theme system. +4. Set the timestamp and optional description. +5. Save to the specified scope. + +This is the "Save Profile" action in the GUI. + +--- + +## Scope Inheritance + +| Scope | Path | +|---|---| +| Global | `/workspace_profiles.toml` | +| Project | `/workspace_profiles.toml` | + +**Merge rule**: `load_all_profiles()` returns global first, then project entries **override** globals of the same name. This is the same pattern as personas and presets. + +**Example**: +- Global has `focused_work` with theme `"dark"`. +- Project has `focused_work` with theme `"nerv"`. +- The project version wins for this project. Other projects without the override use the global version. + +**When to use which**: +- **Global**: Layouts you want across all projects (e.g., "minimal layout for reading", "presentation layout"). +- **Project**: Layouts specific to a project's workflow (e.g., "Python project layout with discussion at bottom"). + +--- + +## Contextual Auto-Switch (Experimental) + +The `bind_to_context(context_id, profile_name)` and `auto_switch(context_id)` methods enable automatic profile switching based on context. + +### Context IDs + +| Context | Format | Example | +|---|---|---| +| MMA Tier | `tier:` | `tier:tier3-worker` | +| Project | `project:` | `project:my_app` | +| User-defined | `` | `code_review`, `documentation` | + +### Activation + +`auto_switch(context_id)` is called on: +- MMA tier transitions (e.g., when moving from Tier 1 to Tier 2) +- Project load/unload +- User-defined triggers (via the GUI's "Apply Profile to Context" action) + +If a profile is bound to the context, it's activated. Otherwise, the current profile remains. + +### Configuration + +```toml +[workspace] +auto_switch_profiles = false # Master toggle; default off +``` + +When `auto_switch_profiles = false` (the default), `auto_switch` is a no-op. Set to `true` to enable. + +### Current Status (as of 2026-06-02) + +The `bind_to_context` and `auto_switch` methods exist on `WorkspaceManager`. The MMA's `ConductorEngine` does **not** yet call `auto_switch` on tier transitions. This is planned integration work (see [guide_mma.md#workspace-profile-auto-switching-roadmap](guide_mma.md#workspace-profile-auto-switching-roadmap)). + +--- + +## GUI Integration + +### Profile Selector + +A dropdown in the View menu lists all available profiles. Selecting one activates it. + +### Save Profile + +A "Save Current as Profile..." action in the View menu prompts for a name and scope (Global / Project), then calls `capture_current_as()`. + +### Delete Profile + +A "Delete Profile" action removes the active profile (with confirmation). + +### Bind to Context (Experimental) + +A "Bind Profile to Context..." action lets the user specify a context ID and bind the current profile. This is gated behind the `auto_switch_profiles` config flag. + +--- + +## Configuration + +```toml +[workspace] +auto_switch_profiles = false +last_active_profile = "default" # Set automatically on activation +``` + +Per-project overrides go in `manual_slop.toml` under `[workspace]`. + +--- + +## Testing + +### Unit Tests + +- `tests/test_workspace_manager.py` — CRUD, scope merging, profile serialization +- `tests/test_workspace_profiles_sim.py` — End-to-end via `live_gui`: save, switch, verify layout + +### Test Pattern + +```python +def test_profile_save_and_load(tmp_path, live_gui): + # Set up a project + # ... (live_gui fixture handles this) + + # Capture the current state as a profile + client.capture_current_as("test_profile", scope="project") + + # Modify the layout (hide a panel) + client.hide_panel("mma_dashboard") + + # Activate the saved profile + client.activate_profile("test_profile") + + # Verify the panel is visible again + state = client.get_window_state("mma_dashboard") + assert state["visible"] == True +``` + +### Layout Stability + +The `docking_layout` is a binary blob (base64 in TOML). It's not human-readable, so tests typically use a "save, modify, restore" pattern rather than asserting specific layout details. + +--- + +## Limitations + +1. **Theme FX State is Global**: NERV FX and other theme effects are global settings, not per-profile. Saving a profile with `theme_fx_enabled = false` and then activating it would change the global setting. + +2. **No Profile Variants per Monitor**: A single profile applies to all connected monitors. Multi-monitor users with different layouts per monitor must use ImGui's native multi-viewport support, not profiles. + +3. **No Profile Composition**: A profile cannot inherit from another. Duplication is the only way to share settings. + +4. **ImGui-Specific**: Profiles are tightly coupled to ImGui's docking format. Migrating to a different GUI framework would require a profile format migration. + +5. **No Undo for Profile Activation**: Activating a profile changes the layout immediately. If the user doesn't like it, they must switch to another profile manually. + +6. **Contextual Auto-Switch Is Off by Default**: Requires explicit opt-in via `auto_switch_profiles = true`. Even when on, the MMA integration is not yet implemented. + +--- + +## Future Work + +- **MMA Integration** — `ConductorEngine` calls `auto_switch` on tier transitions. See [guide_mma.md#workspace-profile-auto-switching-roadmap](guide_mma.md#workspace-profile-auto-switching-roadmap). +- **Per-Monitor Layouts** — Capture and restore layouts per connected monitor. +- **Profile Composition** — Allow a profile to inherit from a base profile. +- **Animated Transitions** — Smoothly animate the layout change rather than snapping. +- **Profile Templates** — Pre-built profiles for common workflows. +- **Versioned Profiles** — Allow multiple versions of a profile, switchable via the GUI. + +See [guide_mma.md](guide_mma.md) for the broader workspace profile integration roadmap.