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:
EditorWindowno longer constructs itself fromUIEditorWorkspaceControllerEditorWindowRuntimeControllerno longer directly ownsm_workspaceControllerEditorWindowHostRuntime::CreateEditorWindow(...)no longer accepts raw workspace controller input- a new content abstraction now exists:
EditorWindowContentControllerEditorWorkspaceWindowContentController
- current workspace-backed behavior has been moved behind the new content layer without changing workspace semantics
- detached workspace chrome policy now reads window-content capabilities instead of shell-owned workspace state
XCUIEditorAppDebug build passes after this phase
What is intentionally not done yet:
- utility window domain does not exist yet
- color picker still opens through detached panel workflow
toolWindowstill 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:
- a first-class utility window domain now exists:
EditorUtilityWindowKindEditorWindowOpenUtilityWindowRequestEditorUtilityWindowCoordinatorEditorUtilityWindowRegistry
- color picker open intent no longer routes through detached workspace mutation
- the color picker now opens through utility-window coordination and utility content
- color picker has been removed from:
UIEditorPanelRegistry- shell hosted-panel composition
- workspace detached-panel request plumbing
openDetachedPanelhas been deleted from frame transfer routingUIEditorWindowWorkspaceController::OpenPanelInNewWindow(...)has been deletedtoolWindowand related workspace utility inference have been deleted- detached floating-window placement logic is now shared instead of duplicated
XCUIEditorAppDebug build passes after this phase
Verified and Archived on 2026-04-22
Section 10 manual verification has now passed.
Verified outcomes:
- workspace regression checks passed
- utility window checks passed
- cross-boundary checks passed
XCUIEditorAppDebug build passesXCUIEditorAppRelease 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:
- native window shell
- workspace-backed dockable windows
- utility windows that are inherently standalone
The immediate trigger is the color picker window, but the real issue is broader:
EditorWindowis still specialized aroundUIEditorWorkspaceController- utility windows are currently modeled as panels with a
toolWindowflag - the color picker is opened through
openDetachedPanel - 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:
- HWND lifetime
- D3D render loop
- DPI handling
- input capture
- title bar / chrome
- screenshot and frame pacing
Sharing the workspace panel abstraction is not correct for the color picker.
The color picker is currently treated as:
- a panel descriptor in
UIEditorPanelRegistry - a hosted panel inside shell composition
- a detached panel opened via
OpenPanelInNewWindow(...) - 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:
- workspace window
- owns a
UIEditorWorkspaceController - participates in layout, docking, tab stacks, cross-window transfer
- can detach and re-merge
- owns a
- utility window
- owns standalone tool content
- does not belong to workspace layout
- does not expose
nodeId/panelIddocking 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:
new_editor/app/Platform/Win32/EditorWindow.hEditorWindowconstructor directly requiresUIEditorWorkspaceController
new_editor/app/Platform/Win32/EditorWindowRuntimeController.h- runtime directly stores
m_workspaceController
- runtime directly stores
new_editor/app/Platform/Win32/EditorWindowTransferRequests.h- utility-open intent is modeled as
EditorWindowOpenDetachedPanelRequest
- utility-open intent is modeled as
new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp- color picker open is converted into
openDetachedPanel
- color picker open is converted into
new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp- color picker window creation is routed through workspace mutation
new_editor/src/Workspace/UIEditorWindowWorkspaceController.cppOpenPanelInNewWindow(...)is used to create the color picker window
new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.htoolWindowis attached to panel descriptors
new_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp- utility semantics are inferred from detached workspace root content
new_editor/app/Composition/EditorShellAssetBuilder.cpp- color picker is registered as a panel
new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp- color picker lives inside hosted panel update / dispatch infrastructure
new_editor/app/State/EditorColorPickerToolState.h- color picker open intent is expressed as
requestOpenDetachedPanel
- color picker open intent is expressed as
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:
- keeping the color picker in
UIEditorPanelRegistryand merely adding moreif (panelId == "color-picker")branches - keeping
toolWindowas the long-term mechanism for non-workspace windows - keeping
openDetachedPanelas the generic request type for utility windows - allowing utility windows to remain inside
UIEditorWindowWorkspaceSet - preserving
nodeId/panelIdtransfer semantics for utility windows - building a second special-case bypass inside
EditorWindowWorkspaceCoordinator - 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:
- HWND creation and destruction
- D3D renderer / swap chain / present loop
- window chrome and title
- DPI, sizing, screenshot, pointer capture plumbing
- input event collection and dispatch to content
This layer must not know whether the content is workspace-backed or utility-backed.
Suggested shape:
EditorNativeWindowShellEditorNativeWindowRuntimeIEditorWindowContentController
The names can change, but the boundary cannot.
6.2 Workspace Window Layer
Workspace windows are the only windows allowed to own:
UIEditorWorkspaceControllerEditorShellRuntime- dock host interaction state
- detached panel transfer requests
- cross-window tab drag and dock / re-dock semantics
A workspace window can:
- represent the main window
- represent a detached panel window
- merge back into another workspace window
Suggested shape:
EditorWorkspaceWindowContentEditorWorkspaceWindowCoordinatorEditorWorkspaceWindowTransferRequests
6.3 Utility Window Layer
Utility windows are standalone tool surfaces.
They must not own:
UIEditorWorkspaceController- dock layout
- tab stack state
nodeIdpanelIdworkspace transfer semantics
They may own:
- dedicated tool state
- dedicated rendering and input logic
- explicit request / response channels to editor features
- separate focus / reuse / close rules
Suggested shape:
EditorUtilityWindowContentEditorUtilityWindowCoordinatorEditorOpenUtilityWindowRequestEditorUtilityWindowKindEditorColorPickerUtilityWindowContent
7. Required Ownership Changes
The refactor must move ownership, not just code location.
7.1 Window Identity
Today:
- detached color picker window identity is derived from
panelId - detached utility behavior is inferred from the root workspace panel descriptor
After refactor:
- workspace windows are identified inside workspace domain
- utility windows are identified inside utility domain
- utility window identity is no longer a panel identity
7.2 Open Intent
Today:
- color picker open intent means "open this panel in a detached window"
After refactor:
- color picker open intent means "open or focus utility window of kind color picker"
- the request carries explicit tool payload:
- initial color
- alpha mode
- inspector target binding
- focus / reuse policy
7.3 Persistence
Today:
- utility-like windows still pass through workspace layout concepts
After refactor:
- workspace window state remains in workspace layout persistence
- utility window state uses separate persistence or no persistence
- utility windows never enter
UIEditorWindowWorkspaceSet
7.4 Visual Policy
Today:
- tool-window policy is inferred from detached workspace content
After refactor:
- workspace detached-window policy is only about workspace windows
- 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:
- stop making the native window host depend directly on
UIEditorWorkspaceController
Required changes:
- split the current
EditorWindowresponsibilities into:- shell responsibilities
- content responsibilities
- extract a content interface for:
- update
- draw
- input dispatch
- external preview / title contributions if needed
- make
EditorWindowHostRuntime::CreateEditorWindow(...)create a shell plus content, not a workspace-bound window object
Success criteria:
- a shell instance can host workspace content
- a shell instance can host non-workspace content
- no shell constructor directly requires
UIEditorWorkspaceController
Red line:
- do not keep a hidden
m_workspaceControllerin shell-level runtime
Phase B. Move Existing Workspace Windows onto the New Content Layer
Goal:
- preserve all existing main-window and detached-panel behavior while isolating it into workspace-specific content
Required changes:
- create
EditorWorkspaceWindowContent - move current workspace-only responsibilities into it:
UIEditorWorkspaceControllerEditorShellRuntime- workspace frame orchestration
- dock transfer request generation
- keep
EditorWindowWorkspaceCoordinatorbut narrow its responsibility to workspace windows only
Success criteria:
- main window still works
- detached panel windows still work
- cross-window tab drag and re-dock still work
- utility windows are still not introduced yet, but the shell is already ready for them
Phase C. Introduce a Real Utility Window Domain
Goal:
- create a first-class utility window pipeline independent from workspace mutation
Required changes:
- add utility window request types
- add
EditorUtilityWindowCoordinator - add utility window registry / descriptor model
- define utility window lifecycle:
- open
- reuse
- focus
- close
- app-shutdown behavior
- define utility window payload passing
Success criteria:
- utility windows can be created without touching
UIEditorWindowWorkspaceController - utility windows can be reused without entering workspace state
- utility windows do not generate dock transfer requests
Phase D. Migrate Color Picker Out of the Panel System
Goal:
- make the color picker the first true utility window
Required changes:
- remove color picker from
UIEditorPanelRegistry - remove color picker from shell hosted-panel composition
- replace
requestOpenDetachedPanelwith utility-window open intent - implement
EditorColorPickerUtilityWindowContent - wire inspector -> color picker communication through explicit tool contracts instead of workspace panel visibility
Success criteria:
- opening the color picker never calls
OpenPanelInNewWindow(...) - color picker never appears in workspace visible panels
- color picker never participates in detach / re-dock / cross-window panel transfer
- color picker still updates inspector-bound color correctly
Phase E. Delete Transitional Workspace Utility Leakage
Goal:
- remove all leftover semantic leakage after color picker migration
Required deletions:
- remove
toolWindowfromUIEditorPanelDescriptor - remove color-picker-specific detached-panel request plumbing
- remove utility inference from
UIEditorDetachedWindowPolicy - remove any remaining workspace special cases that exist only for the old color picker path
Success criteria:
- workspace code no longer knows the color picker exists
- utility code no longer depends on panel registry membership
- 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
new_editor/app/Platform/Win32/EditorWindow.hnew_editor/app/Platform/Win32/EditorWindow.cppnew_editor/app/Platform/Win32/EditorWindowRuntimeController.hnew_editor/app/Platform/Win32/EditorWindowRuntimeController.cppnew_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.hnew_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cppnew_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cppnew_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp
Workspace Layer
new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.hnew_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cppnew_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cppnew_editor/app/Platform/Win32/EditorWindowTransferRequests.hnew_editor/src/Workspace/UIEditorWindowWorkspaceController.cppnew_editor/src/Workspace/UIEditorDetachedWindowPolicy.cpp
Utility Layer
- new utility window coordinator files
- new utility window descriptor / request files
- new color picker utility content files
- possibly a host-level coordinator that routes shell instances to workspace or utility content
Color Picker / Feature Layer
new_editor/app/State/EditorColorPickerToolState.hnew_editor/app/State/EditorColorPickerToolState.cppnew_editor/app/Features/ColorPicker/ColorPickerPanel.hnew_editor/app/Features/ColorPicker/ColorPickerPanel.cppnew_editor/app/Composition/EditorShellAssetBuilder.cppnew_editor/app/Composition/EditorShellHostedPanelCoordinator.cppnew_editor/app/Composition/EditorShellRuntime.hnew_editor/app/Composition/EditorShellRuntime.cppnew_editor/app/Composition/EditorShellDrawComposer.cpp
10. Verification Matrix
This refactor is not done unless all of the following are checked.
Workspace Regression Checks
- main window render / input / title behavior is intact
- panel detach still creates detached workspace windows
- detached workspace windows can re-dock
- cross-window tab drag-drop still works
- workspace close path still closes detached workspace windows correctly
- workspace layout persistence still excludes utility windows
Utility Window Checks
- opening color picker creates or focuses a utility window, not a detached panel window
- color picker cannot be docked into the main window
- color picker cannot be merged by tab drag
- color picker does not appear in workspace visible-panel lists
- color picker close does not mutate workspace layout
- color picker reuse policy is correct:
- single-instance reuse or explicit multi-instance rule, but not accidental workspace reuse
Cross-Boundary Checks
- inspector -> color picker color synchronization still works
- closing the main app tears down utility windows correctly
- focus changes between workspace and utility windows do not break input capture
- utility windows do not accidentally receive workspace drop preview / transfer state
Build Checks
XCUIEditorAppDebug build passesXCUIEditorAppRelease build passes if used by the team
11. Completion Criteria
This refactor is complete only when all of these are true:
- no utility window is represented as a workspace panel
EditorWindowshell no longer requiresUIEditorWorkspaceController- workspace and utility windows share only native shell infrastructure
toolWindowis not the long-term carrier of utility-window semantics- the color picker opens through utility window coordination, not workspace mutation
- workspace code can be reasoned about without referencing utility windows
- utility windows can be reasoned about without referencing dock / transfer semantics
12. Final Statement
The correct fix is not:
- a new bypass for the color picker
- another flag on panel descriptors
- another branch inside workspace mutation
The correct fix is to restore the missing architectural boundary:
- native shell is shared
- workspace semantics are isolated
- 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.