Private
Public Access
0
0
Files
manual_slop/docs/guide_workspace_profiles.md
T

13 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:

  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 (src/models.py)

A snapshot of window state at a moment in time.

@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.
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:

[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

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:

  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 <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

[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 serialization
  • tests/test_workspace_profiles_sim.py — End-to-end via live_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 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 IntegrationConductorEngine calls auto_switch on 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.