The 2026-06-05 live_gui_fragility_fixes refactor replaced the old 7-field WorkspaceProfile (docking_layout: bytes, window_visibility, theme, theme_fx_enabled, captured_at, description) with a 4-field model: ini_content: str, show_windows, panel_states. tomli_w rejects bytes, so the ini_content is now a plain ImGui ini string, not base64. - Update Data Model class example + field table - Update Serialization section + TOML example - Update Profile Activation + Capturing Current State steps - Update Layout Stability note (binary blob -> raw ini string) - Replace 'Theme FX State is Global' limitation with 'Theme is Not Captured'
14 KiB
Workspace Profiles (Docking Layouts)
Top | Architecture | MMA | Simulations
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:
- Architecture — Where WorkspaceProfile fits in the rendering system
- Data Model — The
WorkspaceProfileschema - WorkspaceManager — CRUD and activation
- Scope Inheritance — Global vs Project profiles
- Contextual Auto-Switch — Experimental feature for binding profiles to context
- 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 (src/models.py)
A snapshot of window state at a moment in time.
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_contentisstr, notbytes. The previous base64-encoded-bytes design was removed in the 2026-06-05live_gui_fragility_fixes_20260605track. A regression that usedb""as a sentinel causedtomli_wto raiseTypeError: Object of type 'bytes' is not TOML serializable. The regression testtests/test_workspace_profile_serialization.pyencodes this contract. See the sentinel type contract rule in guide_testing.md and guide_gui_2.md.
The TOML representation:
[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
from src.workspace_manager import WorkspaceManager
manager = WorkspaceManager(project_root=Path("/path/to/project"))
Methods
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:
- Read the profile from the loaded dict.
- Apply
ini_contentviaImGui.LoadIniSettingsFromMemory(ini_content). - Apply
show_windowsto the live windows (hide/show as needed). - Apply
panel_statesto the per-panel settable fields viaApp._apply_panel_states.
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).
Capturing Current State
capture_current_as(name, scope):
- Read the current ImGui state via
ImGui.SaveIniSettingsToMemory()(with the defer-not-catch guard_ini_capture_ready). - Snapshot window visibility into
show_windows. - Snapshot per-panel state (collapsed flags, scroll positions, value overrides) into
panel_states. - 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_workwith theme"dark". - Project has
focused_workwith 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
[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).
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
[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 serializationtests/test_workspace_profiles_sim.py— End-to-end vialive_gui: save, switch, verify layout
Test Pattern
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
-
Theme is Not Captured: Themes are managed by the separate theme system (see 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.
-
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.
-
No Profile Composition: A profile cannot inherit from another. Duplication is the only way to share settings.
-
ImGui-Specific: Profiles are tightly coupled to ImGui's docking format. Migrating to a different GUI framework would require a profile format migration.
-
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.
-
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 —
ConductorEnginecallsauto_switchon tier transitions. See 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 for the broader workspace profile integration roadmap.