Files
XCEngine/docs/used/NewEditor_WorkspaceUtilityWindowSemanticSplitPlan_2026-04-22.md

18 KiB

NewEditor Workspace Utility Window Semantic Split Plan

Date: 2026-04-22 Status: Complete and Archived

0. Execution Progress

Completed on 2026-04-22

Phase A shell/content split foundation is now in place.

Completed changes:

  1. EditorWindow no longer constructs itself from UIEditorWorkspaceController
  2. EditorWindowRuntimeController no longer directly owns m_workspaceController
  3. EditorWindowHostRuntime::CreateEditorWindow(...) no longer accepts raw workspace controller input
  4. a new content abstraction now exists:
    • EditorWindowContentController
    • EditorWorkspaceWindowContentController
  5. current workspace-backed behavior has been moved behind the new content layer without changing workspace semantics
  6. detached workspace chrome policy now reads window-content capabilities instead of shell-owned workspace state
  7. XCUIEditorApp Debug build passes after this phase

What is intentionally not done yet:

  1. utility window domain does not exist yet
  2. color picker still opens through detached panel workflow
  3. toolWindow still exists in workspace panel descriptors as legacy leakage to remove in later phases

This means the root shell/content ownership boundary has been extracted first, while feature semantics remain unchanged for safety.

Completed on 2026-04-22

Phase C, Phase D, and Phase E implementation are now in place.

Completed changes:

  1. a first-class utility window domain now exists:
    • EditorUtilityWindowKind
    • EditorWindowOpenUtilityWindowRequest
    • EditorUtilityWindowCoordinator
    • EditorUtilityWindowRegistry
  2. color picker open intent no longer routes through detached workspace mutation
  3. the color picker now opens through utility-window coordination and utility content
  4. color picker has been removed from:
    • UIEditorPanelRegistry
    • shell hosted-panel composition
    • workspace detached-panel request plumbing
  5. openDetachedPanel has been deleted from frame transfer routing
  6. UIEditorWindowWorkspaceController::OpenPanelInNewWindow(...) has been deleted
  7. toolWindow and related workspace utility inference have been deleted
  8. detached floating-window placement logic is now shared instead of duplicated
  9. XCUIEditorApp Debug build passes after this phase

Verified and Archived on 2026-04-22

Section 10 manual verification has now passed.

Verified outcomes:

  1. workspace regression checks passed
  2. utility window checks passed
  3. cross-boundary checks passed
  4. XCUIEditorApp Debug build passes
  5. XCUIEditorApp Release build passes

1. Objective

This plan solves the problem from the architectural root, not by adding another special case.

The goal is to completely separate:

  1. native window shell
  2. workspace-backed dockable windows
  3. utility windows that are inherently standalone

The immediate trigger is the color picker window, but the real issue is broader:

  1. EditorWindow is still specialized around UIEditorWorkspaceController
  2. utility windows are currently modeled as panels with a toolWindow flag
  3. the color picker is opened through openDetachedPanel
  4. utility window policy is inferred indirectly from workspace root content

This architecture is semantically wrong.

If a window is inherently standalone and can never merge back into the main workspace, it must not live inside the workspace panel model.

2. Architectural Judgment

The current implementation shares too much.

Sharing the native host is correct:

  1. HWND lifetime
  2. D3D render loop
  3. DPI handling
  4. input capture
  5. title bar / chrome
  6. screenshot and frame pacing

Sharing the workspace panel abstraction is not correct for the color picker.

The color picker is currently treated as:

  1. a panel descriptor in UIEditorPanelRegistry
  2. a hosted panel inside shell composition
  3. a detached panel opened via OpenPanelInNewWindow(...)
  4. a single-root detached workspace that is merely styled as a tool window

That means the color picker still belongs to the dock / detach / re-dock / transfer universe.

This is the wrong owner domain.

3. Confirmed Root Cause

The root cause is not one bad function.

The root cause is that the codebase currently conflates two different semantics:

  1. workspace window
    • owns a UIEditorWorkspaceController
    • participates in layout, docking, tab stacks, cross-window transfer
    • can detach and re-merge
  2. utility window
    • owns standalone tool content
    • does not belong to workspace layout
    • does not expose nodeId / panelId docking semantics
    • cannot merge into the main window

Today those two semantics are both forced through EditorWindow + UIEditorWorkspaceController.

That is why the color picker can only exist as a fake panel.

4. Current Coupling Points

These are the concrete places where the semantic mistake is encoded today:

  1. new_editor/app/Platform/Win32/EditorWindow.h
    • EditorWindow constructor directly requires UIEditorWorkspaceController
  2. new_editor/app/Platform/Win32/EditorWindowRuntimeController.h
    • runtime directly stores m_workspaceController
  3. new_editor/app/Platform/Win32/EditorWindowTransferRequests.h
    • utility-open intent is modeled as EditorWindowOpenDetachedPanelRequest
  4. new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp
    • color picker open is converted into openDetachedPanel
  5. new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp
    • color picker window creation is routed through workspace mutation
  6. new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp
    • OpenPanelInNewWindow(...) is used to create the color picker window
  7. new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h
    • toolWindow is attached to panel descriptors
  8. new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp
    • utility semantics are inferred from detached workspace root content
  9. new_editor/app/Composition/EditorShellAssetBuilder.cpp
    • color picker is registered as a panel
  10. new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp
    • color picker lives inside hosted panel update / dispatch infrastructure
  11. new_editor/app/State/EditorColorPickerToolState.h
    • color picker open intent is expressed as requestOpenDetachedPanel

This is not one bug. This is an ownership boundary failure.

5. Refactor Red Lines

The refactor must not degrade into any of the following:

  1. keeping the color picker in UIEditorPanelRegistry and merely adding more if (panelId == "color-picker") branches
  2. keeping toolWindow as the long-term mechanism for non-workspace windows
  3. keeping openDetachedPanel as the generic request type for utility windows
  4. allowing utility windows to remain inside UIEditorWindowWorkspaceSet
  5. preserving nodeId / panelId transfer semantics for utility windows
  6. building a second special-case bypass inside EditorWindowWorkspaceCoordinator
  7. renaming the current path without changing ownership

If any of those remain in the end state, the architecture is still wrong.

6. Target End State

The correct end state has three layers.

6.1 Native Shell Layer

A shared native shell owns only platform and rendering responsibilities:

  1. HWND creation and destruction
  2. D3D renderer / swap chain / present loop
  3. window chrome and title
  4. DPI, sizing, screenshot, pointer capture plumbing
  5. input event collection and dispatch to content

This layer must not know whether the content is workspace-backed or utility-backed.

Suggested shape:

  1. EditorNativeWindowShell
  2. EditorNativeWindowRuntime
  3. IEditorWindowContentController

The names can change, but the boundary cannot.

6.2 Workspace Window Layer

Workspace windows are the only windows allowed to own:

  1. UIEditorWorkspaceController
  2. EditorShellRuntime
  3. dock host interaction state
  4. detached panel transfer requests
  5. cross-window tab drag and dock / re-dock semantics

A workspace window can:

  1. represent the main window
  2. represent a detached panel window
  3. merge back into another workspace window

Suggested shape:

  1. EditorWorkspaceWindowContent
  2. EditorWorkspaceWindowCoordinator
  3. EditorWorkspaceWindowTransferRequests

6.3 Utility Window Layer

Utility windows are standalone tool surfaces.

They must not own:

  1. UIEditorWorkspaceController
  2. dock layout
  3. tab stack state
  4. nodeId
  5. panelId workspace transfer semantics

They may own:

  1. dedicated tool state
  2. dedicated rendering and input logic
  3. explicit request / response channels to editor features
  4. separate focus / reuse / close rules

Suggested shape:

  1. EditorUtilityWindowContent
  2. EditorUtilityWindowCoordinator
  3. EditorOpenUtilityWindowRequest
  4. EditorUtilityWindowKind
  5. EditorColorPickerUtilityWindowContent

7. Required Ownership Changes

The refactor must move ownership, not just code location.

7.1 Window Identity

Today:

  1. detached color picker window identity is derived from panelId
  2. detached utility behavior is inferred from the root workspace panel descriptor

After refactor:

  1. workspace windows are identified inside workspace domain
  2. utility windows are identified inside utility domain
  3. utility window identity is no longer a panel identity

7.2 Open Intent

Today:

  1. color picker open intent means "open this panel in a detached window"

After refactor:

  1. color picker open intent means "open or focus utility window of kind color picker"
  2. the request carries explicit tool payload:
    • initial color
    • alpha mode
    • inspector target binding
    • focus / reuse policy

7.3 Persistence

Today:

  1. utility-like windows still pass through workspace layout concepts

After refactor:

  1. workspace window state remains in workspace layout persistence
  2. utility window state uses separate persistence or no persistence
  3. utility windows never enter UIEditorWindowWorkspaceSet

7.4 Visual Policy

Today:

  1. tool-window policy is inferred from detached workspace content

After refactor:

  1. workspace detached-window policy is only about workspace windows
  2. utility-window policy belongs to utility window descriptors or utility content

8. Migration Strategy

The migration must happen in strict phases.

Phase A. Extract a Content-Neutral Native Window Shell

Goal:

  1. stop making the native window host depend directly on UIEditorWorkspaceController

Required changes:

  1. split the current EditorWindow responsibilities into:
    • shell responsibilities
    • content responsibilities
  2. extract a content interface for:
    • update
    • draw
    • input dispatch
    • external preview / title contributions if needed
  3. make EditorWindowHostRuntime::CreateEditorWindow(...) create a shell plus content, not a workspace-bound window object

Success criteria:

  1. a shell instance can host workspace content
  2. a shell instance can host non-workspace content
  3. no shell constructor directly requires UIEditorWorkspaceController

Red line:

  1. do not keep a hidden m_workspaceController in shell-level runtime

Phase B. Move Existing Workspace Windows onto the New Content Layer

Goal:

  1. preserve all existing main-window and detached-panel behavior while isolating it into workspace-specific content

Required changes:

  1. create EditorWorkspaceWindowContent
  2. move current workspace-only responsibilities into it:
    • UIEditorWorkspaceController
    • EditorShellRuntime
    • workspace frame orchestration
    • dock transfer request generation
  3. keep EditorWindowWorkspaceCoordinator but narrow its responsibility to workspace windows only

Success criteria:

  1. main window still works
  2. detached panel windows still work
  3. cross-window tab drag and re-dock still work
  4. utility windows are still not introduced yet, but the shell is already ready for them

Phase C. Introduce a Real Utility Window Domain

Goal:

  1. create a first-class utility window pipeline independent from workspace mutation

Required changes:

  1. add utility window request types
  2. add EditorUtilityWindowCoordinator
  3. add utility window registry / descriptor model
  4. define utility window lifecycle:
    • open
    • reuse
    • focus
    • close
    • app-shutdown behavior
  5. define utility window payload passing

Success criteria:

  1. utility windows can be created without touching UIEditorWindowWorkspaceController
  2. utility windows can be reused without entering workspace state
  3. utility windows do not generate dock transfer requests

Phase D. Migrate Color Picker Out of the Panel System

Goal:

  1. make the color picker the first true utility window

Required changes:

  1. remove color picker from UIEditorPanelRegistry
  2. remove color picker from shell hosted-panel composition
  3. replace requestOpenDetachedPanel with utility-window open intent
  4. implement EditorColorPickerUtilityWindowContent
  5. wire inspector -> color picker communication through explicit tool contracts instead of workspace panel visibility

Success criteria:

  1. opening the color picker never calls OpenPanelInNewWindow(...)
  2. color picker never appears in workspace visible panels
  3. color picker never participates in detach / re-dock / cross-window panel transfer
  4. color picker still updates inspector-bound color correctly

Phase E. Delete Transitional Workspace Utility Leakage

Goal:

  1. remove all leftover semantic leakage after color picker migration

Required deletions:

  1. remove toolWindow from UIEditorPanelDescriptor
  2. remove color-picker-specific detached-panel request plumbing
  3. remove utility inference from UIEditorDetachedWindowPolicy
  4. remove any remaining workspace special cases that exist only for the old color picker path

Success criteria:

  1. workspace code no longer knows the color picker exists
  2. utility code no longer depends on panel registry membership
  3. no root-content-based inference is needed to determine window kind

9. Files Expected to Change

This refactor is architecture-level and will necessarily touch a broad slice.

Shell / Host Layer

  1. new_editor/app/Platform/Win32/EditorWindow.h
  2. new_editor/app/Platform/Win32/EditorWindow.cpp
  3. new_editor/app/Platform/Win32/EditorWindowRuntimeController.h
  4. new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp
  5. new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h
  6. new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp
  7. new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp
  8. new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp

Workspace Layer

  1. new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.h
  2. new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp
  3. new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp
  4. new_editor/app/Platform/Win32/EditorWindowTransferRequests.h
  5. new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp
  6. new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp

Utility Layer

  1. new utility window coordinator files
  2. new utility window descriptor / request files
  3. new color picker utility content files
  4. possibly a host-level coordinator that routes shell instances to workspace or utility content

Color Picker / Feature Layer

  1. new_editor/app/State/EditorColorPickerToolState.h
  2. new_editor/app/State/EditorColorPickerToolState.cpp
  3. new_editor/app/Features/ColorPicker/ColorPickerPanel.h
  4. new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp
  5. new_editor/app/Composition/EditorShellAssetBuilder.cpp
  6. new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp
  7. new_editor/app/Composition/EditorShellRuntime.h
  8. new_editor/app/Composition/EditorShellRuntime.cpp
  9. new_editor/app/Composition/EditorShellDrawComposer.cpp

10. Verification Matrix

This refactor is not done unless all of the following are checked.

Workspace Regression Checks

  1. main window render / input / title behavior is intact
  2. panel detach still creates detached workspace windows
  3. detached workspace windows can re-dock
  4. cross-window tab drag-drop still works
  5. workspace close path still closes detached workspace windows correctly
  6. workspace layout persistence still excludes utility windows

Utility Window Checks

  1. opening color picker creates or focuses a utility window, not a detached panel window
  2. color picker cannot be docked into the main window
  3. color picker cannot be merged by tab drag
  4. color picker does not appear in workspace visible-panel lists
  5. color picker close does not mutate workspace layout
  6. color picker reuse policy is correct:
    • single-instance reuse or explicit multi-instance rule, but not accidental workspace reuse

Cross-Boundary Checks

  1. inspector -> color picker color synchronization still works
  2. closing the main app tears down utility windows correctly
  3. focus changes between workspace and utility windows do not break input capture
  4. utility windows do not accidentally receive workspace drop preview / transfer state

Build Checks

  1. XCUIEditorApp Debug build passes
  2. XCUIEditorApp Release build passes if used by the team

11. Completion Criteria

This refactor is complete only when all of these are true:

  1. no utility window is represented as a workspace panel
  2. EditorWindow shell no longer requires UIEditorWorkspaceController
  3. workspace and utility windows share only native shell infrastructure
  4. toolWindow is not the long-term carrier of utility-window semantics
  5. the color picker opens through utility window coordination, not workspace mutation
  6. workspace code can be reasoned about without referencing utility windows
  7. utility windows can be reasoned about without referencing dock / transfer semantics

12. Final Statement

The correct fix is not:

  1. a new bypass for the color picker
  2. another flag on panel descriptors
  3. another branch inside workspace mutation

The correct fix is to restore the missing architectural boundary:

  1. native shell is shared
  2. workspace semantics are isolated
  3. utility semantics are isolated

Only after this split is in place will the color picker stop being a fake detached panel and become a real standalone editor window type.