# 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` 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. - Scene viewport shader paths now flow through runtime initialization. The viewport runtime service builds the paths from the runtime repo root and injects them into the render service/pass bundle instead of letting render passes infer source-tree paths from a compile-time repo-root macro. - Editor icon and viewport runtime contracts now live under `editor/app/Core/Assets` and `editor/app/Core/Viewport`. Concrete icon loading, viewport host services, scene viewport rendering, render targets, and object-id picking stay under `editor/app/Rendering`. - 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`. - The generic workspace panel runtime-set implementation also lives under `editor/app/Core/WorkspacePanels`; `Features/EditorWorkspacePanelRegistry.*` owns only concrete panel adapters and the concrete factory. - 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`. - Command routing and shared state contracts now live under `editor/app/Core/Commands` and `editor/app/Core/State`. `XCEditorCore`, `XCEditor`, and app-facing tests expose those contracts through the single `editor/app/Core` include surface instead of separate command/state roots. - 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/Scene`, `app/Services`, `app/Support`, `app/Host/Interfaces`, `app/Host/Win32`, and `app/Host/D3D12`; only executable-host code consumes `app/Bootstrap`. - Concrete `app/Rendering/**` files now build through the `XCEditorCoreRendering` object library. `XCEditorCore` consumes the object files but does not expose `app/Rendering` to Composition, Features, or Core sources. - `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. - `XCEditorCore` and `XCEditor` no longer receive a compile-time `XCUIEDITOR_REPO_ROOT` define. Product behavior that needs repo/resource roots must receive runtime paths through startup wiring, shell/runtime context, or host/resource services. - 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/ Assets/ Commands/ Panels/ Scene/ State/ UtilityWindows/ Viewport/ Windowing/ WorkspacePanels/ Services/ Project/ Scene/ Features/ Console/ ColorPicker/ Hierarchy/ Inspector/ Project/ Scene/ Windowing/ Content/ Coordinator/ Frame/ Host/ Runtime/ Host/ Interfaces/ Win32/ D3D12/ Rendering/ Assets/ Viewport/ 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. Same domain names under different layers are acceptable only when the layer meaning is explicit and build-checked: `Core/Viewport` is the shared contract surface, while `Rendering/Viewport` is the concrete implementation. If a Composition or Feature file needs a viewport concept, it should include the Core contract, not the concrete Rendering path. ## 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`. - `Composition` and `Features` must not include concrete `app/Rendering/**` headers. They should use `Core/Assets/EditorIconService.h` and `Core/Viewport/**` contracts. - `app/Rendering/**` may depend on `Core`, `Host/Interfaces`, `XCUIEditor`, and engine renderer/editor support. It implements Core contracts and is wired from `Application`. - `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`. The generic `EditorWorkspacePanelRuntimeSet` implementation belongs with the Core workspace-panel runtime contract; the feature registry should only add concrete panel adapters to the set. ### 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: - `EditorWorkspacePanelRuntimeSet` generic behavior now lives under `Core/WorkspacePanels`, leaving `Features/EditorWorkspacePanelRegistry.*` responsible only for concrete workspace-panel adapters and factory wiring. - `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/Composition/**` - `app/Features/**` - `app/Project/**` or `app/Services/Project/**` - `app/Scene/**` or `app/Services/Scene/**` - `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 or rendering implementation paths. Completed: ```text Rendering/Viewport/SceneViewportRenderRequest.h -> Core/Scene/SceneViewportRenderRequest.h ``` Current shape: ```text Core/Scene/SceneViewportRenderRequest.h Scene/EditorSceneRuntime.h Rendering/Viewport/SceneViewportRenderService.h ``` `SceneViewportRenderRequest` is a shared scene-to-viewport request contract, not a rendering implementation detail. Keep future scene-to-viewport request values under `Core/Scene` so scene services can build requests without taking an `app/Rendering/**` include dependency. - Scene viewport shader paths are runtime configuration now. Build them from the runtime repo root in the viewport runtime service, inject them into `SceneViewportRenderService`, and keep grid/selection render passes from inferring source-tree paths with compile-time macros or `__FILE__`. ### 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` 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`. - `SceneViewportRenderRequest` now lives under `editor/app/Core/Scene` instead of `editor/app/Rendering/Viewport`. `EditorSceneRuntime` consumes the shared scene viewport request contract without depending on `app/Rendering/**`, and `editor_app_core_tests` no longer needs the app rendering include root to validate scene runtime behavior. - Editor icon contracts now live under `editor/app/Core/Assets/EditorIconService.h`. `BuiltInIcons` remains the concrete `editor/app/Rendering` implementation, and panels/tool overlays resolve icons through the contract instead of loading resource files or naming concrete icon storage. - Viewport runtime contracts now live under `editor/app/Core/Viewport/**`. `SceneViewportFeature` consumes `EditorSceneViewportRuntime`, while `ViewportHostService` and `SceneViewportRenderService` stay concrete rendering implementations behind `CreateEditorViewportRuntimeServices()`. - `Application` injects `CreateEditorIconService()` and `CreateEditorViewportRuntimeServices()` into `EditorShellRuntime`, making the executable startup path the only place that names those concrete rendering factories. - `app/Rendering/**` now builds as `XCEditorCoreRendering`, a dedicated object library consumed by `XCEditorCore`. This keeps rendering behavior in the product core while preventing Composition/Features/Core from regaining an `app/Rendering` include root. - Scene viewport shader/resource paths are now injected from the viewport runtime service into `SceneViewportRenderService` and `SceneViewportRenderPassBundle`. The concrete grid and selection-outline passes no longer depend on `XCUIEDITOR_REPO_ROOT` or `__FILE__` source-tree fallback logic to find editor shaders. - `Application` now resolves the editor repo root at runtime by walking upward from the executable directory and checking for editor/project markers, rather than consuming a compile-time repo-root macro. ## 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. - `Composition` and `Features` no longer include concrete `app/Rendering/**` headers; icon and viewport use flows through Core contracts. - `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. - Editor product behavior no longer depends on a compile-time repo-root macro; runtime resource roots are injected through startup/runtime context. - 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.