# NewEditor Frame/Lifecycle Runtime Refactor Plan Date: 2026-04-22 ## 1. Objective This plan targets the highest-priority remaining architecture debt in `new_editor`: 1. frame execution still has multiple live owners 2. window destruction still has multiple live owners 3. `EditorShellRuntime` is still a per-frame god-object The goal is not to split files mechanically. The goal is to collapse ownership so that: 1. only one module can drive a frame 2. only one module can finalize window death 3. `EditorShellRuntime` becomes a thin facade over explicit per-frame stages ## 2. Confirmed Root Issues ### 2.1 Frame scheduling is not single-owner `RenderFrame()` is currently called from: - main host render loop - `WM_PAINT` - `WM_SIZE` - `WM_DPICHANGED` - `WM_EXITSIZEMOVE` - borderless chrome transition logic This means rendering is still driven by both: 1. the normal frame loop 2. synchronous Win32 message-side side effects As long as this remains true, frame timing, transfer request ordering, and present timing will keep depending on which path happened to trigger first. ### 2.2 Window lifetime is not single-owner `Shutdown()`, `DestroyWindow(...)`, `MarkDestroyed()`, and host erase currently happen from multiple places in `WindowManager`, host runtime, and workspace coordination. This means there is still no single authoritative answer to: 1. who requests close 2. who observes native window destruction 3. who shuts runtime down 4. who removes the managed window from the host container This is the same root class of problem that previously caused sub-window close regressions. ### 2.3 EditorShellRuntime still owns too many per-frame responsibilities `EditorShellRuntime::Update(...)` still performs, in one place: 1. shell definition build 2. workspace/session sync 3. dock host interaction update 4. viewport request/update 5. hosted panel input routing 6. hosted panel update 7. status/trace/command-focus synchronization That makes the runtime difficult to reason about and causes unrelated per-frame concerns to stay tightly coupled. ## 3. Target End State After this refactor: 1. only one frame driver can execute `BeginFrame -> UpdateAndAppend -> Present` 2. Win32 message dispatch only records frame requests / dirty reasons, never renders directly 3. borderless chrome logic can request a new frame, but cannot render directly 4. only one lifecycle coordinator can finalize managed window destruction 5. `WorkspaceCoordinator` can request a window-set mutation, but cannot directly destroy windows 6. `EditorShellRuntime` becomes a thin composition facade over smaller stage modules ## 4. New Modules ### 4.1 Frame ownership Add: - `new_editor/app/Platform/Win32/EditorWindowFrameDriver.h` - `new_editor/app/Platform/Win32/EditorWindowFrameDriver.cpp` - `new_editor/app/Platform/Win32/EditorWindowFrameRequest.h` Responsibilities: - own the only live path that executes a frame - consume frame request reasons / dirty state - normalize when transfer requests are collected and applied ### 4.2 Lifecycle ownership Add: - `new_editor/app/Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.h` - `new_editor/app/Platform/Win32/WindowManager/EditorWindowLifecycleCoordinator.cpp` Responsibilities: - own close request state - own runtime shutdown sequencing - own native destroy finalization and managed erase - own primary-window cascade behavior ### 4.3 Shell runtime stage split Add: - `new_editor/app/Composition/EditorShellSessionCoordinator.h` - `new_editor/app/Composition/EditorShellSessionCoordinator.cpp` - `new_editor/app/Composition/EditorShellInteractionEngine.h` - `new_editor/app/Composition/EditorShellInteractionEngine.cpp` - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.h` - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` - `new_editor/app/Composition/EditorShellDrawComposer.h` - `new_editor/app/Composition/EditorShellDrawComposer.cpp` Responsibilities: - `SessionCoordinator`: context/workspace/session/command-focus/status synchronization - `InteractionEngine`: shell definition, dock host interaction, shell frame/state update - `HostedPanelCoordinator`: hosted panel input filtering, panel update, panel capture ownership - `DrawComposer`: shell draw packets, panel draw packets, viewport frame application to draw data ## 5. Refactor Strategy ### Phase A. Collapse frame execution to one driver Refactor current flow so that: 1. `EditorWindowHostRuntime` requests frame execution through `EditorWindowFrameDriver` 2. `EditorWindowMessageDispatcher` no longer calls `RenderFrame(...)` directly 3. `EditorWindowChromeController` no longer calls `RenderFrame(...)` directly 4. `EditorWindow` no longer exposes uncontrolled render entrypoints to unrelated callers Message-side behavior must become: - mark dirty - enqueue reason - invalidate host window when needed But never: - begin a frame - build draw data - present directly ### Phase B. Collapse lifecycle to one owner Refactor current flow so that: 1. `WM_CLOSE` only means `RequestClose` 2. `WM_DESTROY` only reports native death 3. `EditorWindowLifecycleCoordinator` becomes the only place that can: - call `Shutdown()` - finalize managed destruction - erase the managed window from host storage - cascade close from the primary window 4. `WorkspaceCoordinator` stops duplicating host destroy logic ### Phase C. Split EditorShellRuntime by stage, not by helper dumping Do not split by random utility extraction. Split by stage ownership: 1. session sync stage 2. shell interaction stage 3. hosted panel stage 4. draw composition stage `EditorShellRuntime` should remain as a thin orchestrating facade that wires these stages together in a stable order. ### Phase D. Normalize panel capture ownership As part of hosted panel coordination: 1. stop hardcoding panel capture checks directly in the runtime facade where possible 2. centralize panel capture ownership through one hosted-panel coordination boundary 3. make it explicit which hosted panels participate in pointer-stream ownership decisions ## 6. File-Level Impact ### 6.1 Existing files to shrink / re-scope - `new_editor/app/Platform/Win32/EditorWindow.cpp` - `new_editor/app/Platform/Win32/EditorWindow.h` - `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` - `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp` - `new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h` - `new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp` - `new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceCoordinator.cpp` - `new_editor/app/Composition/EditorShellRuntime.cpp` - `new_editor/app/Composition/EditorShellRuntime.h` ### 6.2 Build graph Update: - `new_editor/CMakeLists.txt` to register the new frame/lifecycle/runtime stage units. ## 7. Validation ### 7.1 Structural validation After completion: 1. direct `RenderFrame(...)` call sites outside the frame driver must be gone 2. duplicate `Shutdown()/DestroyWindow()/MarkDestroyed()/erase` chains must be gone 3. `WorkspaceCoordinator` must no longer perform raw window destruction 4. `EditorShellRuntime` facade must delegate to explicit stage modules ### 7.2 Runtime validation Must validate: 1. editor starts normally 2. main window continues rendering normally 3. resize / maximize / drag-restore no longer perform direct message-side rendering 4. detached panel windows and tool windows still open/close correctly 5. closing child windows does not close the main window 6. closing the primary window still cascades correctly 7. frame transfer requests still behave correctly during docking / detach workflows ## 8. Execution Order 1. introduce frame request model and frame driver 2. remove message-side and chrome-side direct rendering 3. introduce lifecycle coordinator 4. remove duplicate destroy/shutdown ownership from host runtime and workspace coordinator 5. split `EditorShellRuntime` into session/interaction/panel/draw stages 6. rebuild and smoke-test 7. perform structural grep validation ## 9. Success Criteria This refactor is complete only if all of the following are true: 1. `new_editor` frame execution has a single live owner 2. `new_editor` managed window destruction has a single live owner 3. `EditorShellRuntime` is no longer a monolithic per-frame god-object 4. resize / dpi / chrome transitions no longer trigger direct rendering from message handlers 5. build and smoke validation both pass