Files
XCEngine/editor/AGENTS.md

25 KiB

XCUI Editor Agent Guide

This file documents the mandatory editor architecture for agents working under editor/. It is an architecture contract, not just a description of whatever the current checkout happens to contain.

If this file conflicts with editor/CMakeLists.txt or the real directory tree, the target-shape rules in this file win. Fix the code or CMake to match these rules unless the user explicitly asks to change the architecture contract.

Build Shape

The important production targets are:

  • XCUIEditorLib: reusable XCEditor framework code from editor/src and editor/include/XCEditor, including the reusable window authority, synchronization planner, and presentation projection under XCEditor/Windowing.
  • XCUIEditorApp: concrete editor executable. Its output name is XCEngine.

XCUIEditorLib is the only production static library allowed under editor/. All app-side code under editor/app, including Windowing, Platform, Rendering, Composition, Features, and UtilityWindows, must compile into XCUIEditorApp unless the code belongs in XCUIEditorLib.

Do not create, restore, or keep app-internal static library targets. Names such as XCUIEditorAppWindowing, XCUIEditorAppCore, XCUIEditorAppLib, and XCUIEditorHost are forbidden production target boundaries. If any of these targets exist in CMake, treat them as stale build debt and remove them as part of the boundary cleanup.

There is now a narrow public editor/include/XCEditor/Windowing layer for the generic window authority model, synchronization plan/planner, and presentation projection. App-specific window orchestration remains under editor/app/Windowing, render host contracts remain under editor/app/Rendering/Host, the concrete D3D12 renderer remains under editor/app/Rendering/D3D12, and the concrete Win32 host remains under editor/app/Platform/Win32.

Layering

Use these ownership boundaries when changing code:

  • editor/include/XCEditor and editor/src: reusable editor framework. Keep this layer independent from app state, Win32, D3D12 host code, and App::* types.
  • editor/include/XCEditor/Windowing and editor/src/Windowing: reusable window authority, validation, synchronization planning, commit, and presentation projection. Keep this layer generic and free of app content, host runtime, native platform, and rendering details.
  • editor/app/Composition, Commands, Features, Project, Scene, State, System, UtilityWindows: editor product semantics.
  • editor/app/Windowing/Content, Coordinator, Frame, Host, Runtime: app window orchestration, content ownership, frame driving, frame transfer, and per-window runtime state. Keep this layer backend-neutral; it may depend on editor/app/Rendering/Host contracts, but not concrete render backends.
  • editor/app/Platform/Win32: native window, message dispatch, input, lifecycle, chrome, HWND ownership, Win32 diagnostics, concrete native render surface adapters, and native host adapter behavior.
  • editor/app/Rendering/Host: app rendering contracts consumed by app windowing and app content, including the render-runtime factory interface.
  • editor/app/Rendering/D3D12: concrete D3D12 window renderer, UI renderer, texture host, text system, render loop, render-runtime adapter, and render-runtime factory.

The semantic dependency direction should remain:

XCEditor framework
  <- editor app semantics
  <- app windowing runtime / rendering host contracts
  <- application composition root / concrete Win32 and rendering adapters

Startup Flow

The application starts through:

app/main.cpp
  -> RunXCUIEditorApp
  -> Application::Run
  -> Application::Initialize

The primary workspace window is initialized through:

EditorContext::BuildWorkspaceController()
  -> EditorWindowSystem::BootstrapPrimaryWindow(...)
  -> EditorWindowManager::CreateWorkspaceWindow(...)
  -> EditorWindowContentFactory::CreateWorkspaceContentController(...)
  -> EditorWindowRenderRuntimeFactory::CreateWindowRenderRuntime()
  -> EditorWindowRuntimeController(EditorContext, contentController, renderRuntime)
  -> EditorWindowInstance(runtimeController)
  -> EditorWindowHostRuntime::CreateHostWindow(windowInstance, ...)
  -> Win32 EditorWindow native peer

Keep authoritative window bootstrap in EditorWindowSystem; do not move it back into the Win32 host.

Frame Runtime Ownership

EditorWindowManager owns steady-state frame iteration and the immediate-frame callbacks used by native paint, resize, maximize/restore, and chrome actions. The Win32 host may request a frame through EditorWindowHostCoordinator, but it must not own the editor frame loop or fetch EditorContext.

EditorWindowRuntimeController lives under editor/app/Windowing/Runtime. App windowing creates it from EditorContext plus a workspace or utility content controller plus a backend-neutral EditorWindowRenderRuntime, then wraps it in app-owned EditorWindowInstance. The Win32 host receives the instance only as an EditorHostWindow owner/native-peer binding; it must not store or call EditorWindowRuntimeController directly, create content controllers, or receive EditorContext.

Concrete renderer selection is composed above both app windowing and the native host. Application creates the current D3D12EditorWindowRenderRuntimeFactory and passes it into EditorWindowManager; app windowing asks the factory for backend-neutral Rendering::Host::EditorWindowRenderRuntime instances. EditorWindowInstance owns runtime controllers that already contain their render runtime; the Win32 host must not create concrete render backends or own render runtimes. EditorWindowRuntimeController may call EditorWindowRenderRuntime, UiTextureHost, and ViewportRenderHost, but it must not include Rendering/D3D12 headers, windows.h, or HWND types.

The host contract direction is:

EditorWindowManager / coordinators
  -> ask EditorWindowRenderRuntimeFactory for EditorWindowRenderRuntime
  -> create EditorWindowRuntimeController
  -> wrap it in app-owned EditorWindowInstance
  -> EditorWindowHostRuntime::CreateHostWindow(windowInstance, ...)
  -> Win32 EditorWindow owns HWND/input/chrome and supplies native snapshots
  -> EditorWindowInstance renders from snapshots and returns native frame commands

The native peer contract for frame/runtime data is data-shaped. Native render surface creation crosses through EditorNativeWindowRuntimeSurface, which carries an abstract Rendering::Host::EditorWindowRenderRuntimeSurface rather than a raw HWND/void*; per-frame native facts cross through EditorNativeWindowFrameSnapshot; frame-side native effects cross back through EditorNativeWindowFrameCommands. Do not add one-off native-peer getters for normal frame data such as HWND, render size, DPI, pending input, cursor point, workspace bounds, title-bar mode, or cursor application. Extend the snapshot/command structs when the frame contract needs new data.

Window Authority Model

EditorWindowSystem owns the authoritative UIEditorWindowWorkspaceSet through EditorWindowWorkspaceStore.

Normal per-frame workspace edits now use direct writes into that authoritative state:

EditorWorkspaceWindowContentController::UpdateAndAppend(...)
  -> EditorWindowSystem::TryBuildLiveWindowWorkspaceController(windowId, ...)
  -> UIEditorWorkspaceController::BindToState(...)
  -> workspace operations mutate the bound authoritative workspace/session

The old normal path is obsolete:

snapshot diff
  -> workspaceMutation frame request
  -> coordinator
  -> synchronization plan
  -> commit

Do not reintroduce workspaceMutation as the steady-state route for ordinary layout, tab, visibility, or active-panel edits inside an existing workspace window.

Cross-window create, update, close, and destroyed-window reconciliation still use the planner/coordinator flow:

target UIEditorWindowWorkspaceSet
  -> EditorWindowSystem::BuildPlanForWindowSet(...)
  -> EditorWindowWorkspaceCoordinator::ApplySynchronizationPlan(...)
  -> EditorWindowSystem::CommitSynchronizationPlan(...)

EditorWindowWorkspaceCoordinator::RefreshWindowPresentation(...) refreshes each workspace window projection from authoritative state. Presentation should follow authority; it should not become a second source of truth.

Frame Transfer Requests

EditorWindowFrameTransferRequests is only for host-side or cross-window side effects that cannot be represented as direct in-window workspace edits.

Current request fields are:

  • workspace.beginGlobalTabDrag
  • workspace.detachPanel
  • utility.openUtilityWindow

There is no workspace.workspaceMutation field in the current frame transfer contract. Treat any doc, comment, or test name that says otherwise as stale.

Window Categories

Workspace and utility windows share the native host path but use distinct content controllers.

  • Workspace windows use EditorWorkspaceWindowContentController.
  • Utility windows use EditorUtilityWindowContentController.
  • App windowing creates content through EditorWindowContentFactory.
  • App windowing wraps content in EditorWindowRuntimeController, then owns that runtime through EditorWindowInstance.
  • The concrete host creates a native peer for an existing app-owned EditorHostWindow; it does not receive or validate runtime controllers.

Utility windows are descriptor driven through EditorUtilityWindowDescriptor, EditorUtilityWindowRegistry, and CreateEditorUtilityWindowPanel(...). Register new utility windows there rather than hard-coding them in Win32 host logic. Utility descriptors may express native host semantics such as EditorWindowNativeShellRole::ToolWindow, but they must not include windows.h or encode Win32 style constants such as WS_EX_* or WS_*.

Modification Rules

  • First decide whether the change belongs to XCEditor framework, app semantics, app windowing, Win32 host, or rendering host.
  • Keep public framework headers free of App::*, Win32, and D3D12 host types.
  • Keep editor/include/XCEditor and editor/src free of windows.h, HWND, Win32 exception types such as _EXCEPTION_POINTERS, and DbgHelp/StackWalk code. Win32 crash stack capture belongs under editor/app/Platform/Win32; framework trace code only writes platform-neutral trace records.
  • Do not add editor/app as an include directory for XCUIEditorLib. App-only services must cross into framework code through framework-owned interfaces and app-side adapters.
  • Do not compile ${XCUI_EDITOR_SHARED_SOURCES} directly into XCUIEditorApp; the executable should consume shared framework code through XCUIEditorLib.
  • Do not add any app-internal static library targets under editor/. The only editor static library target is XCUIEditorLib.
  • Do not compile editor/app/Windowing as a standalone library target. It is an app source ownership boundary only, and should compile into XCUIEditorApp.
  • Use editor/include/XCEditor/Windowing and editor/src/Windowing for generic window authority, synchronization, validation, and presentation projection.
  • Use editor/app/Windowing for app-specific window runtime semantics, content, frame transfer, host contracts, runtime controllers, and coordinators.
  • Use editor/app/Rendering/Host for renderer-facing contracts that app windowing or app content may consume, including EditorWindowRenderRuntimeFactory.
  • Use editor/app/Rendering/D3D12 for concrete D3D12 renderer ownership, swap-chain/present/capture details, UI texture/text/render systems, and the D3D12 EditorWindowRenderRuntime implementation and factory.
  • Use editor/app/Platform/Win32 only for native host behavior and message integration. Win32 code may request frames through the host coordinator; it must not own the editor frame loop, expose EditorContext, or create concrete render backends. It may create concrete Win32 render surface adapter objects that implement Rendering::Host::EditorWindowRenderRuntimeSurface.
  • Do not let editor/app/Windowing include Platform/Win32 headers.
  • Do not let editor/app/Windowing include Rendering/D3D12 headers, windows.h, or HWND types. It should consume concrete rendering only through Rendering::Host::EditorWindowRenderRuntime and Rendering::Host::EditorWindowRenderRuntimeFactory, and it must pass native render surfaces only as abstract EditorWindowRenderRuntimeSurface objects.
  • Do not let editor/app/Platform/Win32 include Rendering/D3D12 headers or implement render-runtime factory methods on the native host interfaces.
  • Do not let app semantics such as UtilityWindows encode Win32 style bits. Use semantic host policy fields, and map those semantics to WS_* constants inside the Win32 host.
  • Do not spread D3D12 host types into app windowing content, coordinator, frame-transfer, runtime-controller, or public host-contract headers.
  • Do not let Win32 host code create workspace or utility content directly. It should receive an app-owned EditorHostWindow/EditorWindowInstance created by app windowing, not an EditorWindowRuntimeController.
  • Keep EditorWindowNativePeer frame/runtime communication data-shaped: use EditorNativeWindowRuntimeSurface, EditorNativeWindowFrameSnapshot, EditorNativeWindowFrameCommands, and small metrics snapshots instead of exposing individual HWND, DPI, size, input, cursor, or title-bar getters to app windowing. EditorNativeWindowRuntimeSurface must not expose a raw native handle field.
  • Do not let Win32 host code call EditorWindowRuntimeController or directly drive editor runtime frames. Native messages may request immediate frames through EditorWindowHostCoordinator; app windowing owns the render/update step.
  • Use direct authoritative workspace binding for ordinary in-window workspace mutations.
  • Use synchronization plans for operations that create, close, replace, or reconcile workspace windows.
  • Use frame transfer requests only for host-side or cross-window effects.

Current Architecture Debt

The highest-value windowing boundary has been hardened: XCUIEditorLib now owns the reusable window authority, synchronization planner, workspace store, and presentation projection under XCEditor/Windowing. App windowing remains a source ownership boundary under editor/app/Windowing; it is not allowed to be a separate production library target. The Win32 host is the concrete native adapter and creates native host windows for app-owned EditorWindowInstance objects supplied by app windowing.

The framework/app compile boundary is also enforced. XCUIEditorLib does not receive editor/app includes, and XCUIEditorApp does not directly compile the shared framework sources. When framework UI code needs app-provided data such as loaded icons, expose a framework-owned interface and adapt it in app composition code.

The top-level target debt is any app-internal static library target left in the build. XCUIEditorLib must remain the only editor static library. If CMake defines targets such as XCUIEditorAppWindowing, XCUIEditorAppCore, XCUIEditorAppLib, or XCUIEditorHost, remove those target boundaries and compile their app-side sources into XCUIEditorApp.

The main windowing debt that was previously in the Win32 host has been cut: frame iteration, immediate frame driving, content-controller construction, and EditorContext ownership now live in app windowing. EditorWindowHostCoordinator does not expose GetEditorContext(), and the native host receives an app-owned EditorWindowInstance rather than raw workspace/utility content or a runtime controller.

The native-peer ownership cut has also been made: EditorWindowManager owns EditorWindowInstance objects, each instance owns its EditorWindowRuntimeController, and editor/app/Platform/Win32/Windowing EditorWindow is a native peer for HWND/input/chrome/message integration. The Win32 platform code must stay free of Windowing/Runtime/EditorWindowRuntimeController.h.

The native frame contract cut has also been made: frame/runtime initialization no longer crosses the boundary through granular native-peer getters such as HWND, DPI, size, input, cursor, workspace bounds, and title-bar mode. Win32 captures those native facts into EditorNativeWindowRuntimeSurface and EditorNativeWindowFrameSnapshot; app windowing renders from the snapshot and returns native side effects through EditorNativeWindowFrameCommands. EditorNativeWindowRuntimeSurface carries an abstract render surface object, not a raw HWND or erased void*.

The renderer ownership cut has also been made: app windowing no longer owns the concrete D3D12 window render loop or Win32 surface setup. EditorWindowRuntimeController consumes a backend-neutral Rendering::Host::EditorWindowRenderRuntime, and EditorWindowManager receives a backend-neutral Rendering::Host::EditorWindowRenderRuntimeFactory. The current D3D12 factory is composed in Application, not in the Win32 host. The current D3D12 runtime may recognize the concrete Win32 render surface adapter, but app windowing must only traffic in the abstract rendering-host surface type.

The platform-boundary cut has also been made: XCUIEditorLib trace code is platform-neutral, and Win32 exception/DbgHelp stack capture lives under editor/app/Platform/Win32/Diagnostics. Utility-window descriptors express native shell intent with semantic host policy values rather than Win32 style bits; the Win32 host maps those semantics to WS_* constants.

The remaining promotion debt is the app runtime surface and host contract vocabulary itself. editor/app/Windowing is still app-internal and may depend on app semantics such as EditorContext, EditorShellRuntime, utility window descriptors, and product-specific content. This app-internal dependency is a directory-level ownership detail only; it must not become a separate static library boundary. Do not promote host interfaces, frame transfer, runtime controllers, content controllers, or render-runtime contracts to XCEditor until they are generic enough to expose. Do not move frame ownership back into editor/app/Platform/Win32, do not move concrete renderer ownership back into editor/app/Windowing, and do not move renderer factory ownership back into the Win32 host.

Do not take another library-boundary architecture cut just to keep carving this area. After the native-peer and native-frame-contract cuts, the next default improvement should be boundary guardrails, such as checks that keep editor/app/Windowing free of Rendering/D3D12, Platform/Win32, windows.h, HWND types, raw native render handles, and one-off native-peer frame getters; keep app semantics free of Win32 WS_* constants; keep editor/include/XCEditor and editor/src free of Win32 diagnostics and exception types; and keep editor/app/Platform/Win32 free of Rendering/D3D12 and Windowing/Runtime/EditorWindowRuntimeController.h. Consider another structural source-directory cut only when there is concrete pressure, such as another native host, another render backend, or headless editor/window tests; do not satisfy that pressure by adding another production static library.

Validation

Default editor validation is only the app build plus the 12-second smoke run:

cmake --build build --config Debug --target editor_ui_smoke_targets
build\tests\UI\Editor\smoke\Debug\editor_ui_smoke_runner.exe build\editor\Debug\XCEngine.exe

Do not run editor_windowing_phase1_tests, editor_ui_tests, or broader test targets by default. Run them only when the user explicitly asks for them or a separate targeted change makes them necessary.

The runner sets XCUIEDITOR_SMOKE_TEST_DURATION_SECONDS=12, waits for the editor to launch, lets the app auto-exit, and treats a clean editor exit as success.

Start with these files for editor/windowing work:

  • editor/CMakeLists.txt
  • editor/app/Bootstrap/Application.*
  • editor/app/Composition/EditorContext.*
  • editor/app/Composition/EditorShellRuntime.*
  • editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h
  • editor/src/Workspace/UIEditorWorkspaceController.cpp
  • editor/include/XCEditor/Windowing/System/EditorWindowSystem.h
  • editor/include/XCEditor/Windowing/System/EditorWindowSynchronizationPlan.h
  • editor/include/XCEditor/Windowing/System/EditorWindowSynchronizationPlanner.h
  • editor/include/XCEditor/Windowing/Presentation/EditorWorkspaceWindowProjection.h
  • editor/include/XCEditor/Windowing/Presentation/EditorWindowPresentationPolicy.h
  • editor/src/Windowing/System/EditorWindowSystem.cpp
  • editor/src/Windowing/System/EditorWindowWorkspaceStore.*
  • editor/src/Windowing/System/EditorWindowSynchronizationPlanner.cpp
  • editor/src/Windowing/Presentation/EditorWindowPresentationPolicy.cpp
  • editor/app/Windowing/Host/EditorWindowHostInterfaces.h
  • editor/app/Windowing/Content/EditorWindowContentController.h
  • editor/app/Windowing/Content/EditorWindowContentFactory.*
  • editor/app/Windowing/Content/EditorWorkspaceWindowContentController.*
  • editor/app/Windowing/Frame/EditorWindowFrameOrchestrator.*
  • editor/app/Windowing/Frame/EditorWindowTransferRequests.h
  • editor/app/Windowing/Runtime/EditorWindowRuntimeController.*
  • editor/app/Windowing/Runtime/EditorWindowScreenshotController.*
  • editor/app/Windowing/EditorWindowInstance.*
  • editor/app/Windowing/Coordinator/EditorWindowWorkspaceCoordinator.*
  • editor/app/Windowing/Coordinator/EditorUtilityWindowCoordinator.*
  • editor/app/Windowing/EditorWindowManager.*
  • editor/app/Rendering/Host/EditorWindowRenderRuntime.h
  • editor/app/Rendering/D3D12/D3D12EditorWindowRenderRuntime.*
  • editor/app/Platform/Win32/Diagnostics/Win32CrashTrace.*
  • editor/app/Platform/Win32/Windowing/Win32EditorWindowRenderRuntimeSurface.h
  • editor/app/Platform/Win32/Windowing/EditorWindow.*
  • editor/app/Platform/Win32/Windowing/EditorWindowHostRuntime.*
  • editor/app/Platform/Win32/Windowing/EditorWindowMessageDispatcher.*
  • tests/UI/Editor/smoke/CMakeLists.txt

Recent Cuts

  • The framework-to-app icon dependency is sealed: UIEditorShellCompose uses the framework-owned UIEditorShellIconResolver, and the app adapts BuiltInIcons in EditorShellDrawComposer.
  • The framework/app compile boundary is sealed: XCUIEditorLib does not receive editor/app includes, XCUIEditorApp does not directly compile ${XCUI_EDITOR_SHARED_SOURCES}, and the app consumes framework code through XCUIEditorLib.
  • The reusable window authority core lives in the framework: EditorWindowSystem, synchronization planning, workspace store, and presentation projection are under editor/include/XCEditor/Windowing and editor/src/Windowing.
  • App windowing owns the runtime cut: content controllers, EditorWindowRuntimeController, frame driving, frame transfer, host contracts, and EditorWindowManager are under editor/app/Windowing as an app source directory, not as a standalone library target; Win32 remains the native adapter and no longer exposes EditorContext.
  • The native-peer ownership cut is sealed: app windowing now owns live EditorWindowInstance objects, those instances own EditorWindowRuntimeController, and Win32 EditorWindow only owns the native peer side for HWND/input/chrome/message work. CreateHostWindow(...) binds a native peer to an existing EditorHostWindow; it does not receive a runtime controller.
  • The native frame contract cut is sealed: app windowing consumes EditorNativeWindowRuntimeSurface and EditorNativeWindowFrameSnapshot instead of calling granular native-peer getters for frame/runtime data, and Win32 applies frame side effects through EditorNativeWindowFrameCommands. Runtime surface creation carries an abstract Rendering::Host::EditorWindowRenderRuntimeSurface, not a raw HWND/void*.
  • The concrete renderer cut is sealed: app windowing consumes Rendering::Host::EditorWindowRenderRuntime and Rendering::Host::EditorWindowRenderRuntimeFactory, while the current D3D12 implementation and factory live under editor/app/Rendering/D3D12. The factory is composed in Application; editor/app/Windowing should stay free of Rendering/D3D12, windows.h, and HWND types, and editor/app/Platform/Win32 should stay free of Rendering/D3D12.
  • The Win32 platform-boundary cut is sealed: framework runtime trace code no longer includes Win32/DbgHelp or exposes _EXCEPTION_POINTERS; Win32 crash stack capture lives under editor/app/Platform/Win32/Diagnostics; utility window descriptors use semantic native host policies instead of WS_* constants.
  • The production target shape is sealed: XCUIEditorLib is the only editor static library, and all app-side source directories compile into XCUIEditorApp.
  • Default validation remains the editor app build plus the 12-second smoke run; run broader windowing/unit targets only for targeted coverage.