# Editor Core Refactor Plan ## Goal This refactor turns the current `editor/app` directory map from a documented convention into a build-checked architecture. The desired production shape is: ```text XCUIEditor reusable editor UI framework: widgets, docking, shell, workspace, menu, viewport slots, field controls XCEditorCore editor product core: composition, commands, state, project and scene services, feature panels, window/workspace core, and host-facing contracts XCEditor thin executable host: main, Win32 process/window host, D3D12 runtime host, resources, startup, final object wiring ``` `XCEditor` may keep the output name `XCEngine.exe`; the target name should remain `XCEditor`. ## Current Problem The reusable layer is mostly healthy: - `editor/include/XCEditor/**` and `editor/src/**` form `XCUIEditor`. - `XCUIEditor` is testable and mostly free of app, Win32, and D3D12 concerns. The app layer has a good directory vocabulary but enforcement is still incomplete: - `XCEditorCore` now limits its public app include surface to `editor/app/Core`, `editor/app/Commands`, `editor/app/State`, and `editor/app/Host/Interfaces`, and app/core sources now include through explicit module roots instead of a private `editor/app` compatibility root. Concrete host code has converged under `editor/app/Host/Win32` and `editor/app/Host/D3D12`. - Native editor resources now cross the app-core boundary through a neutral `EditorHostResourceService` contract under `editor/app/Host/Interfaces`. `XCEditorCore` no longer consumes `app/Bootstrap` or Win32 resource helper code to load built-in PNGs, title-bar branding, or executable-relative capture output paths. - Feature panels no longer use `Composition/EditorContext.h` directly. The app-core and app feature/viewport test targets now exercise `XCEditorCore` outside the executable host. - Windowing now consumes composition through neutral contracts under `editor/app/Core/Windowing`: `EditorFrameServices` for per-frame composition/state access and `EditorWorkspaceShellRuntime` for per-window shell runtime behavior. `EditorContext` and `EditorShellRuntime` remain the concrete composition implementations, injected from `Application`. Completed boundary cuts: - Project service ownership has moved to `editor/app/Services/Project`. `EditorProjectRuntime` depends on the service-owned `ProjectBrowserModel`, and `Features/Project/ProjectPanel` owns the widget-tree projection. - Panel IDs live under `editor/app/Core/Panels`, and workspace panel runtime contracts live under `editor/app/Core/WorkspacePanels`. - Shared window category, lifecycle, chrome-policy, and native-host-policy contracts live under `editor/app/Core/Windowing`. - Utility-window kinds, descriptors, and panel contracts live under `editor/app/Core/UtilityWindows`; concrete Color Picker/Add Component panel creation lives under `editor/app/Features/EditorUtilityWindowRegistry.*` and is injected from `Application`. - Panel-facing app services live under `editor/app/Core/Panels/EditorPanelServices.h`. Workspace-panel and utility-window runtime contracts accept this service view instead of `EditorContext`, so concrete feature panels no longer depend on `Composition/EditorContext.h`. - Win32 now hands render startup a neutral `Rendering::Host::EditorWindowRenderRuntimeSurface` value, and D3D12 no longer includes `Platform/Win32/**` editor surface headers to obtain `HWND`. - Neutral host-facing contracts now live under `editor/app/Host/Interfaces`, including editor window host interfaces, render-runtime contracts, texture/viewport host contracts, pointer-capture contracts, and system interaction service interfaces. - Concrete Win32 and D3D12 host implementations now live under `editor/app/Host/Win32` and `editor/app/Host/D3D12`. - Shared window screen geometry, chrome metrics, and frame transfer requests now live under `editor/app/Core/Windowing`, and `XCEditorCore` no longer exports the whole `editor/app` root as a public include directory. - `app/Windowing/**` no longer includes concrete `Composition/EditorContext.h` or `Composition/EditorShellRuntime.h`. Instead it consumes `EditorFrameServices` and `EditorWorkspaceShellRuntime` from `editor/app/Core/Windowing`. - The private `editor/app` compatibility include root is gone from `XCEditorCore` and `XCEditor`. App implementation files now include through explicit module roots such as `app/Composition`, `app/Features`, `app/Windowing`, `app/Rendering`, `app/Scene`, `app/Services`, `app/Support`, `app/Host/Interfaces`, `app/Host/Win32`, and `app/Host/D3D12`; only executable-host code consumes `app/Bootstrap`. - `editor_app_core_tests` now links `XCEditorCore` directly and uses explicit app module include roots. Its initial suite covers host command routing, project runtime, shell asset validation, project browser model, hierarchy scene binding, and inspector presentation. - `editor_app_feature_tests` now links `XCEditorCore` directly and uses explicit app module include roots. It restores project panel, scene viewport, viewport render-plan, viewport object-id picker, and app input-routing tests without widening the include surface. - `XCEditorCore` is now controlled by `XCENGINE_BUILD_XCUI_EDITOR_CORE` instead of the executable-host switch. `XCENGINE_BUILD_XCUI_EDITOR_APP=OFF` can still build the product core and the app-facing test targets when renderer editor support is enabled. - The old Win32 tab-drag-drop target test now covers the current reusable `XCEditor/Docking/UIEditorDockHostTransfer.h` API through `editor_windowing_phase1_tests`. The root issue is not the existence of a single executable target by itself. The root issue was that shared app contracts were stored inside concrete layer directories, and CMake exposed the whole `editor/app` include root through `XCEditorCore` usage requirements. The public include surface has now been narrowed, and internal source compatibility with the private app root has now been removed. App-core and app feature/viewport tests now exercise the narrowed surface without using the whole `editor/app` include root. ## Target Directory Shape The refactor should converge on this shape: ```text editor/app/ Core/ Commands/ Panels/ State/ UtilityWindows/ Windowing/ WorkspacePanels/ Services/ Project/ Scene/ Features/ Console/ ColorPicker/ Hierarchy/ Inspector/ Project/ Scene/ Windowing/ Content/ Coordinator/ Frame/ Host/ Runtime/ Host/ Interfaces/ Win32/ D3D12/ Bootstrap/ Support/ ``` This is a convergence target, not a requirement to move every file at once. The first cut should only move contracts that are already acting as global app interfaces. ## Dependency Rules The final direction should be: ```text XCEditor -> XCEditorCore -> XCUIEditor -> XCEngine ``` Within `XCEditorCore`: - `Composition` may depend on `Core`, `Services`, `Windowing` contracts, and `XCUIEditor`. - `Composition` must not include concrete feature panel headers. - `Features` may depend on `Core`, `Services`, and `XCUIEditor`. - `Services` must not depend on `Features`. - `State` must not depend on `Composition`. - `Windowing` may depend on `Core`, `Composition` interfaces, and `XCUIEditor`, but must not include Win32 or D3D12 concrete host headers. - The current composition interfaces for windowing are `Core/Windowing/EditorFrameServices.h` and `Core/Windowing/EditorWorkspaceShellRuntime.h`. - Host contracts may live in `Core` or `Host/Interfaces`. - Win32 and D3D12 concrete implementations live outside `XCEditorCore` unless they are purely abstract host contracts. ## Phase 1: Fix Shared Contract Placement Move the most obviously misplaced shared contracts first. ### 1. Panel IDs Move: ```text editor/app/Composition/EditorPanelIds.h ``` to: ```text editor/app/Core/Panels/EditorPanelIds.h ``` Then update all includes to use `Core/Panels/EditorPanelIds.h`. Reason: panel IDs are global app model constants, not composition-private implementation details. ### 2. Workspace Panel Runtime Contract Split the existing workspace panel registry header: Current: ```text editor/app/Features/EditorWorkspacePanelRegistry.h ``` Target: ```text editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h editor/app/Features/EditorWorkspacePanelRegistry.h ``` The new core header owns: - `EditorWorkspacePanelCursorKind` - `EditorWorkspacePanelUpdatePhase` - `EditorWorkspacePanelFrameEvent` - `EditorWorkspacePanelInitializationContext` - `EditorWorkspacePanelShutdownContext` - `EditorWorkspacePanelUpdateContext` - `EditorWorkspacePanel` - `EditorWorkspacePanelRuntimeSet` The feature registry header owns only: - `CreateEditorWorkspacePanelRuntimeSet()` Reason: composition should depend on the panel runtime interface, not on the feature registry. Concrete feature construction belongs to `Features`. ### 3. Keep the First Cut Buildable This phase should not change behavior. Expected behavioral diff: none. Expected dependency improvement: ```text Composition -> Core/WorkspacePanels Features -> Core/WorkspacePanels Features -> Core/Panels ``` This does not fully solve all cycles, but it removes the worst misplaced public contract and creates the landing zone for the next cut. Completed follow-up: - `EditorPanelServices` now carries the panel-facing references to session, project runtime, scene runtime, command focus, color-picker state, system interaction, text measurement, and utility-window requests. - `EditorWorkspacePanelRuntimeSet` updates a single phase at a time; Composition owns the command-focus sync point between main panels and after-focus panels. - Concrete workspace and utility panels use `EditorPanelServices` and no longer include `Composition/EditorContext.h`. - `app/Windowing/**` no longer accepts or calls `EditorWorkspacePanelRuntimeSetFactory`. `Application` composes `CreateEditorWorkspacePanelRuntimeSet()` with `CreateEditorWorkspaceShellRuntime(...)`, then injects a zero-argument `EditorWorkspaceShellRuntimeFactory` into windowing. ## Phase 2: Introduce XCEditorCore Status: completed and build-graph enforced. Create: ```cmake add_library(XCEditorCore STATIC ...) ``` Initial contents: - `app/Core/**` - `app/Commands/**` - `app/Composition/**` - `app/Features/**` - `app/Project/**` or `app/Services/Project/**` - `app/Scene/**` or `app/Services/Scene/**` - `app/State/**` - `app/Core/UtilityWindows/**` - `app/Core/Windowing/**` - `app/Windowing/**` core files that do not require Win32 or D3D12 - host-facing abstract interfaces Keep in `XCEditor` executable: - `app/main.cpp` - `app/Bootstrap/Application.*` - `app/Bootstrap/EditorApp.rc` - `app/Host/Win32/**` - `app/Host/D3D12/**` - any concrete host glue that includes `windows.h` `XCEditor` links `XCEditorCore`, `XCUIEditor`, and concrete platform/rendering libraries. Important: do not hide Win32/D3D12 in `XCEditorCore` just to make the first CMake edit easier. If a source file needs `windows.h`, it belongs in the host side until a neutral interface exists. Completed target-graph cut: - `XCENGINE_BUILD_XCUI_EDITOR_CORE` controls `XCEditorCore` and defaults to `ON`. - `XCENGINE_BUILD_XCUI_EDITOR_APP` controls only the concrete executable host target `XCEditor`. - `XCEditor` requires `XCENGINE_BUILD_XCUI_EDITOR_CORE=ON`. - When either the core library or executable host is enabled, the root build defaults `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON` because editor core owns viewport services that link renderer editor support. - The host-off validation shape is: `-DXCENGINE_BUILD_XCUI_EDITOR_APP=OFF -DXCENGINE_BUILD_XCUI_EDITOR_CORE=ON -DXCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON`. ## Phase 3: Restore App-Core And App Feature Tests Status: completed for the initial app-core and app feature/viewport suites. Created: ```cmake editor_app_core_tests editor_app_feature_tests ``` This target links: ```text XCEditorCore GTest::gtest_main ``` The initial suite contains tests that should not need Win32/D3D12: - `test_editor_host_command_bridge.cpp` - `test_editor_project_runtime.cpp` - `test_editor_shell_asset_validation.cpp` - `test_project_browser_model.cpp` - `test_hierarchy_scene_binding.cpp` - `test_inspector_presentation.cpp` The restored app feature/viewport suite contains: - `test_editor_window_input_routing.cpp` - `test_project_panel.cpp` - `test_scene_viewport_render_plan.cpp` - `test_scene_viewport_runtime.cpp` - `test_viewport_object_id_picker.cpp` Completed stale-reference cleanup: - `Ports/SystemInteractionPort.h` test use was replaced with `SystemInteractionService.h`. - `Rendering/Viewport/ViewportRenderTargetInternal.h` test use was replaced with current `ViewportRenderTargets` and `ViewportRenderTargetUtils` headers. - `app/Platform/Win32/WindowManager/TabDragDropTarget.h` was not restored. The test now covers `ResolveUIEditorDockHostTabDropTarget` from the reusable `XCEditor/Docking/UIEditorDockHostTransfer.h` API through `editor_windowing_phase1_tests`. These test targets are part of the architecture. If app core and app feature/viewport behavior cannot be tested without starting the executable host, the boundary is not real. ## Phase 4: Service and Feature Boundary Cleanup After `XCEditorCore` exists, remove remaining service-to-feature dependencies. ### Project Completed: ```text Services/Project/EditorProjectRuntime.h -> Services/Project/ProjectBrowserModel.h Features/Project/ProjectPanel.h -> Services/Project/EditorProjectRuntime.h ``` Current shape: ```text Services/Project/ProjectBrowserModel.h Services/Project/EditorProjectRuntime.h Features/Project/ProjectPanel.h ``` `ProjectBrowserModel` is now service-owned domain/project state. It exposes folder, asset, and breadcrumb data, while `ProjectPanel` converts folders into `UIEditorTreeViewItem` presentation data. Keep future project filesystem and command-target behavior in `Services/Project`, not under `Features/Project`. ### Scene Keep scene runtime as a service layer. Scene viewport feature code may use scene services, but scene services should not depend on concrete feature UI. ### Utility Windows Completed: ```text UtilityWindows/EditorUtilityWindowKind.h UtilityWindows/EditorUtilityWindowPanel.h UtilityWindows/EditorUtilityWindowRegistry.* -> Core/UtilityWindows/EditorUtilityWindowRuntime.h -> Core/UtilityWindows/EditorUtilityWindowRegistry.* -> Features/EditorUtilityWindowRegistry.* ``` Current shape: ```text Core/UtilityWindows/EditorUtilityWindowRuntime.h Core/UtilityWindows/EditorUtilityWindowRegistry.* Features/EditorUtilityWindowRegistry.* ``` Keep utility kinds, descriptors, host context, panel contract, and factory type in `Core/UtilityWindows`. Keep concrete utility panel construction in `Features/EditorUtilityWindowRegistry.*` and inject it from the application composition root. Windowing may resolve descriptors from Core, but must not include Color Picker, Add Component, or other concrete feature-panel headers. ## Phase 5: Host Boundary Cleanup Split neutral host contracts from concrete implementations. Target: ```text app/Host/Interfaces/ EditorWindowHostInterfaces.h EditorWindowRenderRuntime.h UiTextureHost.h ViewportRenderHost.h SystemInteractionService.h app/Host/Win32/ Win32SystemInteractionHost.* EditorWindow.* message dispatch, DPI, chrome, pointer capture app/Host/D3D12/ D3D12EditorWindowRenderRuntime.* D3D12 UI renderer, texture host, text system, swap chain presenter ``` Then replace concrete Win32/D3D12 cross-includes with a neutral surface or factory contract. Completed cuts: - The old `Win32EditorWindowRenderRuntimeSurface` concrete adapter was removed. - `EditorWindowRenderRuntimeSurface` is now a value contract in `Host/Interfaces/EditorWindowRenderRuntime.h`. - Win32 fills that neutral contract during native surface capture; D3D12 reads the native handle from the contract instead of including a Win32 editor windowing header. - Neutral host-facing contracts now live under `app/Host/Interfaces/`. - `EditorWindowTransferRequests`, window screen geometry, and title-bar chrome metrics now live under `app/Core/Windowing/`. - `XCEditorCore` now exposes only `app/Core`, `app/Commands`, `app/State`, and `app/Host/Interfaces` through its public usage requirements, and both `XCEditorCore` and `XCEditor` enumerate explicit module roots instead of using `editor/app` as a compatibility include directory. - Concrete Win32 and D3D12 host implementations now live under `app/Host/Win32` and `app/Host/D3D12`; `XCEditor` consumes those roots privately, while `XCEditorCore` only sees `app/Host/Interfaces`. - Native PNG resource lookup and executable-directory discovery now go through `Host/Interfaces/EditorHostResourceService.h`. The concrete `Host/Win32/Resources/Win32EditorResourceService.*` owns `EditorResources.h`, Win32 resource APIs, and the mapping from product resource IDs to built-in editor icon requests, so `XCEditorCore` no longer needs `app/Bootstrap` as a private include root for resource loading. - Windowing now depends on composition through `Core/Windowing/EditorFrameServices.h` and `Core/Windowing/EditorWorkspaceShellRuntime.h`. `Application` injects the concrete `EditorShellRuntime` factory, and `EditorContext` remains the concrete `EditorFrameServices` implementation. - The shell-runtime construction hook visible to `app/Windowing/**` is now a zero-argument `EditorWorkspaceShellRuntimeFactory`. Workspace panel runtime set construction stays in `Application`, so windowing no longer sees `EditorWorkspacePanelRuntimeSetFactory`. ## Phase 6: Documentation Update Update `editor/AGENTS.md` after each completed boundary cut. Required changes: - production target shape becomes `XCUIEditor`, `XCEditorCore`, `XCEditor` - `Core` owns shared app contracts - `Features/EditorWorkspacePanelRegistry.*` owns concrete feature factories - app tests link `XCEditorCore` - no new app code should add direct `Composition <-> Features` cycles ## Validation Run these after each phase where possible: ```powershell cmake --build build --config Debug --target XCUIEditor cmake --build build --config Debug --target XCEditorCore cmake --build build --config Debug --target XCEditor cmake --build build --config Debug --target editor_ui_tests cmake --build build --config Debug --target editor_app_core_tests cmake --build build --config Debug --target editor_app_feature_tests cmake --build build --config Debug --target editor_windowing_phase1_tests ``` To prove the product core boundary without the executable host, configure a separate build directory with: ```powershell cmake -S . -B build/editor_core_hostoff -DXCENGINE_BUILD_XCUI_EDITOR_APP=OFF -DXCENGINE_BUILD_XCUI_EDITOR_CORE=ON -DXCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT=ON cmake --build build/editor_core_hostoff --config Debug --target XCEditorCore cmake --build build/editor_core_hostoff --config Debug --target editor_app_core_tests cmake --build build/editor_core_hostoff --config Debug --target editor_app_feature_tests ``` When app smoke is available: ```powershell ctest -C Debug -R xceditor_smoke --output-on-failure ``` If `XCEditorCore` does not exist yet, skip that target until Phase 2 lands. ## Done Criteria The refactor is complete when: - `XCEditorCore.lib` exists and is linked by `XCEditor`. - `XCEditor` executable source is limited to host startup and concrete platform/render backend wiring. - `Composition` no longer includes concrete feature panel headers. - `Windowing` no longer includes concrete composition runtime/state headers; it depends on `Core/Windowing` composition interfaces instead. - `Services` no longer include `Features/**`. - Win32 and D3D12 communicate through neutral host/render contracts. - app-core and app feature/viewport tests are wired into CMake and build without running the executable. - `editor/AGENTS.md` describes the new target shape and directory rules.