Private
Public Access
0
0
Files
manual_slop/docs/guide_workspace_profiles.md
T
ed 46f0ec152a docs(guides): fix stale src/models.py refs + line-number drift across 11 guides
Sweep of the per-source-file guides + Readme.md for stale references
to src/models.py as the data model home. models.py is now a ~1.5KB
re-export shim per module_taxonomy_refactor_20260627; dataclasses
moved to src/mma.py, src/project_files.py, src/type_aliases.py,
src/mcp_tool_specs.py, src/result_types.py, src/context_presets.py,
src/workspace_manager.py, src/personas.py.

- guide_gui_2.md: _gui_func line 754->1062; render_main_interface
  line 1259->1898.
- python.md §10 exemption table: App gui_2.py:307->314; AppController
  795->801; RAGEngine 123->125; HookServer 856->941;
  HookServerInstance 130->171; HookHandler 155->208;
  WebSocketServer 908->993.
- guide_multi_agent_conductor.md: Ticket now in src/mma.py (not
  models.py); ConductorEngine 116+->112+; WorkerPool 50-114->52-110.
- guide_agent_memory_dimensions.md: FileItem ref models.py:510-559
  -> src/project_files.py.
- guide_context_aggregation.md: FileItem + ContextPreset refs ->
  src/project_files.py + src/context_presets.py; ai_client _send_*
  count 5->8.
- guide_discussions.md: parse_history_entries now in src/mma.py;
  ContextPreset/FileItem cross-refs updated.
- guide_personas.md: Persona now in src/personas.py; import example
  updated.
- guide_rag.md: RAGConfig now in src/mcp_client.py.
- guide_workspace_profiles.md: WorkspaceProfile now in
  src/workspace_manager.py.
- guide_mma.md: Data Structures section notes src/mma.py as the live
  location.
- Readme.md: MMA Engine + Data Models rows updated for the
  models.py shim reality + 8 providers + mma_exec deprecation.
- guide_ai_client.md: '5 provider SDKs'->8 (added note); PROVIDERS
  line 56->62; __getattr__ re-export line 261->31; provider
  switching examples use real registered model names (claude-sonnet-
  4-5, MiniMax-M2, gemini-2.5-flash, qwen-plus, grok-2, llama-3.1);
  _provider union lists all 8 providers.
2026-07-02 19:25:33 -04:00

306 lines
14 KiB
Markdown

# 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: <user_config>/workspace_profiles.toml│
│ - Project: <project_root>/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` (defined in `src/workspace_manager.py`; moved out of `src/models.py` per `module_taxonomy_refactor_20260627`)
A snapshot of window state at a moment in time.
```python
class WorkspaceProfile:
name: str
ini_content: str # ImGui ini settings string (from SaveIniSettingsToMemory)
show_windows: Dict[str, bool] = field(default_factory=dict) # {"mma_dashboard": True, "context_panel": True, ...}
panel_states: Dict[str, Any] = field(default_factory=dict) # Per-panel UI state (collapsed flags, scroll positions, etc.)
```
| Field | Type | Purpose |
|---|---|---|
| `name` | `str` | Unique identifier within the scope. |
| `ini_content` | `str` | ImGui ini settings string from `SaveIniSettingsToMemory()`. Plain string in TOML (not base64). |
| `show_windows` | `Dict[str, bool]` | Maps window name to visibility. Windows not in the dict use the default (visible). |
| `panel_states` | `Dict[str, Any]` | Per-panel UI state (collapsed flags, scroll positions, value overrides). See `App._capture_workspace_profile` in `src/gui_2.py` for the captured key set. |
### Serialization
The `ini_content` is captured via `ImGui.SaveIniSettingsToMemory()` as a plain string. On load, `ImGui.LoadIniSettingsFromMemory(ini_data)` restores the layout. The `to_dict`/`from_dict` methods on `WorkspaceProfile` handle the TOML round-trip via the standard `models` registry.
> **Type contract note:** `ini_content` is `str`, not `bytes`. The previous base64-encoded-bytes design was removed in the 2026-06-05 `live_gui_fragility_fixes_20260605` track. A regression that used `b""` as a sentinel caused `tomli_w` to raise `TypeError: Object of type 'bytes' is not TOML serializable`. The regression test `tests/test_workspace_profile_serialization.py` encodes this contract. See the sentinel type contract rule in [guide_testing.md](guide_testing.md) and [guide_gui_2.md](guide_gui_2.md).
The TOML representation:
```toml
[profiles.focused_work]
ini_content = "[Window][Project Manager]\nPos=8,8\nSize=400,400\n..."
show_windows = { mma_dashboard = false, context_panel = true, ai_settings = false }
panel_states = { mma_dashboard_collapsed = false, context_panel_scroll_y = 0.0 }
```
---
## 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 `ini_content` via `ImGui.LoadIniSettingsFromMemory(ini_content)`.
3. Apply `show_windows` to the live windows (hide/show as needed).
4. Apply `panel_states` to the per-panel settable fields via `App._apply_workspace_profile` (which iterates `profile.panel_states.items()` and `setattr`s on the App — see `src/gui_2.py:844-848`).
Activation is immediate; the next frame renders the new layout. Theme is NOT part of the profile (themes are managed by the separate theme system; see [guide_themes.md](guide_themes.md)).
### Capturing Current State
`capture_current_as(name, scope)`:
1. Read the current ImGui state via `ImGui.SaveIniSettingsToMemory()` (with the defer-not-catch guard `_ini_capture_ready`).
2. Snapshot window visibility into `show_windows`.
3. Snapshot per-panel state (collapsed flags, scroll positions, value overrides) into `panel_states`.
4. Save to the specified scope.
This is the "Save Profile" action in the GUI. Theme is NOT captured (see `activate_profile` above).
---
## Scope Inheritance
| Scope | Path |
|---|---|
| Global | `<user_config>/workspace_profiles.toml` |
| Project | `<project_root>/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_name>` | `tier:tier3-worker` |
| Project | `project:<project_name>` | `project:my_app` |
| User-defined | `<custom>` | `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 `ini_content` is the raw ImGui ini settings string from `SaveIniSettingsToMemory()`. It's machine-generated text (not human-readable, but parseable). Tests typically use a "save, modify, restore" pattern rather than asserting specific layout details. The `panel_states` dict, by contrast, is human-readable and testable.
---
## Limitations
1. **Theme is Not Captured**: Themes are managed by the separate theme system (see [guide_themes.md](guide_themes.md)) and are global. A profile captures docking layout, window visibility, and panel state — but not the active theme. Switching profiles does not change the theme.
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.