# Modernization Debt Log Status: live Last updated: 2026-06-17 Every shortcut, temporary adapter, retained vendored dependency, skipped platform gate, compatibility shim, or incomplete automation path must be recorded here before it lands. Entries must be specific enough for a future agent or engineer to remove them without reconstructing context from chat. ## Entry Rules - Add an entry before merging the shortcut. - Reference the debt id in code comments, TODOs, ADRs, or roadmap notes. - Include an owner, reason, validation command, and removal condition. - Do not close an entry until the removal condition is met and validated. - Prefer deleting shortcuts over expanding this log. ## Reductions - 2026-06-17: `DEBT-0003` was narrowed again. `src/platform_windows/windows_runtime_flow.*` now owns the live Win32 startup/session composition flow, including the bound app/session preflight and the existing `convert` / `-vrmode` mode switch, so `src/platform_windows/windows_runtime_shell.cpp` is down to a thin entry wrapper and no longer repeats manual app/session teardown on each exit path. - 2026-06-17: `DEBT-0017` was narrowed again. The orphaned `src/platform_legacy/legacy_platform_services.*` shim and its `legacy_platform_fallback_behavior.h` helper were deleted after the root app, WebGL entrypoint, and retained Android package graphs all moved off that fallback path. - 2026-06-17: `DEBT-0017` was narrowed again. The retained Android package CMake projects now compile `src/platform_android/android_platform_services.cpp` instead of `src/platform_legacy/legacy_platform_services.cpp`, matching the Android entrypoint's existing concrete `create_platform_services(...)` path. The retained standard package link gate still fails, but the same package build also fails after restoring the old legacy source list, so that linker failure is currently tracked as preexisting noise rather than a regression from this slice. - 2026-06-17: `DEBT-0017`/`DEBT-0055` were narrowed again. `src/platform_legacy/legacy_platform_services.*` is no longer compiled into the root `panopainter_app` source graph. The retained shim still exists on disk for non-root compatibility consumers, but the root Windows/Android/Apple modernization matrix no longer links it into the live app target. - 2026-06-17: `DEBT-0017`/`DEBT-0050`/`DEBT-0053`/`DEBT-0057` were narrowed again. `webgl/src/main.cpp` now binds a concrete `src/platform_web/web_platform_services.*` `PlatformServices` instance directly, so the live WebGL entrypoint no longer depends on `src/platform_legacy/legacy_platform_services.*` for render-context, prepared-file, picker, app-close, or default-canvas behavior. - 2026-06-17: `DEBT-0003` was narrowed again. `src/platform_windows/windows_runtime_shell.cpp` now binds a local `MainWindowSession` object for the live Win32 session, and the window handle, title buffer, and sandbox flag now live on that explicit runtime-owned session instead of the retired retained accessor pocket. - 2026-06-17: `DEBT-0016`/`DEBT-0017`/`DEBT-0050`/`DEBT-0051`/`DEBT-0053`/ `DEBT-0057` were narrowed again. `src/platform_web/web_platform_services.*` now owns the concrete Web `PlatformServices` implementation and `webgl/src/main.cpp` now binds that owned service directly, so `src/platform_legacy/legacy_platform_services.*` no longer carries the touched Web render-context, app-close, picker, storage-path, persistent-storage, default-canvas, prepared-file, or default-render-target branches, and `src/platform_legacy/legacy_platform_state.*` is gone. - 2026-06-17: `DEBT-0016`/`DEBT-0017`/`DEBT-0051` were narrowed again. `src/platform_linux/linux_platform_services.*` now owns the concrete Linux `PlatformServices` implementation and `linux/src/main.cpp` now binds that owned service directly, so `src/platform_legacy/legacy_platform_services.*` no longer carries the touched Linux storage-path, GLFW render-context, app-close, desktop picker, default-render-target, or FPS-reporting branches. - 2026-06-17: `DEBT-0016`/`DEBT-0017` were narrowed again. `src/platform_android/android_platform_services.*` now owns the concrete Android `PlatformServices` implementation and `android/src/cpp/main.cpp` now binds that owned service directly, so `src/platform_legacy/legacy_platform_services.*` no longer carries the Android bridge/configuration surface or the touched Android execution branches. - 2026-06-17: `DEBT-0016`/`DEBT-0017`/`DEBT-0050`/`DEBT-0051`/`DEBT-0052`/ `DEBT-0053` were narrowed again. `src/platform_apple/apple_platform_services.*` now owns the concrete Apple `PlatformServices` implementation and `create_apple_platform_services()`, the Apple entrypoints bind that owned service directly, and `src/platform_legacy/legacy_platform_services.*` no longer carries the touched Apple execution branches or Apple provider configuration surface. - 2026-06-17: `DEBT-0016`/`DEBT-0017`/`DEBT-0051`/`DEBT-0052`/`DEBT-0053` were narrowed again. `PanoPainter-OSX/main.cpp` and `PanoPainter/GameViewController.m` now bind owned legacy `PlatformServices` instances into `App`, and `src/platform_legacy/legacy_platform_services.*` now takes an injected Apple document-service provider instead of hardcoding direct calls to `active_legacy_apple_document_platform_services()` in the touched path. - 2026-06-17: `DEBT-0016`/`DEBT-0017` were narrowed again. `src/platform_legacy/legacy_platform_services.*` now takes an explicit Android bridge through `create_platform_services(...)`, and `android/src/cpp/main.cpp` now seeds Android-owned clipboard, keyboard, JNI-thread attach/detach, async render-context, and file-picker callbacks into that adapter instead of leaving those JNI/EGL entrypoints declared directly inside the cross-platform fallback layer. - 2026-06-17: `DEBT-0017`/`DEBT-0050`/`DEBT-0053`/`DEBT-0057` were narrowed again. `src/platform_legacy/legacy_platform_services.*` now takes an explicit `WebPlatformServices*` dependency through `create_platform_services(...)`, `webgl/src/main.cpp` now threads its owned Web service directly into that legacy adapter, and `src/platform_legacy/legacy_platform_state.*` no longer carries the retained `active_legacy_web_platform_services()` / `try_*legacy_web*` dispatch layer; retained non-Windows adapter execution still exists, but the live WebGL path no longer depends on a legacy Web fallback singleton-style bridge. - 2026-06-17: `DEBT-0017` was narrowed again. `src/platform_legacy/legacy_platform_services.*` no longer exposes the dead `pp::platform::legacy::platform_services()` fallback singleton; Linux, WebGL, and Android already bind owned `create_platform_services(...)` instances at their entrypoints, so the retained non-Windows debt is now limited to the adapter implementation itself rather than a live global accessor surface. - 2026-06-16: `DEBT-0003` was narrowed again. `main.cpp` main-thread queued task state now lives behind a narrow retained helper instead of `RetainedState.main_tasklist` / `main_task_mutex` directly; broader Win32 bootstrap/runtime ownership remains. - 2026-06-16: `DEBT-0037` was narrowed again. `App::update_rec_frames()` in `src/app.cpp` now delegates recording label refresh through `update_legacy_recording_frame_label(...)` in `src/legacy_recording_services.cpp` instead of building the label directly; retained recording label lookup, encoder-state reads, and MP4 execution remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` non-`draw_merged` per-frame layer draw callback setup now routes through `make_legacy_canvas_draw_merge_layer_frame_draw(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping that callback body inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0003` was narrowed again. The Win32 async GL/context lock state now lives in `src/platform_windows/windows_platform_services.cpp` instead of `src/main.cpp` retained state, and `main.cpp` only seeds the created `HDC`/`HGLRC` into that platform-owned helper during initialization and context recreation; broader Win32 bootstrap/runtime ownership remains. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` merged-path per-plane merged-texture draw callback setup now routes through `make_legacy_canvas_draw_merge_layer_texture_draw(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping that callback setup inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0037` was narrowed again. `App::rec_loop()` in `src/app.cpp` now delegates recording worker iteration orchestration into `process_legacy_recording_worker_iteration(...)` in `src/legacy_recording_services.cpp` instead of owning the wait/plan/encode shell itself; retained recording loop control, readback call sites, and MP4 execution remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` checkerboard background-plane callback setup now routes through `make_legacy_canvas_draw_merge_background_checkerboard_plane(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping that callback inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0003` was narrowed again. Canvas async import/export/save/open background work now runs through an `AppRuntime`-owned queue/worker in `src/app_runtime.h/.cpp`, and `src/canvas.cpp` no longer defines a retained static canvas async worker; broader app task ownership and retained runtime singleton reach remain. - 2026-06-16: `DEBT-0037` was narrowed again. `App::rec_loop()` in `src/app.cpp` now routes its wait plus iteration plan/encode shell through a local helper instead of carrying that orchestration inline; retained recording loop control, readback call sites, and MP4 execution remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` grid-mode draw callback setup now routes through `make_legacy_canvas_draw_merge_grid_modes_draw(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping that callback loop inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0003` was narrowed again. Prepared-file background work now runs through an `AppRuntime`-owned queue/worker in `src/app_runtime.h/.cpp`, and `src/app_events.cpp` no longer defines a retained static prepared-file worker; broader app task ownership and canvas async worker ownership remain. - 2026-06-16: `DEBT-0037` was narrowed again. `App::rec_loop()` in `src/app.cpp` now routes its worker-iteration pointer lookup plus `plan_recording_worker_iteration(...)` setup through a local helper instead of carrying that setup inline; retained recording loop control, readback call sites, and MP4 execution remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` current-mode draw callback setup now routes through `make_legacy_canvas_draw_merge_current_modes_draw(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping that callback loop inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0037` was narrowed again. `App::rec_loop()` in `src/app.cpp` now routes its coherent frame encode/update chunk through a local helper instead of carrying dirty-stroke clearing, equirect PBO creation, the worker-side `yield`, `ImageRef` creation, encode, and frame label refresh inline; retained recording loop control, readback call sites, and MP4 execution remain. - 2026-06-16: `DEBT-0003` was narrowed again. The prepared-file worker in `src/app_events.cpp` and the canvas async worker in `src/canvas.cpp` now sit behind named retained local worker-state helpers instead of bare static worker accessors; retained app/canvas singleton reach and broader runtime ownership remain. - 2026-06-16: `DEBT-0003` was narrowed again. `src/main.cpp` now keeps Win32 window handles, GL/task mutex state, stylus timers, splash-dialog state, and VR worker state behind one retained local state object instead of separate file-scope globals; retained entrypoint orchestration, direct `App::I` mutation, and broader platform/runtime coupling remain. - 2026-06-16: `DEBT-0017` was narrowed again. Retained GLFW window hooks and the non-Linux fallback storage-path return in `src/platform_legacy/legacy_platform_services.cpp` now route through local retained-state helpers instead of direct method-body `App::I` reads; the retained platform fallback adapter and broader platform-to-app singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` heightmap draw callback setup now routes through `make_legacy_canvas_draw_merge_heightmap_draw(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping that callback body inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0003` was narrowed again. The Windows main-loop run-state and VR worker coordination flags in `src/main.cpp` now use `std::atomic` instead of unsynchronized globals; retained Win32 entrypoint ownership, global app singleton reach, and broader runtime coupling remain. - 2026-06-16: `DEBT-0017` was narrowed again. Retained Apple ObjC handles plus storage paths now live behind one local helper in `src/platform_legacy/legacy_platform_services.cpp`, and the iOS SonarPen bridge now starts through that retained Apple state instead of reading `App::I` inside the bridge body; the retained Apple fallback adapter and broader platform-to-app singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` smoothing-mask face shader setup plus per-face draw execution now route through `execute_legacy_canvas_draw_merge_smask_faces(...)` in `src/legacy_canvas_draw_merge_services.h` instead of spelling that pass out inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` merged-path per-plane merged-texture draw execution now routes through `execute_legacy_canvas_draw_merge_layer_texture(...)` instead of spelling out the sampler bind, `TextureAlpha` setup, texture bind, draw, and unbind inline in `NodeCanvas::draw()`; broader canvas draw orchestration remains. - 2026-06-16: `DEBT-0003` was narrowed again. Windows VR device ownership in `src/main.cpp` now uses `std::unique_ptr` instead of a raw global `Vive*`; retained global VR state, Win32 message-loop ownership, and broader app/runtime coupling remain. - 2026-06-16: `DEBT-0017` was narrowed again. Linux/Web GLFW render-context acquire/present hooks and Linux app-close now route through retained local GLFW callback hooks in `src/platform_legacy/legacy_platform_services.cpp` instead of direct method-body `App::I` access; retained fallback platform ownership and broader singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` cache-to-screen checkerboard-plane callback setup now routes through `make_legacy_canvas_draw_merge_cache_to_screen_checkerboard_plane(...)` in `src/legacy_canvas_draw_merge_services.h` instead of building the full MVP callback body inline in `NodeCanvas::draw()`; broader canvas draw orchestration remains. - 2026-06-16: `DEBT-0003` was narrowed again. `LogRemote` now owns its network logging worker as `std::jthread` with explicit stop requests in `src/log.*` instead of raw `std::thread` ownership; retained global logger singleton, network logging policy, and queue execution remain. - 2026-06-16: `DEBT-0017` was narrowed again. Apple crash-test, app-close, and iOS SonarPen hooks now route through explicit bridge callbacks in `src/platform_apple/apple_platform_services.*` consumed by `src/platform_legacy/legacy_platform_services.cpp` instead of direct `App::I` calls in those legacy platform methods; the retained Apple fallback adapter and broader platform singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` merged-path and non-blend checkerboard background setup now route through `execute_legacy_canvas_draw_merge_background_setup(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping that shared background loop inline in both branches of `NodeCanvas::draw()`; broader canvas draw orchestration remains. - 2026-06-16: `DEBT-0003` was narrowed again. The Windows splash-dialog worker and HMD renderer worker in `src/main.cpp` now use `std::jthread` with explicit stop requests instead of raw `std::thread` ownership; retained global VR state, Win32 message-loop ownership, and broader app runtime coupling remain. - 2026-06-16: `DEBT-0017` was narrowed again. Apple render-context acquire/release/present hooks and iOS main-render-target binding now route through explicit bridge callbacks in `src/platform_apple/apple_platform_services.*` consumed by `src/platform_legacy/legacy_platform_services.cpp` instead of direct `App::I` calls in those legacy platform methods; the retained Apple fallback adapter and broader platform singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` non-`draw_merged` per-layer/per-plane retained draw execution now routes through `execute_legacy_canvas_draw_merge_layer_plane(...)` in `src/legacy_canvas_draw_merge_services.h` instead of keeping the erase, temporary paint/composite, regular texture, and optional blend path fully inline in `NodeCanvas::draw()`; retained layer traversal and broader canvas renderer orchestration remain. - 2026-06-16: `DEBT-0003` was narrowed again. `AppRuntime` now owns its render/UI worker threads as `std::jthread` with explicit stop requests in `src/app_runtime.cpp` and `src/app_runtime.h` instead of raw `std::thread` ownership; retained app task call sites, singleton composition, and broader runtime/platform coupling remain. - 2026-06-16: `DEBT-0017`/`DEBT-0053` were narrowed again. iOS virtual-keyboard visibility and prepared-file save handoff now route through explicit Apple bridge callbacks in `src/platform_apple/apple_platform_services.*` consumed by `src/platform_legacy/legacy_platform_services.cpp` instead of direct `App::I` calls in those legacy platform methods; the retained Apple fallback adapter and broader platform singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` smoothing-mask overlay draw, smoothing-mask face pass, grid keepalive draw, heightmap draw, and current-mode draw now route through `execute_legacy_canvas_draw_merge_post_draw(...)` in `src/legacy_canvas_draw_merge_services.h` instead of living inline in `NodeCanvas::draw()`; broader canvas draw orchestration and retained GL resource ownership remain. - 2026-06-16: `DEBT-0036` was narrowed again. The retained `NodePanelGrid::bake_uvs()` worker now uses scoped `std::jthread` ownership instead of a raw local `std::thread`; retained bake execution, progress-loop polling, and grid rendering ownership remain. - 2026-06-16: `DEBT-0017` was narrowed again. `LegacyPlatformServices::prepare_storage_paths()` now routes Apple storage path setup through a local helper instead of reading `App::I` directly in the method body; the retained Apple fallback adapter and broader platform-to-app singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeStrokePreview` background preview execution now owns its worker as `std::jthread` with explicit stop, unblock, and join semantics instead of a raw `std::thread`; retained queue ownership, static renderer resources, and preview execution flow remain. - 2026-06-16: `DEBT-0037` was narrowed again. Recording worker ownership now uses `std::jthread` through `App::rec_thread` and `src/legacy_recording_services.cpp`, with explicit stop requests before the existing notify/join flow; retained recording loop control, progress lifetime, and export execution remain. - 2026-06-16: `DEBT-0017`/`DEBT-0051`/`DEBT-0052`/`DEBT-0055` were narrowed again. `active_apple_document_platform_services()` in `src/platform_legacy/legacy_platform_services.cpp` now captures explicit Apple view/app handles for its bridge callbacks instead of closing over `App::I` inside those lambdas, while the rest of the retained platform fallback still owns broader singleton reach and platform execution. - 2026-06-16: `DEBT-0043`/`DEBT-0040`/`DEBT-0042` were narrowed again. The retained `Canvas` async import/export/save/open entrypoints in `src/canvas.cpp` no longer launch detached threads; they now use an owned local `std::jthread` queue and return completion callbacks to the UI thread, while retained progress-node mutation, render-task orchestration, and canvas storage/export ownership remain. - 2026-06-16: `DEBT-0044` was narrowed again. The retained asynchronous timelapse export path in `src/legacy_document_export_services.cpp` no longer launches a detached worker; it now uses a service-owned `std::jthread` queue and returns the success dialog to the UI thread, while retained recording/export execution still stays behind the legacy bridge. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` cache-to-screen checkerboard plus cache-texture composite now route through `legacy_canvas_draw_merge_services.h` instead of living inline in `NodeCanvas::draw()`; broader canvas draw orchestration remains retained. - 2026-06-16: `DEBT-0051`/`DEBT-0052`/`DEBT-0055` were narrowed again. `src/platform_apple/apple_platform_services.*` no longer reaches `App::I` for clipboard, display/share, cursor-visibility, or save-ui-state behavior; those calls now flow through narrow injected Apple bridge callbacks from `src/platform_legacy/legacy_platform_services.cpp`, while retained Apple bridge construction and broader platform singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` density-resolve display execution now routes through `legacy_canvas_draw_merge_services.h` instead of living inline in `NodeCanvas::draw()`; the cache-to-screen composite block and broader canvas draw orchestration remain retained. - 2026-06-16: `DEBT-0053` was narrowed again. `App::pick_file_save(...)` no longer launches a detached worker for background prepared-file writes; it now uses a service-owned `std::jthread` queue and posts prepared-file save completion back to the UI thread, while retained platform save/download handoff execution remains. - 2026-06-16: `DEBT-0036` was narrowed again. The retained grid lightmap launch in `src/legacy_grid_ui_services.cpp` no longer uses a detached worker thread; it now uses a service-owned `std::jthread` queue with UI-thread state handoff, while retained bake execution and grid rendering ownership remain. - 2026-06-16: `DEBT-0048` was narrowed again. The retained ABR/PPBR import bridge in `src/legacy_brush_package_import_services.cpp` no longer launches detached worker threads; it now uses a service-owned `std::jthread` queue, while retained preset ownership, progress UI, and storage mutation remain. - 2026-06-16: `DEBT-0047` was narrowed again. The retained PPBR export bridge in `src/legacy_brush_package_export_services.cpp` no longer launches a detached desktop worker; it now uses a service-owned `std::jthread` queue and returns dialog close plus success-message work to the UI thread, while retained dialog data extraction, preset export ownership, and mobile/Web completion remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeStrokePreview` final composite plus preview-copy execution now routes through `legacy_node_stroke_preview_execution_services.h` instead of living inline in `draw_stroke_immediate()`; retained live-pass sequencing, GL resource ownership, and node-owned draw execution remain. - 2026-06-15: `DEBT-0042` was narrowed again. The retained Save Version dialog wiring in `src/app_dialogs.cpp` now routes through a focused helper instead of living inline in `App::dialog_save_ver()`; the remaining document-session bridge debt stays concentrated in close prompts, save dialogs, app document field mutation, and keyboard/dialog cleanup. - 2026-06-15: `DEBT-0042` was narrowed again. The retained Save dialog button wiring in `src/app_dialogs.cpp` now routes through a focused helper instead of living inline in `App::dialog_save()`; the remaining document-session bridge debt stays concentrated in close prompts, save-version routing, app document field mutation, and keyboard/dialog cleanup. - 2026-06-15: `DEBT-0042` was narrowed again. The retained Save Version execution in `src/legacy_document_session_services.cpp` now routes through a focused helper instead of living inline in `LegacyDocumentVersionSaveServices::save_document_version()`; the remaining document-session bridge debt stays concentrated in close prompts, save dialogs, overwrite prompts, and keyboard/dialog cleanup. - 2026-06-15: `DEBT-0040` was narrowed again. The retained close-unsaved prompt wiring in `src/legacy_document_session_services.cpp` now routes through a focused helper instead of living inline in `show_unsaved_close_prompt()`; the remaining document-session bridge debt stays concentrated in save-before-workflow prompts, save-version routing, app document field mutation, and keyboard/dialog cleanup. - 2026-06-15: `DEBT-0040` was narrowed again. The retained save-before- continue workflow prompt wiring in `src/legacy_document_session_services.cpp` now routes through a focused helper instead of living inline in `prompt_save_before_continue()`; the remaining document-session bridge debt stays concentrated in close prompts, native close requests, save-version routing, app document field mutation, and keyboard/dialog cleanup. - 2026-06-15: `DEBT-0040`/`DEBT-0041`/`DEBT-0042` were narrowed again. The retained overwrite-prompt wiring in `src/legacy_document_session_services.cpp` now routes through a focused helper instead of living inline in the new document and save-file prompt methods; the remaining document-session bridge debt stays concentrated in close prompts, native close requests, save-version routing, app document field mutation, and keyboard/dialog cleanup. - 2026-06-15: `DEBT-0039` was narrowed again. The retained ABR/PPBR import prompt wiring in `src/legacy_document_open_services.cpp` now routes through a focused helper instead of living inline in each document-open import prompt; the remaining document-open bridge debt stays concentrated in unsaved-project prompts, project-open execution, layer refresh, title updates, and retained history services. - 2026-06-15: `DEBT-0039` was narrowed again. The retained document-browse button wiring in `App::dialog_browse()` now routes through a focused helper in `src/app_dialogs.cpp` instead of living inline in the retained browse dialog method; the remaining document-open bridge debt stays concentrated in unsaved-project prompts, project-open execution, layer refresh, title updates, and retained history services. - 2026-06-15: `DEBT-0039` was narrowed again. The retained unsaved-project discard prompt wiring in `LegacyDocumentOpenServices::prompt_discard_unsaved_project()` now routes through a focused helper in `src/legacy_document_open_services.cpp` instead of living inline in the retained document-open service method; the remaining document-open bridge debt stays concentrated in project-open execution, layer refresh, title updates, and retained history services. - 2026-06-15: `DEBT-0039` was narrowed again. The retained project-open success/failure handling in `open_legacy_project()` now routes through a focused helper in `src/legacy_document_open_services.cpp` instead of living inline in the retained document-open helper; the remaining document-open bridge debt stays concentrated in layer refresh, title updates, and retained history services. - 2026-06-15: `DEBT-0039` was narrowed again. The retained project-open layer/title update work and failure-path error dialog in `handle_open_legacy_project_result()` now route through focused helpers in `src/legacy_document_open_services.cpp` instead of living inline in the retained document-open helper; the remaining document-open bridge debt stays concentrated in retained history services. - 2026-06-15: `DEBT-0039` was narrowed again. The retained project-open history-planning call in `open_legacy_project()` now routes through a focused helper in `src/legacy_document_open_services.cpp` instead of living inline in the retained document-open helper; the remaining document-open bridge debt stays concentrated in retained history services. - 2026-06-15: `DEBT-0039`/`DEBT-0040` were narrowed again. The retained history adapter now reuses the shared `src/legacy_history_services.*` helper from both document-open and document-session bridges instead of living inline in those bridge files; the remaining bridge debt stays concentrated in retained history services. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud downloaded- project open camera reset and layer-clear setup in `execute_legacy_downloaded_project_open()` now routes through a focused helper in `src/legacy_document_open_services.cpp` instead of living inline in the document-open bridge; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud downloaded- project open, layer refresh, and action-history reset in `execute_legacy_downloaded_project_open()` now route through a focused helper in `src/legacy_document_open_services.cpp` instead of living inline in the document-open bridge; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud publish prompt setup in `show_cloud_publish_prompt()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained prompt/setup body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud publish detached-thread launch in `show_cloud_publish_prompt()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained prompt/setup body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud publish transfer-and-success body in `execute_cloud_publish_worker()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained worker body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud download flow in `execute_cloud_download_thread()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained worker body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud download post-transfer open/close sequence in `execute_cloud_download_thread()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained worker body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud download URL construction and progress-callback wiring in `execute_cloud_download_thread()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained worker body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud download progress-dialog creation path in `execute_cloud_download_thread()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained worker body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud download thread launch in `LegacyCloudServices::start_download()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud browser dialog creation path in `LegacyCloudServices::show_browser()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud publish prompt path in `LegacyCloudServices::prompt_publish()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud save-required warning path in `LegacyCloudServices::show_save_required_warning()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud bulk-upload per-file execution loop in `LegacyCloudServices::upload_all_bulk_files()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud bulk-upload progress lifetime wiring in `LegacyCloudServices::begin_bulk_upload()` and `end_bulk_upload()` now routes through focused helpers in `src/legacy_cloud_services.cpp` instead of living inline in the retained service methods; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud-browser OK-button wiring in `LegacyCloudServices::show_browser()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud-publish prompt button wiring in `LegacyCloudServices::prompt_publish()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud-publish worker body in `LegacyCloudServices::prompt_publish()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and the still-retained transfer-thread execution model. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud-download detached worker body in `LegacyCloudServices::start_download()` now routes through a focused helper in `src/legacy_cloud_services.cpp` instead of living inline in the retained service method; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and transfer-thread execution. - 2026-06-15: `DEBT-0038` was narrowed again. The retained cloud-browser thumbnail fetch, decode, and texture-apply path in `NodeDialogCloud::load_thumbs_thread()` now routes through a focused helper in `src/node_dialog_cloud.cpp` instead of living inline in the retained loader body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, and transfer-thread execution. - 2026-06-15: `DEBT-0038` was narrowed again. The cloud-browser slot creation and selection wiring in `NodeDialogCloud::load_thumbs_thread()` now route through a focused helper in `src/node_dialog_cloud.*` instead of living inline in the retained loader body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, `NodeDialogCloud` thumbnail execution, and transfer-thread execution. - 2026-06-15: `DEBT-0038` was narrowed again. The cloud-browser file-list request/response handling in `NodeDialogCloud::load_thumbs_thread()` now routes through a focused helper in `src/node_dialog_cloud.*` instead of living inline in the retained loader body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, `NodeDialogCloud` thumbnail execution, and transfer-thread execution. - 2026-06-15: `DEBT-0038` was narrowed again. The initial cloud-browser loading placeholder setup in `NodeDialogCloud::load_thumbs_thread()` now routes through a focused helper in `src/node_dialog_cloud.*` instead of living inline in the retained loader body; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, `NodeDialogCloud` network/thumbnail execution, and transfer-thread execution. - 2026-06-15: `DEBT-0038` was narrowed again. Retained cloud downloaded-project post-open reconciliation now routes through `src/legacy_document_open_services.*` instead of living inline in `src/legacy_cloud_services.cpp`; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, `NodeDialogCloud`, and transfer-thread execution. - 2026-06-15: `DEBT-0038` was narrowed again. Retained save-before-upload execution now routes through `src/legacy_document_session_services.*` instead of `src/legacy_cloud_services.cpp` calling `Canvas::I->project_save_thread(...)` directly; the remaining cloud bridge debt stays concentrated in retained prompt/progress lifetime, OpenGL context guarding, downloaded-project open, layer refresh, and action-history reset. - 2026-06-15: `DEBT-0038` was narrowed again. Retained cloud upload post-transfer response/error handling now routes through a dedicated helper in `src/legacy_cloud_services.cpp` instead of living inline in `execute_cloud_upload_transfer(...)`; the remaining cloud bridge debt stays concentrated in save-before-upload execution, retained prompt/progress lifetime, OpenGL context guarding, downloaded-project open, layer refresh, and action-history reset. - 2026-06-15: `DEBT-0038` was narrowed again. Retained cloud upload form construction now routes through a dedicated helper in `src/legacy_cloud_services.cpp` instead of living inline in `execute_cloud_upload_transfer(...)`; the remaining cloud bridge debt stays concentrated in save-before-upload execution, upload response/error handling, retained prompt/progress lifetime, OpenGL context guarding, downloaded-project open, layer refresh, and action-history reset. - 2026-06-15: `DEBT-0038` was narrowed again. Retained upload/download CURL execution, TLS-bypass policy consumption, and progress callback dispatch now live in `src/legacy_cloud_services.*` instead of `App::upload` and `App::download`; the remaining cloud bridge debt is now concentrated in save-before-upload execution, upload form/response handling, retained prompt/progress lifetime, OpenGL context guarding, downloaded-project open, layer refresh, and action-history reset. - 2026-06-15: `DEBT-0038`/`DEBT-0063` were narrowed again. The cloud browser dialog in `src/legacy_cloud_services.cpp` now opens through `src/legacy_ui_overlay_services.*` and participates in the checked overlay lifetime seam instead of direct raw `layout[main_id]->add_child(...)` ownership; remaining cloud debt is now concentrated in retained worker threads, network transfer helpers, project-open refresh, and dialog-internal legacy behavior. - 2026-06-15: `DEBT-0035`/`DEBT-0063` were narrowed again. The main-toolbar settings dialog in `src/legacy_app_shell_services.cpp` now opens through `src/legacy_ui_overlay_services.*` and participates in the checked overlay lifetime seam instead of direct raw `layout[main_id]->add_child(...)` ownership; cloud-browser and other remaining retained dialog families still stay open under the same debts. - 2026-06-15: `DEBT-0058`/`DEBT-0063` were narrowed again. App-owned progress/message/input dialogs created through `src/legacy_ui_overlay_services.*` now open through checked overlay handles when the main layout anchor is available instead of only attaching raw root children; toolbar settings, cloud browser, and other remaining retained dialog families still stay open under the same debt until they move to the same lifetime model. - 2026-06-16: `DEBT-0049` was narrowed again. `pp_assets_brush_package_tests` now names the legacy PPBR compatibility fixture set explicitly: `0.0`, `0.1`, `0.2`, `1.1`, `2.1`, and rejected `1.2`. The live parser still preserves the legacy tolerance until a stricter supported version matrix is proven safe. - 2026-06-15: `DEBT-0063` was narrowed again. Sidebar stroke/color/layer/grid popups, file/about/layer menu popups in `src/app_layout.cpp`, quick brush/color popup+ticker overlays in `src/legacy_quick_ui_services.cpp`, combo-box popup open/close in `src/node_combobox.cpp`, and picker outside-click teardown in `src/node_dialog_picker.cpp` now route through checked overlay handles or explicit handle-safe close callbacks instead of raw attach-and-destroy popup ownership; remaining popup families still stay open under the same debt until the last retained dialog/menu surfaces are converted. - 2026-06-15: `DEBT-0058`/`DEBT-0063` were narrowed again. App-owned About, Changelog, User Manual, New Document, Open, Browse, Save, Resize, Layer Rename, and PPBR Export dialogs now open and close through checked overlay handles in `src/app_dialogs.cpp`, and the corresponding retained dialog nodes no longer self-destroy their cancel/OK buttons through `bind_legacy_click_destroys_node(...)`; menu/layout-owned popup families still remain on separate retained cleanup paths. - 2026-06-15: `DEBT-0036` was narrowed again. `NodeStrokePreview` now drops the retained pass-sequence, mix-execution, final-composite-request, background capture, and preview-copy wrapper structs/functions in favor of direct retained helper dispatch plus renderer-facing preview copy helpers; the live preview path still owns concrete framebuffer, texture-binding, and draw execution around those helpers. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the retained pre-dispatch state capture through local commit helpers; the live path still owns concrete layer/action mutation until the retained commit adapter is fully split from `Canvas`. - 2026-06-14: `DEBT-0063` was narrowed again. Open/Browse delete-confirmation now uses handle-based open/close in `src/node_dialog_open.cpp` and `src/node_dialog_browse.cpp`, with `attach_legacy_overlay_node_to_root` fallback removed; migration still pending in remaining panel families. - 2026-06-14: `DEBT-0063` was narrowed again. `src/node_combobox.cpp` now uses overlay handle open/close (`open_legacy_overlay_node_with_handle` and `close_legacy_overlay_node`) for popup lifecycle, with the older attach/close overlay path removed in this file. - 2026-06-14: `DEBT-0063` was narrowed again. `src/node_panel_stroke.cpp` now routes the stroke preset/brush/dual-brush/pattern popup open-close flow through checked overlay handles and explicit partial-open cleanup; migration is still pending in remaining panel families. - 2026-06-16: `DEBT-0063` was narrowed again. `src/node_panel_stroke.cpp` now routes stroke popup close/reset through a shared overlay helper, and `src/node_popup_menu.cpp` now keeps the popup alive through selection dispatch with scoped ownership instead of raw `this` callbacks. - 2026-06-14: `DEBT-0063` was narrowed again. `src/node_panel_brush.cpp` now routes brush popup-menu open-close flow through checked overlay handles and handle-based close on selection. - 2026-06-14: `DEBT-0063` was narrowed again. `src/legacy_quick_ui_services.cpp` now converts quick brush/color popup lifecycle from raw attach+bind-destroy to checked-overlay open/close (`open_legacy_overlay_node_with_handle` and `close_legacy_overlay_node`) with explicit handle invalidation on closure. - 2026-06-14: `DEBT-0063` was narrowed again. `src/node_popup_menu.h/.cpp` now routes popup-menu close through checked overlay handles and removes direct `close_legacy_popup_overlay(*this)` usage. - 2026-06-14: `DEBT-0063` was narrowed again. `src/node_panel_layer.h/.cpp` now uses shared ownership for current-layer/m_layers tracking, guards stale-layer callback/mutation paths, and routes popup outside-click close through `close_legacy_popup_panel`; migration remains open in other panel/dialog families. - 2026-06-14: `DEBT-0063` was narrowed again. `src/node_dialog_picker.cpp` now inlines outside-click/autohide popup close through capture release, parent detach, and guarded `on_popup_close` dispatch instead of routing that path through `close_legacy_popup_panel(*this, on_popup_close)`. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::stroke_draw()` now routes the retained main-pass request assembly through helper APIs and keeps the live callsite focused on branch selection and pass dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::stroke_draw()` now routes the main-pass destination texture binding declaration through `Canvas::make_stroke_draw_main_pass_request`; `Canvas::stroke_draw()` now only assembles execution inputs and dispatches the retained main-pass callback chain. - 2026-06-14: `DEBT-0064` was narrowed again. Stroke-preview result copy now routes through `pp::paint_renderer::copy_stroke_preview_result_to_texture(...)` in `src/paint_renderer/compositor.h`, and the node-level copy wrapper, final-composite wrapper, and pass-sequence wrapper are gone. The compositor test-local `Texture2D::bind()` shim is also gone, while live preview still owns concrete texture binding and remaining OpenGL copy execution. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the retained request-dispatch invocation through `execute_canvas_stroke_commit_dispatch(...)`; the wrapper still owns the state capture and helper dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the retained sequence plan assembly through `make_canvas_stroke_commit_sequence_plan(...)`; the wrapper still owns the request dispatch and commit sequencing. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the retained request build and execution through `execute_canvas_stroke_commit_request(...)`; the wrapper still owns the sequence plan assembly and dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::draw_merge_final_plane_composite()` now routes the retained final-plane composite execution through `make_canvas_draw_merge_final_plane_composite_execution(...)`; the wrapper still owns request assembly and dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `execute_canvas_draw_merge_plane_dispatch()` now routes the per-plane framebuffer setup and unbind handling through `execute_canvas_draw_merge_plane_setup(...)` plus `execute_canvas_draw_merge_plane_final_composite(...)`; the per-plane wrapper still owns the plane-selection loop and branch dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `execute_canvas_draw_merge_plane_iteration()` now routes the per-plane dispatch wrapper through `execute_canvas_draw_merge_plane_dispatch(...)`; the plane loop still owns the plane-selection guard and helper dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `execute_canvas_draw_merge_branch_body()` now routes the branch dispatch object assembly through `make_canvas_draw_merge_branch_dispatch(...)`; the branch body still owns the selection guard and dispatch wiring. - 2026-06-15: `DEBT-0036` was narrowed again. `execute_canvas_draw_merge_branch_body()` now drops the unused `draw_checkerboard` flag from the branch helper chain, shrinking the helper API mismatch while `Canvas::draw_merge_branch_orchestration()` still owns the remaining branch orchestration. - 2026-06-13: `STR-016` completed in `b9ed78e1`. The per-layer composite block moved out of `Canvas::draw_merge()`; archival record of the completed extraction. - 2026-06-15: `DEBT-0036` was narrowed again. `Canvas::draw_merge_branch_orchestration()` now drops the unused `draw_checkerboard` parameter from the internal branch wrapper, and `Canvas::draw_merge_temporary_paint_branch()` now drops the unused `copy_blend_destination` parameter from the temporary-paint wrapper, but those changes are follow-on cleanup after `STR-016`, not blockers on the completed extraction. - 2026-06-15: `DEBT-0036` was narrowed again. The stale `make_canvas_draw_merge_branch_dispatch(...)` forward declaration in `src/canvas.cpp` now matches the 7-argument implementation, and the remaining `Canvas::draw_merge()` inline-default comment in `src/canvas.cpp` now matches the header's `SIXPLETTE(true)` default; both are historical cleanup notes after `STR-016`, not reasons the task stayed blocked. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::draw_merge_branch_orchestration()` now routes the temporary erase, temporary paint, texture, and blend dispatch bodies through retained helpers inside `execute_canvas_draw_merge_branch_body(...)`; the branch body still owns the selection guard and helper dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `execute_canvas_draw_merge_branch_body()` now routes the temporary erase, texture, and blend dispatch bodies through `make_canvas_draw_merge_temporary_erase_dispatch(...)`, `make_canvas_draw_merge_layer_texture_dispatch(...)`, and `make_canvas_draw_merge_layer_blend_dispatch(...)`; the branch body still owns the selection guard and execution dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::draw_merge_temporary_paint_branch()` now routes the retained temporary-paint request construction through `make_canvas_draw_merge_temporary_paint_request(...)`; the temporary-paint wrapper still owns the branch-selection guard and execution dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::draw_merge_branch_orchestration()` now routes the retained branch execution body through `execute_canvas_draw_merge_branch_body(...)`; the branch wrapper still owns the branch-selection guard and dispatch wiring. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::draw_merge()` now routes the final-composite gate through `execute_canvas_draw_merge_plane_final_composite(...)`; the plane-loop helper still owns the per-plane framebuffer setup and branch dispatch. - 2026-06-14: `DEBT-0036` was narrowed again. `Canvas::draw_merge()` now routes its plane iteration and branch orchestration through `execute_canvas_draw_merge_plane_iteration(...)`; the canvas path still owns the per-plane framebuffer setup and retained branch execution callbacks. - 2026-06-14: `DEBT-0036` was narrowed again. `execute_stroke_preview_final_composite_pass()` now routes the retained final-composite request construction through `make_stroke_preview_final_composite_request(...)`; the preview path still owns the concrete shader setup, sampler binding, input binding, and draw callback. - 2026-06-14: `DEBT-0036` was narrowed again. `NodeStrokePreview::stroke_draw_mix()` now routes the retained preview mix request construction through `make_stroke_preview_mix_execution_request(...)`; the preview path still owns the concrete mixer framebuffer, sample/input binding, and draw callback. - 2026-06-14: `DEBT-0036` was narrowed again. `execute_stroke_preview_sample_pass()` now routes the retained sample-request construction through `make_stroke_preview_sample_request(...)`; the preview path still owns the concrete destination binding, copy callback, and brush draw dispatch. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes the combined setup, request bundle, and shell execution through `make_legacy_canvas_stroke_mix_pass_shell(...)` plus `execute_legacy_canvas_stroke_mix_pass_shell(...)`; the live path still owns the concrete framebuffer bind and GL capability toggles. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes the combined setup and request bundle through `make_legacy_canvas_stroke_mix_pass_shell(...)`; the live path still owns the concrete framebuffer bind and GL capability toggles. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes the retained mix-pass shell through `execute_legacy_canvas_stroke_mix_pass_shell(...)`; the live path still owns the concrete framebuffer bind and GL capability toggles. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes the full retained mix-pass shell through `execute_legacy_canvas_stroke_mix_pass_with_setup(...)`; the live path still owns the concrete framebuffer bind and GL capability toggles. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes the setup/end shell through `make_legacy_canvas_stroke_mix_pass_setup(...)`; the live path still owns the concrete framebuffer bind and GL capability toggles. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes its retained mix-pass execution shell through `execute_legacy_canvas_stroke_mix_pass_with_setup(...)` with a concrete request builder; the live path still owns the OpenGL setup and per-plane texture wiring. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes retained mix-pass request wiring through `make_legacy_canvas_stroke_mix_pass_request(...)`; the legacy path still owns the concrete framebuffer setup and per-plane GL callbacks. - 2026-06-13: `PLT-004` was narrowed again. `LegacyPlatformServices::display_file()` and `share_file()` now delegate Apple file actions through `AppleDocumentPlatformServices`, leaving the legacy adapter with only the cross-platform dispatch shell. - 2026-06-13: `PLT-005` was narrowed again. Linux rendered-frame title updates now route through `src/platform_linux/linux_platform_services.*`; the legacy platform adapter still owns the surrounding cross-platform dispatch shell. - 2026-06-13: `PLT-006` was narrowed again. macOS cursor visibility now routes through `src/platform_apple/apple_platform_services.*`; the legacy platform adapter still owns the surrounding cross-platform dispatch shell. - 2026-06-13: `PLT-006` was narrowed again. macOS cursor visibility now routes through the Apple service boundary instead of the catch-all legacy adapter; the Apple path still owns the OS-specific cursor toggle. - 2026-06-13: `PLT-007` was narrowed again. macOS UI-state saving now routes through the Apple service boundary instead of the catch-all legacy adapter; the Apple path still owns the OS-specific UI-state save call. - 2026-06-13: `PLT-007` was narrowed again. macOS UI-state saving now routes through `src/platform_apple/apple_platform_services.*` instead of the catch-all legacy adapter; the Apple path still owns the OS-specific save call. - 2026-06-13: `PLT-008` was narrowed again. Apple clipboard get/set now routes through `src/platform_apple/apple_platform_services.*` instead of the catch-all legacy adapter; the Apple path still owns the OS-specific clipboard calls. - 2026-06-13: `PLT-008` was narrowed again. Apple clipboard get/set now routes through the Apple service boundary instead of the catch-all legacy adapter; the Apple path still owns the OS-specific clipboard calls. - 2026-06-13: `PLT-009` was narrowed again. Apple clipboard helper methods now route through `src/platform_apple/apple_platform_services.*` instead of the catch-all legacy adapter; the Apple path still owns the OS-specific clipboard calls. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now calls the retained mix-pass executor directly instead of a local wrapper shell helper; the live path still owns the concrete mixer framebuffer setup and GL capability toggles. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes the remaining mixer framebuffer/capability shell through a local helper, leaving the call site with only mix-pass planning and helper invocation. - 2026-06-13: `LATER-003` was narrowed again. The retained stroke mix shell now has direct executor regression coverage in `retained_stroke_mix_pass_shell_builder_preserves_combined_wiring`. - 2026-06-13: `LATER-003` was narrowed again. The retained stroke mix shell now also has isolated executor regression coverage in `retained_stroke_mix_pass_shell_executor_preserves_combined_wiring`. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` no longer computes an unused mix-plane plan in the live shell path. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now routes the remaining framebuffer setup callbacks through a local helper, leaving the method with only shell assembly and executor dispatch. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw_mix()` now keeps only retained shell assembly and executor dispatch at the callsite. - 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the large retained callback bundle through a local helper, leaving the callsite with sequence planning and helper invocation. - 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now keeps the commit callback bundle in a local helper, leaving the callsite with sequence planning and retained callback invocation only. - 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the commit-input texture role switch through a local helper, leaving the callsite with sequence planning plus retained callback wiring. - 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the commit-input texture role switch through a retained service helper, leaving the callsite with sequence planning and concrete face bindings only. - 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the face-indexed commit-input binding wrapper through the retained service boundary, leaving `Canvas` with only concrete face bindings. - 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the retained commit request assembly through `make_legacy_canvas_stroke_commit_request(...)`, leaving the callsite with only concrete faces, sequence, and callbacks. - 2026-06-13: `DEBT-0036` was narrowed again. `Canvas::stroke_commit()` now routes the retained sequence invocation through a local helper, leaving the callsite with only the built commit state. - 2026-06-13: `DEBT-0036` was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes final composite execution and preview copy-back through a retained local wrapper, leaving the call site with only sequence wiring. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw()` now routes dual-brush tip dispatch through retained helper overloads, so the face-index callback wiring is no longer built inline in the dual-pass branch. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw()` now routes pad destination texture dispatch through retained helper overloads, so the face-index callback wiring is no longer built inline in the pad branch. - 2026-06-13: `LATER-003` was narrowed again. `Canvas::stroke_draw()` now routes its main live-pass bind, execute, and unbind sequence through `execute_legacy_canvas_stroke_main_pass(...)`; the retained adapter still owns the concrete sampler and texture callbacks. - 2026-06-13: RND-005 was completed. `desktop-gpu` now has a second deterministic OpenGL readback fixture and the CTest registration uses the configured target path. - 2026-06-13: RND-006 was completed. `desktop-gpu` now has a compositor-named deterministic OpenGL readback fixture in `pp_renderer_gl_gpu_readback_tests`. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes final-composite setup and preview copy-back through retained helpers in `legacy_node_stroke_preview_execution_services.h`; the retained path still owns the concrete texture objects and pass-order wiring. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes main-pass texture binding through `bind_legacy_node_stroke_preview_main_pass_textures(...)`; the retained path still owns the concrete texture objects and mixer binding callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` no longer duplicates main-pass setup in the outer `prepare_main_pass` block; the retained main-live-pass helper now owns blend-uniform and texture binding setup, while the sequence wrapper keeps only structural pass ordering. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes the main live-pass setup, frame execution, and preview copy-back through `execute_legacy_node_stroke_preview_main_live_pass(...)`; the retained path still owns the concrete frame mutation, sample shader setup, and final mixer unbind. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes the main live-pass request assembly through `make_stroke_draw_immediate_main_live_pass_request(...)`; the retained path still owns the concrete frame mutation, sample shader setup, and final mixer unbind. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes the dual-pass live body through `execute_stroke_draw_immediate_dual_pass(...)`; the retained path still owns the concrete frame mutation, shader setup, and preview copy behavior. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes the pass-sequence request assembly through `make_stroke_draw_immediate_pass_sequence_request(...)`; the retained path still owns the concrete preview copy behavior and final-state restoration. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` now routes the remaining per-layer branch orchestration through `execute_legacy_canvas_draw_merge_layer_composite(...)`; the retained path still owns the concrete temporary-stroke, layer-texture, and layer-blend callback bodies. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_commit()` now routes the remaining service wiring through `make_canvas_stroke_commit_request(...)`; the retained path still owns the concrete history mutation and layer dirty box updates. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_commit()` now routes action bookkeeping and dirty-box capture/mutation through helper functions; the retained callback builder now only forwards concrete canvas state. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_commit()` now builds its retained callback table through `make_legacy_canvas_stroke_commit_callbacks(...)`; the legacy executor still owns ordering, history capture, and dilate sequencing, and the concrete Canvas state wiring remains open in the legacy path. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` now routes per-plane merge-target clear, blend-state gating, and optional checkerboard prepass through `execute_legacy_canvas_draw_merge_plane_setup(...)`; per-plane iteration, per-layer branch selection, and concrete framebuffer ownership remain in the legacy Canvas path. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` now routes the standard per-layer texture-alpha pass through `execute_legacy_canvas_draw_merge_layer_texture(...)`; per-plane iteration, temporary-stroke branch selection, and concrete layer RTT ownership remain in the legacy Canvas path. - 2026-06-14: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_mix()` now routes the mix-pass execution request through `make_stroke_draw_mix_execution_request(...)`; the retained path still owns the concrete framebuffer, viewport, and draw ordering. - 2026-06-14: DEBT-0036 was narrowed again. `NodeStrokePreview::make_stroke_draw_immediate_main_live_pass_request()` now routes sample shader setup through `execute_stroke_draw_immediate_main_live_sample_pass(...)`; the retained path still owns the concrete preview frame mutation and copy behavior. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` temporary composite setup now routes through `execute_canvas_draw_merge_temporary_composite(...)`; setup, sampler, texture, draw, and unbind callbacks still remain retained in the legacy Canvas path. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` temporary erase and paint branch wiring now routes through `execute_legacy_canvas_draw_merge_temporary_composite(...)`; the retained path still owns the concrete setup, sampler, texture, draw, and unbind callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` now routes the remaining temporary erase and paint callback bundle through `execute_legacy_canvas_draw_merge_temporary_composite(...)`; branch selection remains in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` temporary erase branch execution now routes through `make_legacy_canvas_draw_merge_temporary_erase_composite(...)`; the live path still owns the concrete layer RTT, mixer RTT, sampler, and plane callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` temporary paint branch execution now routes through `make_legacy_canvas_draw_merge_temporary_paint_composite(...)`; the live path still owns the concrete layer RTT, mixer RTT, sampler, and plane callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` now routes the layer-composite shell through a local wrapper around `execute_legacy_canvas_draw_merge_layer_composite(...)`; the final branch selection remains in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` now routes the layer texture, layer blend, and final-plane composite callbacks through local wrappers around the retained helpers; only branch selection glue remains in the legacy Canvas path. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` remaining branch orchestration now routes through `draw_merge_branch_orchestration(...)`; the retained path still owns the concrete per-branch framebuffer, sampler, and texture wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` final-plane composite now routes through `draw_merge_final_plane_composite(...)`; the retained path still owns the concrete merged-texture copy, checkerboard redraw, and final blend/draw wiring. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes retained preview feedback/material/composite planning plus stroke shader uniform assembly through `plan_legacy_node_stroke_preview_pass_orchestration(...)`; focused compositor coverage now locks the retained destination-feedback fallback, composite-slot intent, and pattern/dual shader-uniform handoff while brush mutation, retained `Stroke` population, and live GL callback execution remain in the preview node. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes retained preview stroke max-size fallback, dual-preview max-size derivation, pattern-scale flips, and Bezier preview-point generation through `plan_legacy_node_stroke_preview_stroke_setup(...)`; compositor coverage now also locks the retained stroke-setup curve/pressure intent and pass-sequence validation short-circuit behavior. Brush object mutation, camera wiring, retained `Stroke` population, and all live GL execution remain in the legacy preview node. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` per-layer blend composite now routes merge-RTT unbind, shader setup, optional destination-copy feedback, draw, and cleanup ordering through `execute_legacy_canvas_draw_merge_layer_blend(...)`; layer iteration, temporary-stroke branch selection, framebuffer copies, sampler/texture object callbacks, and final merged-plane ownership remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` erase live temporary-stroke composite now routes retained setup, sampler bind, texture bind, draw, and texture unbind ordering through `execute_legacy_canvas_stroke_temporary_composite(...)`; broader final composite ownership remains in the legacy Canvas path. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_mix()` now routes retained mix-pass shader setup plus framebuffer/state/input/draw ordering through `execute_legacy_node_stroke_preview_mix_pass(...)`; the preview node keeps only the concrete GL save/restore, texture-object bind, and plane-draw callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` live-pass sampler bind/unbind plus semantic texture-input dispatch now route through retained stroke execution helpers; concrete GL object mapping, framebuffer ownership, shader timing, and final draw execution remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` live-pass face callback orchestration now routes through `execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(...)`; the retained path still owns the concrete framebuffer array and per-face GL callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass face execution now routes through `execute_legacy_canvas_stroke_dual_pass_frame_callbacks(...)`; the retained path still owns the concrete dual-brush shader, sampler, and framebuffer wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass frame orchestration now routes through a retained helper; the retained path still owns the concrete shader setup, brush-tip dispatch, and frame callback execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass frame callback body now routes through `stroke_draw_dual_pass_frame_pass(...)`; the retained path still owns the concrete dual-pass request shell and frame execution wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass request assembly now routes through `make_stroke_draw_dual_pass_request(...)`; the retained path still owns the concrete request execution shell and frame callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass request assembly now routes through a retained helper object; the retained path still owns the concrete request shell and frame callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad-face orchestration now routes through `stroke_draw_pad_face_orchestration(...)`; the retained path still owns the concrete pad shader setup, destination dispatch, and callback wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad-face callback body now routes through `stroke_draw_pad_face_callback_body(...)`; the retained path still owns the concrete pad shader setup, face selection, and copy wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` temporary paint branch now routes through `draw_merge_temporary_paint_branch(...)`; the retained path still owns the concrete temporary-paint setup, texture binding, and draw ordering. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` remaining branch orchestration now routes through `draw_merge_branch_orchestration(...)`; the retained path still owns the concrete per-branch framebuffer, sampler, and texture wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` main-pass live face orchestration now routes through `execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(...)`; the retained path still owns the concrete shader, sampler, and framebuffer wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` main-pass face loop orchestration now routes through `execute_legacy_canvas_stroke_main_pass_frame_callbacks(...)`; the retained path still owns the concrete shader, sampler, and framebuffer wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad-face orchestration now routes through `execute_legacy_canvas_stroke_pad_face_callbacks(...)`; the retained path still owns the concrete brush shape, destination dispatch, and framebuffer wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad-face callback body now routes through `Canvas::stroke_draw_pad_pass(...)`; the retained path still owns the concrete pad shader setup and face callback execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` request assembly now routes through `make_stroke_draw_samples_request(...)`; the retained path still owns the concrete destination-copy dispatch and brush upload/draw wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` destination-texture dispatch now routes through `make_stroke_draw_samples_destination_texture_dispatch(...)`; the retained path still owns the concrete destination-copy callback wiring and request execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` now routes polygon triangulation, sample-point assembly, and retained destination-copy / upload / draw helper handoff through `execute_legacy_canvas_stroke_sample_polygon(...)`; direct GL callback wiring and remaining live draw ownership remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_mix()` now routes visible-plane filtering, retained sampler/texture-slot binding, and final plane draw ordering through `execute_legacy_canvas_stroke_mix_pass(...)`; mixer framebuffer/state setup and per-plane shader material/MVP preparation remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_samples()` now routes face-indexed destination bind/copy/unbind and brush upload/draw through `execute_legacy_canvas_stroke_face_sample_polygon(...)`; the retained sample executor owns the face-aware dispatch contract while `Canvas` keeps only concrete GL object callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge()` non-erase live temporary-stroke composite now routes setup, sampler bind, texture bind, draw, and texture unbind ordering through `execute_legacy_canvas_stroke_temporary_composite(...)`; erase-path and broader final composite ownership remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw_stroke_immediate()` now routes dual-pass/background/main-pass/final-composite/copy-back ordering through `execute_legacy_node_stroke_preview_pass_sequence(...)`; the remaining local preview ownership is concentrated around `stroke_draw_mix()` setup/execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_mix()` now routes mixer framebuffer bind/unbind, viewport/scissor/blend state, texture-slot binding, and final plane draw through one local helper; material planning and shader uniform setup remain retained in the preview node. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_samples` now routes destination bind/unbind, framebuffer copy callback wrapping, sample-point assembly, and brush-vertex upload/draw through one local helper; mixer-pass state execution and higher-level pass orchestration remain retained in the preview node. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` main, pad, and dual live-pass texture-input binding/unbinding intent now routes through shared retained stroke execution helpers; sampler binding, concrete GL object mapping, framebuffer ownership, and final draw execution remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` live-pass sampler binding, dual/main pass texture binding, checkerboard/background capture wrapping, and final preview copy-back now route through shared local helpers; mixer state execution and per-sample GL ordering remain retained in the preview node. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` dual-pass and main-pass frame-loop execution plus full-frame copy-back now route through shared local helpers; checkerboard/background capture ordering, texture-unit binding, mixer ownership, and final composite semantics remain retained in the preview node. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` main and dual live-pass per-face framebuffer begin/end execution plus pad-face array assembly now route through `legacy_canvas_stroke_execution_services.h`; shader activation timing, texture/sampler binding, framebuffer ownership, pad execution, and final OpenGL draw remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` main and dual live-pass dirty semantics now route through `execute_legacy_canvas_stroke_live_pass_with_dirty_tracking(...)` in `legacy_canvas_stroke_execution_services.h`; shader timing, sampler/texture binding, framebuffer ownership, pad execution, and final OpenGL draw remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` final composite sampler/input binding and slot intent now route through one local preview helper; mixer execution, per-sample stroke callbacks, framebuffer copies, and final OpenGL draw ownership remain retained in the preview node. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now routes through `legacy_canvas_stroke_execution_services.h`; shader setup, pad color selection, framebuffer ownership, and final OpenGL draw remain retained in `Canvas`. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and dual live sample frame/face traversal plus dirty tracking now route through `legacy_canvas_stroke_execution_services.h`; shader timing, sampler/texture binding, framebuffer ownership, pad execution, and final OpenGL draw remain retained in `Canvas`. - 2026-06-13: DEBT-0036 preview-adapter hardening grew again. `pp_paint_renderer_compositor_tests` now covers `legacy_node_stroke_preview_execution_services.h` framebuffer-feedback fallback clamping plus retained preview composite slot intent, while live preview sample execution, mixer passes, texture binding, and final draw ordering remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_compute` now routes preview quad frame planning through `legacy_canvas_stroke_execution_services.h`. Preview stroke sampling, mixer execution, texture binding, and final OpenGL draw ordering remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and dual stroke per-face framebuffer/sample callback ordering now routes through the retained stroke execution helper; framebuffer ownership, shader uniform timing, sampler/texture binding, and live draw execution remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and dual stroke dirty-box mutation now routes through the retained stroke execution helper; framebuffer binding, shader uniform timing, sampler/texture binding, and live draw execution remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_commit` retained commit input texture/sampler binding, erase/composite draw dispatch, committed-copy-to-dilate-source, and dilate draw now route through `legacy_canvas_stroke_commit_services.h`; Canvas still owns history readback, `ActionStroke` population, layer dirty-box mutation, and retained RTT/framebuffer ownership. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` pad-pass dirty-face iteration, pad-region planning, and NDC quad assembly now route through a retained stroke execution helper callback boundary; Canvas still owns framebuffer copies, brush-shape uploads, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` current and dual stroke frame-face traversal now routes through the retained stroke execution helper; framebuffer binding, shader uniform timing, dirty-box mutation, sampler/texture binding, and live draw execution remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_compute` frame planning now routes brush-quad construction, mixer feedback bounds, 2D/3D projection selection intent, and frame assembly through the retained stroke execution helper; legacy projection geometry, stroke samples, and live draw execution remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` pad-region planning now routes through the retained stroke execution helper wrapping `pp_paint_renderer`; pad color selection, dirty-face iteration, framebuffer copies, quad upload, and draw execution remain retained. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw` face dirty-box planning now routes through a retained stroke execution helper wrapping `pp_paint_renderer`, retained stroke commit step dispatch clamps malformed step counts to the fixed plan array, and compositor coverage now exercises malformed retained commit plans plus all-input stroke-preview composite planning. Live stroke rasterization, callback execution, texture binding, and history mutation remain retained. - 2026-06-13: DEBT-0036 was narrowed again. Remaining live shader setup outside retained helper headers now routes through retained helper surfaces for canvas modes, equirect layer export, `NodeCanvas` debug dirty bounds, atlas image drawing, and text drawing; those paths still own geometry, framebuffer flow, texture/sampler binding, blend/depth state, readback, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. Remaining simple color, hue, color-quad, grid heightmap, and pen/line preview shader setup in UI nodes and canvas modes now routes through retained helper surfaces; those paths still own geometry, texture/sampler binding, blend/depth state, readback, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. Scrollbar, text-input, and VR frame/cursor/controller shader setup now routes through retained UI/preview helpers; those paths still own geometry, blend/depth state, sampler/texture binding, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. Depth export `TextureColorize` shader setup now routes through `legacy_canvas_draw_merge_services.h`; the export path still owns sampler/texture binding, layer color selection, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeCanvas` smoothing-mask `TextureMask` setup now routes through `legacy_canvas_draw_merge_services.h`; the node still owns smoothing-mask texture binding, per-face MVP updates, blend state, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. Slider `Color` and `ColorHue` shader setup now routes through `legacy_ui_overlay_services.h`; the slider nodes still own geometry, hue direction, border drawing, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. Retained `TextureAlpha` shader setup now routes through `legacy_canvas_draw_merge_services.h` for Canvas draw-merge layer redraws, depth export merged-layer rendering, NodeCanvas cached/live layer redraws, and desktop VR layer redraws; those paths still own sampler/texture binding, render-task ordering, per-frame alpha updates, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeCanvas` density-resolve and desktop VR UI `Texture` shader setup now route through `legacy_canvas_draw_merge_services.h`; those paths still own render target, sampler/texture binding, viewport/state restoration, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `CanvasModeTransform::on_Draw` and `CanvasModeFloodFill::on_Draw` retained `Texture` shader setup now route through `legacy_canvas_draw_merge_services.h`; the mode paths still own active texture selection, sampler/texture binding, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::draw` retained preview `Texture` shader setup now routes through `legacy_canvas_draw_merge_services.h`; the node still owns preview texture binding, sampler binding, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeViewport` retained preview draw and `NodePanelGrid` sun-overlay `Texture` shader setup now route through `legacy_canvas_draw_merge_services.h`; the node paths still own texture/sampler binding, viewport/state setup, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::import_layer` retained object-draw `Texture` shader setup now routes through `legacy_canvas_draw_merge_services.h`; import_layer still owns texture upload, sampler/texture binding, draw callbacks, and object geometry. - 2026-06-13: DEBT-0036 was narrowed again. `NodeImage` and `NodeImageTexture` retained plain image `Texture` shader setup now routes through `legacy_canvas_draw_merge_services.h`; the nodes still own texture/sampler binding, atlas setup, blend state, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::thumbnail_generate` checkerboard and final `Texture` shader setup now route through `legacy_canvas_draw_merge_services.h`; thumbnail generation still owns destination copies, sampler/texture binding, readback, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_objects` retained object composite `Texture` shader setup now routes through `legacy_canvas_draw_merge_services.h`; draw_objects still owns temporary face readback, sampler/texture binding, draw execution, and dirty-box updates. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::thumbnail_generate` thumbnail layer `TextureBlend` shader setup now routes through `legacy_canvas_draw_merge_services.h`; thumbnail generation still owns destination feedback copies, sampler/texture binding, grid redraw, readback, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. Desktop VR checkerboard background shader setup now routes through `legacy_canvas_draw_merge_services.h`; VR still owns render state transitions, per-plane transforms, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `CanvasModeMaskCut::apply` retained `CompDraw` shader setup now routes through `legacy_canvas_stroke_composite_services.h`; mask-cut still owns render-task ordering, framebuffer copy bounds, sampler/texture binding, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. Desktop VR retained temporary erase and draw shader setup now route through the shared stroke erase/composite helpers; VR still owns sampler/texture binding, per-eye/view transforms, temporary stroke texture selection, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_commit` and `Canvas::draw_merge` retained `CompErase` shader setup now route through `legacy_canvas_stroke_erase_services.h`; Canvas still owns texture binding, mask RTT binding, dirty/layer mutation, framebuffer feedback, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeCanvas` live temporary-erase `CompErase` shader setup now routes through `legacy_canvas_stroke_erase_services.h`; the retained node path still owns per-onion-frame alpha updates, texture binding, temporary erase texture selection, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeCanvas` live temporary-stroke `CompDraw` shader setup now routes through `legacy_canvas_stroke_composite_services.h`; the retained node path still owns per-onion-frame alpha updates, texture binding, temporary stroke texture selection, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` preview background checkerboard shader setup now routes through `legacy_canvas_draw_merge_services.h`, preserving the existing colorize condition and preview MVP. The retained preview path still owns background capture ordering, texture copies, stroke setup, and draw execution. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge` non-stroke `TextureBlend` shader setup now routes through `legacy_canvas_draw_merge_services.h`, preserving the optional `TexBG` uniform only when copy-based blend destination feedback is active. Canvas still owns draw-merge layer iteration, framebuffer copies, sampler/texture binding, and draw ordering. - 2026-06-13: DEBT-0036 was narrowed again. `NodeCanvas` non-stroke `TextureBlend` shader setup now routes through `legacy_canvas_draw_merge_services.h`, including the optional destination texture uniform only when blend destination copies are used. NodeCanvas still owns panorama layer traversal, onion-frame drawing, blend destination copies, sampler/texture binding, framebuffer lifetime, and draw ordering. - 2026-06-13: DEBT-0036 was narrowed again. `NodeCanvas` now reuses `legacy_canvas_draw_merge_services.h` for retained checkerboard grid shader setup and the final cached-layer texture redraw setup. NodeCanvas still owns panorama layer traversal, onion-frame drawing, blend destination copies, sampler/texture binding, and draw ordering. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_mix` now reuses `legacy_canvas_stroke_composite_services.h` for mixer-pass retained `kShader::CompDraw` binding and blend uniform writes, with the helper now preserving caller-specific texture slot uniforms. Mixer framebuffer, viewport/scissor/capability state, sampler binding, texture binding, and draw execution remain retained Canvas code. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_mix()` now routes retained mix-shell execution through a local wrapper around `execute_legacy_canvas_stroke_mix_pass_shell(...)`; the live path still owns the concrete mixer framebuffer setup and GL capability toggles. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw_mix()` now has regression coverage for retained mixer-state callback ordering through `execute_legacy_canvas_stroke_mix_pass(...)`; the live path still owns the concrete mixer framebuffer setup and GL capability toggles. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` main-pass texture dispatch now has regression coverage through `make_legacy_canvas_stroke_main_pass_texture_dispatch(...)`; the live path still owns the concrete texture objects and sampler state. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` live-pass sampler dispatch now has regression coverage through `make_legacy_canvas_stroke_live_pass_sampler_dispatch(...)`; the live path still owns the concrete sampler objects and binding state. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass brush-tip dispatch now uses a retained helper object, with regression coverage proving the helper order; the live path still owns the concrete brush-tip texture object and dual-pass branch wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad copy behavior now uses a retained helper for copy-region wiring; the pad branch still owns the concrete framebuffer and texture-object callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` dual-pass shader setup now uses a retained wrapper helper; the dual-pass branch still owns the concrete shader selection and framebuffer wiring. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_draw()` pad destination dispatch now reuses a retained helper object, with regression coverage proving the helper order; the pad branch still owns the concrete texture-object wiring and copy timing. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge` checkerboard background shader setup and final merged-texture redraw setup now route through `legacy_canvas_draw_merge_services.h`. The retained Canvas path still owns draw-merge layer iteration, blend-gate branching, framebuffer copies, sampler/texture binding, and draw ordering. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::draw_merge` end-of-plane merged-texture copy, optional checkerboard redraw, and final merged-texture composite ordering now route through `execute_legacy_canvas_draw_merge_final_plane_composite(...)`; the retained Canvas path still owns per-plane iteration plus the concrete framebuffer, sampler, texture, and draw callbacks. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::layer_merge` now reuses `legacy_canvas_stroke_composite_services.h` for retained layer-merge `kShader::CompDraw` binding and source/destination blend uniform writes. Render-task ordering, dirty face/box mutation, framebuffer copies, sampler binding, texture binding, and draw execution remain in the retained Canvas layer-merge path. - 2026-06-13: DEBT-0036 was narrowed again. `Canvas::stroke_commit` now routes its retained per-face commit order through `execute_legacy_canvas_stroke_commit_sequence`, consuming the tested `CanvasStrokeCommitSequencePlan` while keeping history `ActionStroke` mutation, layer dirty state, RTT/framebuffer binding, shader execution, and sampler/texture binding inside Canvas callbacks. The adapter remains retained until stroke commit execution is owned by the renderer backend implementation. - 2026-06-13: DEBT-0036 was narrowed again. `pp_paint_renderer` now owns a tested `CanvasStrokeCommitSequencePlan` for `Canvas::stroke_commit` readback, dirty-state, scratch-copy, erase/composite draw, committed-copy, dilate order, and commit texture slot roles. A retained `legacy_canvas_stroke_commit_services.h` adapter skeleton consumes the semantic plan through callbacks, but the live Canvas commit body still owns history/layer mutation, RTT/framebuffer binding, sampler binding, and final OpenGL execution until the adapter is wired. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_mix` now reuses `legacy_canvas_stroke_composite_services.h` for mixer-pass `kShader::CompDraw` binding and composite/pattern/dual uniform writes. Mixer framebuffer, scissor/capability state, texture binding, and draw/copy ordering remain retained legacy execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview::stroke_draw_mix` now routes retained mix-pass material planning, pattern scale/offset derivation, and composite/pattern/dual uniform payload assembly through `plan_legacy_node_stroke_preview_mix_pass(...)`, with semantic adapter coverage in `pp_paint_renderer_compositor_tests`. Mixer framebuffer ownership, retained shader execution, texture binding, and final draw/copy ordering remain local legacy preview execution. - 2026-06-13: DEBT-0036 was narrowed again. `NodeStrokePreview` final preview background capture, composite input binding/draw, and preview texture copy now route through `legacy_canvas_stroke_preview_services.h`, with semantic preview composite ordering and texture-slot intent covered by `pp_paint_renderer` tests. Static preview RTT/texture ownership, checkerboard shader setup, framebuffer copies, and retained GL callbacks remain in legacy preview code. - 2026-06-13: DEBT-0036 was narrowed again. Stroke sample copy bounds, live face dirty-box accumulation, and preview padding region math now live as tested `pp_paint_renderer` planners and are consumed by retained Canvas and stroke sample execution adapters. Canvas/preview still own GL ordering, RTT/texture binding, history mutation, and final dirty state storage. - 2026-06-12: DEBT-0036 was narrowed again. `Canvas::stroke_commit` now reuses `legacy_canvas_stroke_composite_services.h` for commit-time final stroke `kShader::CompDraw` binding and composite/pattern/dual uniform writes. Texture/framebuffer binding, copy ordering, layer dirty mutation, and non-stroke composite callers remain retained legacy execution. - 2026-06-12: DEBT-0036 was narrowed again. Canvas stroke padding and commit dilate shader setup now route retained `kShader::StrokePad` and `kShader::StrokeDilate` binding/uniform writes through `legacy_canvas_stroke_edge_services.h`. Quad expansion, dirty-box policy, texture/framebuffer binding and copies, and draw ordering remain retained legacy execution. - 2026-06-12: DEBT-0036 was narrowed again. Canvas live draw-merge and `NodeStrokePreview` final stroke composite `kShader::CompDraw` setup now share `legacy_canvas_stroke_composite_services.h` for retained shader binding and composite/pattern/dual uniform writes. Texture/RTT binding, samplers, draw calls, commit-time compositing, and non-stroke `CompDraw` callers remain retained legacy execution. - 2026-06-12: DEBT-0036 was narrowed again. Canvas and `NodeStrokePreview` live stroke passes now route retained `kShader::Stroke` binding plus setup, blend, pattern, and per-sample uniform writes through `legacy_canvas_stroke_shader_services.h`. RTT/texture ownership, dirty-box policy, composite/pad/checkerboard shaders, and retained callback execution remain in the legacy callers until the stroke backend is owned by renderer services. - 2026-06-12: DEBT-0036 was narrowed again. Canvas and `NodeStrokePreview` stroke sample execution now delegate optional destination copy, brush vertex upload, brush-shape draw, and destination unbind through `execute_legacy_canvas_stroke_sample`. Shader setup, RTT/texture ownership, dirty-box policy, and retained callback execution remain in the legacy callers until a renderer-owned stroke backend replaces the adapter. - 2026-06-12: DEBT-0036 was narrowed again. `NodeStrokePreview` now consumes the same `CanvasStrokeMaterialPlan` boundary for preview dual-brush, each-sample pattern, and composite material decisions. Preview GL draw calls, texture binding details, and pattern scale/offset uniforms remain retained. - 2026-06-12: DEBT-0036 was narrowed again. Live `Canvas::stroke_draw`, stroke commit, and draw-merge paths now consume `CanvasStrokeMaterialPlan` through `plan_legacy_canvas_stroke_material` for destination feedback, each-sample pattern, dual-brush, and final composite material decisions. Retained OpenGL draw calls and `NodeStrokePreview` still duplicate execution and preview material wiring. - 2026-06-12: DEBT-0036 was narrowed again. Live `Canvas::stroke_draw` destination feedback now consumes a named `CanvasStrokeRasterizationPlan` through `plan_legacy_canvas_stroke_rasterization`, and `pp_paint_renderer` owns pure material/pass planning for stroke pattern, mixer, dual-brush, and final composite texture/uniform intent. Retained OpenGL stroke execution still lives in `Canvas`, but feedback/material decisions now have tested renderer-facing plan boundaries. - 2026-06-12: DEBT-0036 was narrowed again. The opt-in `desktop-gpu` preset now owns a real OpenGL readback golden gate through `pp_renderer_gl_gpu_readback_tests`, validating a deterministic 1x1 clear and `glReadPixels` result against exact RGBA bytes. The first context helper is Windows/WGL-only and skips clearly on platforms without a helper; macOS and Linux GPU context helpers plus broader golden coverage remain open. - 2026-06-12: DEBT-0060 was closed. Retained Android standard/Quest/Focus package CMake files no longer generate or prepend a patched `nanort.h` overlay. Android package configure now applies the tracked nanort source compatibility patch, Android/GLES runtime dispatch avoids desktop debug callback symbols, and the retained package source lists include the extracted overlay/preference adapters needed by recent app-service boundaries. - 2026-06-12: DEBT-0058 and DEBT-0063 were narrowed. App-level progress/message/input dialogs now route through a pure `pp::app::AppDialogFactory`; retained `NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` construction and root attachment are centralized behind the legacy dialog/overlay factory layer. - 2026-06-12: DEBT-0017, DEBT-0051, and DEBT-0055 were narrowed. Apple document browse roots, file/image/save/directory picker dispatch, macOS empty-selection filtering, working-directory picker policy, and display-path formatting now live in `src/platform_apple/apple_platform_services.*`; the retained legacy platform adapter delegates Apple document-platform calls to that boundary while `App` still stores Apple platform handles directly. - 2026-06-12: DEBT-0010, DEBT-0036, and DEBT-0043 were narrowed. Payload-complete depth export now generates deterministic image/depth PNG payloads through pure `pp_paint_renderer` and writes them through the app-core two-payload writer before retained fallback. Retained OpenGL depth rendering/readback is now fallback-only for unsupported targets, incomplete readback, or writer failure; the pure path currently uses the extracted fixed perspective export view rather than captured live legacy camera state. - 2026-06-12: DEBT-0062 was closed. The generated VS 2026 fmt overlay was removed from root CMake, reused build trees delete the stale `compat/fmt-vs2026` directory, and VS 2026 retained fmt sources now use a generated forced-include compatibility header that includes the STL configuration once and undefines `_SECURE_SCL` before fmt selects its removed checked-array-iterator branch. - 2026-06-12: DEBT-0039, DEBT-0040, and DEBT-0042 were narrowed. Document-open, close, save, save-before-workflow, Save As, Save Version, and new-document history effects now surface as explicit `pp_app_core` history outputs; the live document-open/session bridges no longer include `legacy_history_services.h`. Retained project open/save, prompt callbacks, metadata mutation, and concrete history execution remain in legacy adapters. - 2026-06-12: DEBT-0050, DEBT-0053, and DEBT-0057 were narrowed. WebGL exported-image publishing, persistent-storage flushing, prepared-file handoff, and default canvas resolution now dispatch through injectable `pp::platform::WebPlatformServices` in `pp_platform_api`; the retained Web fallback implementation still lives in `src/platform_legacy/legacy_platform_services.cpp` until a dedicated Web platform shell injects the service directly. - 2026-06-12: DEBT-0021 was narrowed. Layer rename execution now exposes app-core history intent explicitly, records undo before applying the new layer name, and has focused call-order coverage. Layer operation and merge plans also expose app-core history-intent helpers, while retained `NodePanelLayer`/`Canvas` execution still owns the concrete `ActionManager` entries for add/remove/property-change/clear paths. - 2026-06-12: DEBT-0020 was narrowed. The shared document resize/canvas-clear live bridge no longer includes `legacy_history_services.h`; resize history clearing is now an explicit `DocumentResizeServices::clear_history()` app-core execution output implemented directly by the retained `ActionManager`, while canvas clear continues to record `ActionLayerClear` undo and mark the canvas unsaved inside the retained canvas operation. Legacy `Canvas::resize`, `Canvas::clear`, title updates, and direct `ActionManager` ownership remain open. - 2026-06-04: DEBT-0009 was narrowed. `platform-build.ps1` and `platform-build.sh` now include the current headless component/test matrix, including brush-package coverage and the app-core startup/frame/shutdown/file/document/ brush/canvas/history/grid/toolbar/tools/about/preferences/status automation tests, and `panopainter_platform_build_target_matrix_self_test` now verifies the wrapper defaults against the current CMake test executables. On 2026-06-05 the default platform-build preset set was widened to Android standard arm64/x64, Quest arm64, and Focus/Wave arm64, with the shell wrapper gaining multi-preset output parity with PowerShell, so Android root CMake validation no longer silently skips newly extracted feature slices or named Android variants. Package targets remain open under DEBT-0009 and DEBT-0011. - 2026-06-05: DEBT-0011 was narrowed. `package-smoke.ps1` and `package-smoke.sh` now have readiness-only modes that report the same Windows AppX, Android standard/Quest/Focus APK, Apple bundle, Linux app, and WebGL blocker matrix without requiring an app build first, and `panopainter_package_smoke_readiness_self_test` guards package-kind parity across both wrappers. Package target migration remains open. - 2026-06-05: DEBT-0004 was narrowed for Apple compile coverage. The Mac mini build host `panopainter-mac` now pulls from Gitea over SSH, uses Homebrew CMake/Ninja/Git plus full Xcode through `DEVELOPER_DIR`, and `scripts/automation/apple-remote-build.ps1` drives the `macos`, `ios-simulator`, and `ios-device` root CMake headless component matrix. Apple app bundle/signing/package migration remains open under DEBT-0011 and DEBT-0059. - 2026-06-05: DEBT-0022 was narrowed. `pp_app_core` now owns tested onion-skin frame range and alpha falloff planning, and live `NodeCanvas` panorama drawing consumes that helper instead of open-coding frame clamping and opacity falloff in the render loop. Later on 2026-06-05, animation timeline mouse scrubbing also moved to tested `pp_app_core` planning with `pano_cli plan-animation-timeline-scrub` coverage, so `NodeAnimationTimeline` no longer owns cursor-to-frame clamp policy. Animation panel layer/frame view projection now also uses a tested `pp_app_core` view model exposed by `pano_cli plan-animation-panel-view`, including stale-selection behavior. Legacy canvas/layer/UI execution remains open under DEBT-0022. - 2026-06-05: DEBT-0047 was narrowed. PPBR export success-dialog metadata now lives in tested `pp_app_core`, is exposed through `pano_cli plan-brush-package-export`, and is consumed by `src/legacy_brush_package_export_services.*`. Retained `NodeDialogExportPPBR` reads, legacy `Image` header ownership, `PPBRInfo` conversion, `NodePanelBrushPreset::export_ppbr`, desktop worker threading, dialog lifetime, mobile/Web completion, and PPBR serialization remain open. - 2026-06-05: DEBT-0021 was narrowed again. Layer panel selected-control and visibility view projection now goes through tested `pp_app_core` planning, `NodePanelLayer::update_attributes()` consumes that view model in the live app, and `pano_cli plan-layer-panel-view` exposes the same path for automation. Legacy layer mutation, UI node ownership, and undo wiring remain open under DEBT-0021. - 2026-06-05: DEBT-0023 was narrowed again. Stroke-panel control projection now goes through tested `pp_app_core` planning, `NodePanelStroke::update_controls()` consumes that view model in the live app, and `pano_cli plan-brush-stroke-panel-view` exposes the same state path for automation. Legacy brush mutation, brush thumbnail ownership, popup behavior, and preset child-node mutation remain open under DEBT-0023. - 2026-06-05: DEBT-0023 was narrowed again. App-level brush refresh projection now goes through tested `pp_app_core` planning, `App::brush_update()` consumes that view model in the live app, and `pano_cli plan-brush-refresh` exposes the same fan-out path for automation. Retained legacy quick/stroke/color widget writes plus `Brush`/`Canvas::I` ownership remain open under DEBT-0023. - 2026-06-05: DEBT-0025 was narrowed. Quick size/flow slider preview planning now goes through tested `pp_app_core`, live `NodePanelQuick` slider callbacks consume that plan for cursor placement and pen/line tip flags, and `pano_cli plan-quick-slider-preview` exposes the path for automation. Legacy quick widgets, brush previews, popup state, and direct `CanvasMode*` field writes remain open under DEBT-0025. - 2026-06-05: DEBT-0027 was narrowed. Canvas cursor visibility policy now goes through tested `pp_app_core`, live `NodeCanvas::update_cursor()` consumes the planner before retained platform cursor dispatch, and `pano_cli plan-canvas-cursor` exposes draw/erase versus non-paint mode, small-brush, not-painting, modifier, and malformed-brush states for automation. Legacy `Canvas`/`CanvasModePen` state reads and app cursor execution remain open under DEBT-0027. - 2026-06-05: DEBT-0027 was narrowed again. The full draw-toolbar binding set now goes through tested `pp_app_core` planning, live `App::init_toolbar_draw()` consumes the same plan for button handler wiring and default draw-mode initialization, and `pano_cli plan-canvas-tool-toolbar` exposes the binding ids, actions, button-class expectation, and default selection for automation. Retained `NodeButton`/`NodeButtonCustom` lookup, legacy `Canvas` mode mutation, picking/touch-lock state, and transform action execution remain open under DEBT-0027. - 2026-06-05: DEBT-0033 was narrowed again. Canvas reset-camera defaults, viewport density, and cursor mode now go through tested `pp_app_core` plans and shared `src/legacy_canvas_view_services.*` execution. Live Tools reset-camera, document open/new-document reset, cloud download reset, and options viewport/cursor callbacks consume that bridge, while `pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`, and `pano_cli plan-canvas-view-cursor-mode` expose the paths for automation. This also narrows DEBT-0045 for viewport-density and cursor-mode preference execution, though preference persistence remains retained in the legacy canvas-view bridge. - 2026-06-05: DEBT-0046 was narrowed. Main startup shader, asset, layout, title, and UI render-target sequencing now goes through tested `pp_app_core` resource plans and `src/legacy_app_startup_services.*`; `App::init` keeps the retained OpenGL startup task in place, then delegates startup resources and runtime side effects through the startup bridge. `pano_cli plan-app-startup-resources` exposes the resource path for automation. - 2026-06-05: DEBT-0040/0041/0042 were narrowed. Close-unsaved, save-before-workflow, new-document overwrite, Save As overwrite, and save-error prompt metadata now comes from a tested pure document-session prompt catalog consumed by `src/legacy_document_session_services.*`, and `pano_cli plan-document-session-prompt` exposes the titles, messages, captions, and cancel visibility for automation. Retained `NodeMessageBox` creation, callback wiring, project-save execution, and canvas/document mutation remain open under the same debts. - 2026-06-05: DEBT-0040/0041/0042 were narrowed again. Close-unsaved, save-before-workflow, new-document overwrite, and Save As overwrite prompt creation now uses `src/legacy_app_dialog_services.*`; retained document-session callbacks, save dialogs, canvas/project mutations, keyboard cleanup, and dialog lifetime still remain open under the same debts. - 2026-06-05: DEBT-0003 was narrowed. Initial surface sizing, redraw/animation update gating, layout tick selection, resize render-target recreation, canvas-stroke draw eligibility, VR UI pass selection, main UI pass selection, UI observer clipping/on-screen transition/scissor projection, and redraw reset are now tested `pp_app_core` frame plans consumed by `App::create`, `App::tick`, `App::resize`, `App::update`, `App::draw`, `App::update_ui_observer`, and `pano_cli plan-app-frame`; retained layout traversal, toolbar widget writes, render-target recreation, `Node` parent walking, on-screen callback execution, and OpenGL/UI drawing remain in the legacy app. - 2026-06-05: DEBT-0003 was narrowed. Pointer coordinate normalization, mouse designer-first routing, gesture midpoint/delta math, touch/key main-layout routing, VR spacebar camera-sync intent, UI visibility toggling, and stylus touch-lock attachment are now tested `pp_app_core` input plans consumed by `App::mouse_*`, `App::gesture_*`, `App::touch_tap`, `App::key_*`, `App::toggle_ui`, `App::set_stylus`, and `pano_cli plan-app-input`; retained `MouseEvent`/`GestureEvent`/`TouchEvent`/`KeyEvent` construction, UI child-node mutation, and legacy `Node` event dispatch remain in the app shell. - 2026-06-05: DEBT-0003 was narrowed. Render/UI task dispatch, unique queued task replacement, async redraw notification, queue draining, render-context wrapping, UI tick redraw scheduling, UI-loop frame/FPS/live-reload timer cadence, and thread start/stop intents are now tested `pp_app_core` plans consumed by `App::render_task*`, `App::ui_task*`, `App::async_redraw`, `App::render_thread_*`, `App::ui_thread_*`, and `pano_cli plan-app-thread`; retained `std::thread`, condition-variable, OpenGL context, live asset reload, and task execution remain in the app shell. - 2026-06-05: DEBT-0003 was narrowed again. Shutdown cleanup staging for UI-state save, stroke-preview renderer shutdown, recording stop, texture/shader invalidation, layout unload, UI render-target and face-plane destruction, panel-node release, and quick-mode cleanup now goes through tested `pp_app_core` plans consumed by `App::terminate` and `pano_cli plan-app-shutdown`; retained cleanup execution remains in the legacy app. - 2026-06-05: DEBT-0003 was narrowed again. Command-line panorama conversion sequencing for renderer-state setup, temporary canvas allocation, project open, and equirectangular export now goes through tested `pp_app_core` planning consumed by `App::cmd_convert` and `pano_cli plan-command-convert`; retained OpenGL state dispatch and legacy `Canvas` project open/export execution remain in the legacy app. - 2026-06-05: DEBT-0003 was narrowed again. Recording worker encode-wake eligibility now goes through tested `pp_app_core` planning consumed by `App::rec_loop` and `pano_cli plan-recording-session`; retained PBO equirect generation, dirty-stroke mutation, MP4 encoder calls, and frame label rendering remain in the legacy app/canvas/video path. - 2026-06-05: DEBT-0037 was narrowed. MP4 recording export progress-dialog metadata now lives in tested `pp_app_core` planning, is exposed by `pano_cli plan-recording-session`, and retained progress bar creation now routes through `src/legacy_app_dialog_services.*`. Recording thread lifecycle, frame readback scheduling, progress lifetime, and MP4 writing remain open. - 2026-06-05: DEBT-0038 was narrowed. Cloud transfer request/progress policy now lives in tested `pp_app_core` planning, live `App::download` and `App::upload` consume those plans before retained CURL setup, and `pano_cli plan-cloud-transfer` exposes missing endpoint, TLS policy, progress-callback, and zero/overrun progress cases for automation. CURL ownership, response/error handling, progress UI, cloud dialog/document execution, and injected network service work remain open under DEBT-0038. - 2026-06-05: DEBT-0038 was narrowed again. Cloud upload warning/publish/ success prompts, upload/bulk progress dialog titles, download-progress prompt metadata, and formatted download progress text now live in tested `pp_app_core` plans consumed by `src/legacy_cloud_services.*`; existing `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, and `pano_cli plan-cloud-transfer` expose that metadata for automation. Retained `NodeMessageBox`/progress-bar creation, save-before-upload threading, CURL ownership, response/error handling, and downloaded-project execution remain open under DEBT-0038. - 2026-06-05: DEBT-0038 was narrowed again. Cloud download progress prompt creation now uses `src/legacy_app_dialog_services.*`, matching the upload warning/publish/success and bulk progress paths that already route through `App::message_box`/`App::show_progress`. Retained cloud prompt/progress lifetime, save-before-upload threading, CURL ownership, response/error handling, and downloaded-project execution remain open. - 2026-06-05: DEBT-0035 was narrowed. Main-toolbar test-message dialog metadata now lives in tested `pp_app_core` planning, is exposed by `pano_cli plan-main-toolbar --command message-box`, and retained live message-box creation now routes through `src/legacy_app_dialog_services.*`. Open/save/settings routing, raw compatibility pointers, history/canvas adapter dispatch, and settings dialog execution remain open. - 2026-06-05: DEBT-0058 was opened. App-level progress, message, and input dialog metadata now lives in tested `pp_app_core` planning consumed by `App::show_progress`, `App::message_box`, `App::input_box`, and `pano_cli plan-app-dialog`, but retained `Node*` dialog creation, layout insertion, callback wiring, and lifetime ownership remain in the legacy app. Later on 2026-06-05, retained `NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` creation moved into `src/legacy_app_dialog_services.*`, making the `App` methods thin adapters while retained layout/lifetime ownership remains open. - 2026-06-05: DEBT-0043 and DEBT-0044 were narrowed. Export success, failure, and license-disabled dialog metadata plus export execution log labels now live in tested `pp_app_core` planning consumed by `src/legacy_document_export_services.*`, `App::dialog_export*`, `pano_cli plan-export-message`, and `pano_cli plan-export-report`. Retained canvas/video export calls, Web prepared-file handoff, picker execution, export-directory creation, and desktop timelapse worker threading remain open. - 2026-06-05: DEBT-0050, DEBT-0051, DEBT-0052, DEBT-0053, and DEBT-0057 were narrowed. `pp_platform_api` now owns a tested `platform_policy` catalog for retained platform-family decisions covering iOS exported-image publishing, WebGL persistent-storage flushing, iOS document browse Inbox roots, Windows/macOS working-directory picker availability, iOS/Web prepared-file target selection, iOS collection-export working-directory policy, macOS PPBR data directory override, SonarPen availability, WebGL default canvas resolution, native UI/window state saving, live asset reload policy, recording cleanup policy, and canvas tip visibility. `WindowsPlatformServices` and the non-Windows legacy fallback consume those helpers while SDK-specific execution remains open in platform shells. - 2026-06-05: DEBT-0054 was narrowed. Layout XML file reload policy now has a pure `plan_asset_file_load_with_probe` decision in `pp_platform_api`, with Windows/macOS mtime reload behavior and mobile/Web already-loaded no-op behavior selected by the tested `platform_policy` catalog. The live `plan_asset_file_load` wrapper still performs the retained `stat` probe for mtime platforms until asset/file watching is owned by injected storage services. - 2026-06-12: DEBT-0054 was narrowed again. The retained mtime probe is now an explicit `pp_platform_api::probe_asset_file_timestamp` boundary with missing and existing file coverage, and `plan_asset_file_load_for_platform` consumes that probe instead of owning the `stat` call inline. Injected file-watch or storage services still need to own the live probe before the debt closes. - 2026-06-12: DEBT-0054 was narrowed again. `LayoutManager::load` now calls the explicit timestamp probe only for platform families that use mtime reloads, then passes the result into the pure `plan_asset_file_load_with_probe` decision. The retained probe still uses local `stat` until injected storage or file-watch services replace it. - 2026-06-12: DEBT-0045 was narrowed. Options-menu boolean preference persistence for VR controllers and auto-timelapse now routes through a named legacy preference-save helper inside `src/legacy_app_preference_services.*` instead of duplicating `Settings::set`/`Settings::save` in each callback. The helper still uses retained `Settings` storage until an injected preference store replaces it. - 2026-06-12: DEBT-0045/0046 were narrowed. Retained preference persistence for options-menu VR-controller, auto-timelapse, viewport-density, cursor-mode, and startup run-counter writes now routes through `src/legacy_preference_storage.*`. The adapter still calls legacy `Settings` directly until a real preference store service is injected. - 2026-06-12: DEBT-0045 was narrowed again. UI scale and retained UI-state/RTL saves now use `src/legacy_preference_storage.*`, leaving `App::set_ui_scale` and `App::ui_save` as UI/layout adapters instead of direct `Settings` writers. - 2026-06-12: DEBT-0045/0052/0058 were narrowed. The whats-new dialog preference and Windows window-placement persistence now use `src/legacy_preference_storage.*` instead of writing `Settings` directly from dialog or platform entrypoint code. - 2026-06-12: DEBT-0045/0046/0052/0058 were narrowed again. Matching retained preference reads for startup, UI restore/options, NodeCanvas defaults, whats-new state, and Windows window placement now use `src/legacy_preference_storage.*` instead of direct `Settings` reads. - 2026-06-12: DEBT-0045/0052/0058 were narrowed again. App dialog, app layout, Windows entrypoint, and NodeCanvas paths that now use the retained preference storage adapter no longer include `settings.h` directly. - 2026-06-12: DEBT-0046 was narrowed again. Startup preference loading now uses `src/legacy_preference_storage.*`, so `App::create` no longer includes `settings.h` directly for startup preferences. - 2026-06-12: DEBT-0046 was narrowed again. `App::init` now reads startup preferences through a retained `LegacyStartupPreferenceSnapshot`, keeping the run-counter, auto-timelapse, and VR-controller preference keys inside `src/legacy_preference_storage.*`. - 2026-06-12: DEBT-0045/0052 were narrowed again. Retained UI restore, NodeCanvas default, and Windows placement preference keys now live behind `LegacyUiPreferenceSnapshot`, `LegacyCanvasPreferenceSnapshot`, and `LegacyWindowPreferenceSnapshot` in `src/legacy_preference_storage.*`. - 2026-06-12: DEBT-0045/0052 were narrowed again. UI scale, UI-state/RTL, and Windows placement write keys now also live behind named `src/legacy_preference_storage.*` helpers. - 2026-06-12: DEBT-0045/0046/0058 were narrowed again. Whats-new, VR-controller, auto-timelapse, startup run-counter, viewport-density, and cursor-mode write keys now use named `src/legacy_preference_storage.*` helpers instead of generic key/value calls in legacy service bridges. - 2026-06-12: DEBT-0045/0046/0052/0058 were narrowed again. The retained preference storage header now exposes only named snapshots/operations; generic key/value helpers are local to `src/legacy_preference_storage.cpp`. - 2026-06-12: DEBT-0045/0046/0052/0058 were narrowed again. Retained preference keys are centralized as local constants inside `src/legacy_preference_storage.cpp`, with all typed legacy `Settings` calls routed through local helper functions. - 2026-06-05: DEBT-0056 was narrowed. `src/asset.h` no longer exposes Android SDK types or forward declarations; retained Android asset-manager and asset handles are stored as opaque pointers and cast only inside `src/asset.cpp`, where the concrete `` dependency remains. The legacy Android `asset.cpp` object compiled under the old Android CMake path; the full legacy `native-lib` target still fails later on unrelated pre-modernization C++ standard/header issues. - 2026-06-05: DEBT-0009/0011 were narrowed for retained Android packages. Standard/Quest/Focus package CMake files now use CMake 3.10, target C++23, share the current modern component/service source set, and route `nanort` through a generated compatibility overlay without dirtying the submodule. The standard arm64 `native-lib` package target builds and links directly; Quest and Focus package CMake paths configure with the current Yoga source list. APK/package migration into root CMake remains open. - 2026-06-05: DEBT-0009 was narrowed again. Android automation now queries `sdkmanager` for the newest available NDK/CMake packages, installs newer or missing versions when needed, selects the resulting pair through `scripts/automation/android-sdk-env.*`, and reports package update decisions in structured wrapper output. The current host reports NDK `30.0.14904198` and CMake `4.1.2` as `already-latest-available`; root Android `pp_assets`, retained standard `native-lib`, and retained Quest/Focus configure gates pass with that pair. - 2026-06-05: DEBT-0004 was narrowed. Retained Linux and WebGL app CMake files now require CMake 3.10 and use target-level `cxx_std_23` with extensions off instead of global C++14 flags. `panopainter_retained_platform_cmake_self_test` guards those baselines while root CMake app/package target migration remains open. - 2026-06-05: DEBT-0011 was narrowed again. Package readiness now includes a Linux app output blocker and points WebGL readiness at the retained WebGL app CMake entrypoint, with both PowerShell and shell wrappers reporting the retained Linux/WebGL CMake baseline prerequisite guarded by `panopainter_retained_platform_cmake_self_test`. - 2026-06-05: DEBT-0011 was narrowed again. Root CMake now exposes `panopainter_linux_webgl_package_readiness`, a filtered package-smoke target for the retained Linux app and WebGL readiness blockers. - 2026-06-05: DEBT-0011 was narrowed again. `package-smoke.ps1` now has `-AndroidNativeChecks`, which invokes the retained Android standard `native-lib` build and Quest/Focus configure helper for selected Android package kinds and reports those structured results beside the still-blocked APK readiness matrix. The shell readiness wrapper now advertises the same retained-native validation commands, and the package-smoke self-test guards that metadata. - 2026-06-05: DEBT-0011 was narrowed again. Root CMake now exposes non-default package validation targets for package readiness and retained Android native package checks. `panopainter_android_native_package_smoke` calls the package-smoke Android native gate from CMake, validates the current latest SDK-managed NDK/CMake pair, and still reports APK outputs as blocked until root CMake owns real package targets. - 2026-06-05: DEBT-0009 was narrowed again. Root CMake now exposes non-default platform-build automation targets for the default headless sweep, Android root CMake asset validation across standard/Quest/Focus presets, and the completed remote Apple compile validation. The Android target validated `pp_assets` across Android arm64/x64, Quest arm64, and Focus/Wave arm64 with the latest SDK-managed NDK/CMake pair. The platform-build wrapper defaults also include `pp_app_core_app_dialog_tests` again, so the target-matrix self-test is green. - 2026-06-05: DEBT-0011 was narrowed again. Root CMake now exposes `panopainter_windows_app_package_smoke`, which calls the full Windows `package-smoke` command from CMake so the app executable/runtime payload check and Windows AppX blocker matrix are available from the CMake target graph. - 2026-06-14: DEBT-0011 was narrowed again. `package-smoke.ps1` and `package-smoke.sh` now classify package readiness as `validated`, `compile-only`, or `blocked` instead of reporting every platform gate as blocked, and root CMake now exposes per-platform readiness targets for Windows AppX, Apple bundles, Android standard/Quest/Focus APKs, Linux app, and WebGL while real package outputs remain debt-tracked. - 2026-06-05: DEBT-0007 was narrowed. `platform-build.ps1` now resolves `VCPKG_ROOT` for vcpkg presets from the environment or bundled Visual Studio installs, reports the selected vcpkg root in JSON, and root CMake exposes `panopainter_platform_build_vcpkg_ui_core` to validate the vcpkg-backed `pp_ui_core`/tinyxml2 boundary through `pp_ui_core_layout_xml_tests`. - 2026-06-04: DEBT-0036 was narrowed again. Canvas stroke commit, thumbnail, and object-draw history paths now query saved blend state through tested `pp_renderer_gl` capability-state dispatch; CanvasLayer equirect export now binds cube textures through tested `pp_renderer_gl` dispatch; and `Font` text drawing now activates texture units through tested `pp_renderer_gl` dispatch. Desktop VR drawing also now routes active texture unit switches and fallback 2D texture unbinds through tested `pp_renderer_gl` dispatch. The debt remains open for live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, and retained `ShaderManager::ext_*` compatibility fields. - 2026-06-05: DEBT-0036 was narrowed again. Retained `NodeCanvas` and `NodeStrokePreview` draw-state paths now share `legacy_ui_gl_dispatch` for active-texture selection, fallback 2D texture unbinds, viewport/scissor execution, viewport and clear-color queries, clear-color restore, color-buffer clears, and capability query/apply adapter endpoints. Their live panorama and brush-preview compositing order remains legacy OpenGL and stays open under DEBT-0036. - 2026-06-05: DEBT-0036 was narrowed again. Retained `CanvasMode` overlay, mask, transform, and canvas-tip pick paths now share `legacy_ui_gl_dispatch` for active-texture selection, capability query/apply, viewport execution, read-framebuffer queries, and RGBA8 pixel-readback adapter endpoints. Mode behavior, transform/cut execution, and live OpenGL paint UI drawing remain retained under DEBT-0036. - 2026-06-05: DEBT-0036 was narrowed again. Retained `NodePanelGrid` heightmap draw and bake setup now share `legacy_ui_gl_dispatch` for active-texture selection, depth/blend capability query/apply, viewport query/execution, depth clears, and color-write-mask adapter endpoints. Grid lightmap baking, heightmap rendering, and retained OpenGL mesh/texture execution remain open. - 2026-06-05: DEBT-0036 was narrowed again. Retained `CanvasLayer` cube/equirect generation and frame-clear paths now share `legacy_ui_gl_dispatch` for active-texture selection, cube texture binding, viewport execution, blend capability execution, clear-color query/restore, and color-buffer clear adapter endpoints. The cube-face framebuffer-to-texture copy remained open until it could use the shared retained framebuffer-copy utility bridge. - 2026-06-05: DEBT-0036 was narrowed again. `CanvasLayer` cube-face framebuffer-to-texture copies now use the shared retained `copy_framebuffer_to_texture_target` utility bridge backed by tested `pp_renderer_gl` dispatch, so `src/canvas_layer.cpp` no longer owns a local raw `glCopyTexSubImage2D` adapter. The bridge remains retained until renderer services own cube and 2D copy execution. - 2026-06-05: DEBT-0036 was narrowed again. Retained `Canvas` stroke, thumbnail, object-render, export, and `LayerFrame::clear` state endpoints now share `legacy_ui_gl_dispatch` for active-texture selection, fallback texture unbind, viewport/scissor execution, viewport and clear-color query, clear-color restore, and capability query/apply adapter endpoints. At that checkpoint, Canvas depth renderbuffer allocation/attachment/delete remained a retained renderer-resource bridge. - 2026-06-05: DEBT-0036 was narrowed again. Retained `Canvas` and `RTT` depth renderbuffer allocation, framebuffer depth attachment, and renderbuffer deletion now share `legacy_gl_renderbuffer_dispatch`, so those files no longer carry duplicated raw renderbuffer callbacks. Renderer services still need to own renderbuffer lifetime before this bridge can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained `Texture2D`, `TextureCube`, and RTT texture allocation, deletion, binding, parameter setup, 2D update, and mipmap dispatch now share `legacy_gl_texture_dispatch`, so `src/texture.cpp` and `src/rtt.cpp` no longer carry duplicated raw texture callbacks. Renderer services still need to own texture resource lifetime before this bridge can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained `Texture2D` readback plus RTT framebuffer allocation, deletion, bind/restore, blit, readback, and PBO readback dispatch now share `legacy_gl_framebuffer_dispatch`, so `src/texture.cpp` and `src/rtt.cpp` no longer carry duplicated raw framebuffer/readback callbacks. Renderer services still need to own framebuffer and readback lifetime before this bridge can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained `Sampler` create, parameter, border-color, bind, and unbind dispatch now share `legacy_gl_sampler_dispatch`, and retained `PBO` allocation, framebuffer readback, map, unmap, and delete dispatch now share `legacy_gl_pixel_buffer_dispatch`. Renderer services still need to own sampler and pixel-buffer lifetime before these bridges can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained `Shape`, `TextMesh`, and `NodeColorWheel` mesh buffer/VAO creation, dynamic vertex/index uploads, fill/stroke/text draws, and buffer/VAO deletion now share `legacy_gl_mesh_dispatch`, so `src/shape.cpp`, `src/font.cpp`, and `src/node_colorwheel.cpp` no longer carry duplicated raw mesh callback clusters. Renderer services still need to own mesh resource lifetime before this bridge can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained shader source compilation/deletion, program attach/link/use/delete, attribute rebinding and location lookup, active-uniform enumeration, uniform-location discovery, and vec/mat/scalar uniform writes now share `legacy_gl_shader_dispatch`, so `src/shader.cpp` no longer carries raw shader/program/uniform callback ownership. Renderer services still need to own shader-program lifetime before this bridge can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained `gl_state` save/restore and `copy_framebuffer_to_texture_target` now reuse `legacy_ui_gl_dispatch`, `legacy_gl_framebuffer_dispatch`, `legacy_gl_shader_dispatch`, and `legacy_gl_sampler_dispatch`, so `src/util.cpp` no longer carries its local raw state/copy callback cluster. Renderer services still need to own state snapshot/restore and framebuffer-copy execution before these bridges can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained app startup, app clear, app UI viewport/scissor, command-convert renderer state, and desktop VR draw-state endpoints now share `legacy_ui_gl_dispatch`, so `src/app.cpp`, `src/app_commands.cpp`, and `src/app_vr.cpp` no longer carry duplicated raw callback clusters for capability, blend, clear, viewport, scissor, active texture, or 2D texture-unbind execution. Renderer services still need to own app and VR command execution before these bridges can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained RTT clear and masked-clear endpoints now share `legacy_ui_gl_dispatch`, so `src/rtt.cpp` no longer carries a local raw clear/color-mask callback cluster. Renderer services still need to own RTT render-target execution before this bridge can be removed. - 2026-06-05: DEBT-0036 was narrowed again. Retained app startup logging, Windows early context logging/window-title detection, and shader capability detection now share `legacy_gl_runtime_dispatch`, so `src/app.cpp`, `src/main.cpp`, and `src/app_shaders.cpp` no longer carry duplicated raw runtime-query callback clusters. Renderer services still need to own runtime and capability probing before this bridge can be removed. - 2026-06-06: DEBT-0036/DEBT-0017 were narrowed again. Retained HMD viewport setup, text atlas texture-unit activation, Windows/legacy default framebuffer binding, platform render-hint enable callbacks, the global OpenGL error drain, and Windows debug message callback installation now reuse `legacy_ui_gl_dispatch`/`legacy_gl_runtime_dispatch` instead of file-local raw OpenGL entrypoint adapters. Renderer/context services still need to own these paths before the retained dispatch bridges can be removed. - 2026-06-05: DEBT-0061 was opened. `pp_platform_api` now owns a tested desktop XR runtime-selection policy that prefers OpenXR and labels OpenVR as a legacy fallback; `WindowsPlatformServices` consumes that policy before calling the retained OpenVR bridge. The actual OpenXR SDK/backend wiring remains open. - 2026-06-05: DEBT-0008 was narrowed. `windows-msvc-default` and `windows-msvc-vcpkg-headless` now explicitly select the Visual Studio 18 2026 generator and should be configured with the VS 2026-bundled CMake, avoiding a stale/generated-tree failure where a non-VS2026 CMake selected Ninja while also carrying an x64 platform value. - 2026-06-05: DEBT-0062 was opened. VS 2026 builds generate a patched fmt header overlay in the build tree for `pp_legacy_vendor` so retained fmt skips the removed `stdext::checked_array_iterator` compatibility path without modifying the fmt submodule. - 2026-06-05: DEBT-0061 was narrowed. While OpenVR remains the temporary desktop XR fallback, the Windows runtime payload now deploys `openvr_api.dll` beside `PanoPainter.exe` so Visual Studio launches exercise the retained bridge instead of failing during process load. - 2026-06-06: DEBT-0063 was opened. The retained UI `Node` tree lifetime model now has an explicit roadmap track: raw parent/lookup pointers, public child ownership, raw callback targets, and manual destroy semantics must be replaced incrementally by `pp_ui_core` ownership handles, scoped callback connections, and mutation-safe dispatch tests before broad panel/dialog migration accelerates. - 2026-06-06: DEBT-0063 was narrowed. `pp_ui_core::NodeLifetimeTree` now owns a pure checked-handle model for parent/child invariants, subtree destruction, scoped callback disconnection, and mutation-safe dispatch when callbacks destroy the dispatched node or add new connections. Retained `Node` adoption, layout reload coverage, and `pp_panopainter_ui` dialog/popup lifetime tests remain open. - 2026-06-06: DEBT-0063 was narrowed again. The same `NodeLifetimeTree` model now covers pointer/keyboard capture ownership, automatic capture release when a captured node is destroyed, and whole-tree `clear()` invalidation for layout reloads. Retained `Node` adoption and app-specific popup/dialog lifetime tests remain open. - 2026-06-06: DEBT-0063 was narrowed again. `pp_ui_core::UiOverlayLifetime` now models root and nested popup ownership, modal/modeless dialog capture policy, capture restoration after child/modal close, parent popup branch teardown, untracked close rejection, and layout-reload invalidation. Retained `NodePopupMenu`/`NodeDialog*` adoption remains open. - 2026-06-06: DEBT-0063/DEBT-0058 were narrowed. `src/legacy_ui_overlay_services.*` now centralizes retained app-dialog overlay initialization and root attachment, and the app-level progress/message/input dialogs plus about/manual/changelog, document open/save/new/browse/resize/layer-rename, PPBR export, shortcuts, and what's-new overlays route through it. Raw `Node` ownership, callback captures, and retained popup/menu lifetime semantics remain open. - 2026-06-06: DEBT-0063 was narrowed again. Retained File, Export, Edit, Tools, Panels, Options, About, and Layers app-menu popups now clone and attach through `src/legacy_ui_overlay_services.*`, so missing popup templates return explicit status/logging instead of direct `m_children[0]` dereferences in `App::init_menu_*`. Raw popup callback captures and retained close semantics remain open. - 2026-06-06: DEBT-0063 was narrowed again. `src/legacy_ui_overlay_services.*` moved down to `pp_legacy_app` and now exposes retained root-popup attachment for `pp_panopainter_ui`; quick-panel brush/color popups, stroke-panel preset/ tip/dual/pattern popups, and brush-panel preset menu insertion no longer call `root()->add_child(...)` directly. Base retained popup widgets such as `NodeComboBox`, raw callback captures, and close/capture semantics remain open. - 2026-06-12: DEBT-0063 was narrowed again. Retained `NodeComboBox` popups and Open/Browse delete-confirmation dialogs now initialize and attach through `src/legacy_ui_overlay_services.*` instead of direct root insertion. Raw popup callback captures, retained close/capture semantics, and broader `Node` handle adoption remain open. - 2026-06-12: DEBT-0063 was narrowed again. Top-toolbar stroke/color/layer/grid panel popups, quick-panel and stroke-panel popup tick decoration nodes, and brush preset menu template attachment now route through `src/legacy_ui_overlay_services.*` instead of direct root insertion or temporary panel child insertion. Raw popup callback captures, retained close/capture semantics, and broader `Node` handle adoption remain open. - 2026-06-12: DEBT-0063 was narrowed again. Retained popup mouse-ignore/flood-events/capture-children setup and mouse capture activation for top-toolbar, quick-panel, stroke-panel, and combo-box popups now route through `src/legacy_ui_overlay_services.*`. Raw popup callback captures, retained close semantics, and broader `Node` handle adoption remain open. - 2026-06-12: DEBT-0063 was narrowed again. Retained menu, submenu, combo-box, and brush-preset popup close/release execution now routes through `src/legacy_ui_overlay_services.*` instead of open-coded `mouse_release()` and `destroy()` pairs. Raw callback captures, retained popup close policy, and broader `Node` handle adoption remain open. - 2026-06-12: DEBT-0063 was narrowed again. Top-toolbar, quick-panel, and stroke-panel popup tick-decoration close callbacks now bind through `src/legacy_ui_overlay_services.*` instead of per-panel lambdas. Other raw popup callbacks, retained close policy, and broader `Node` handle adoption remain open. - 2026-06-12: DEBT-0063 was narrowed again. Brush, brush-preset, color, layer, grid, stroke, and color-picker popup-panel outside-click close handling now routes through `src/legacy_ui_overlay_services.*` for release, root removal, and callback dispatch instead of repeating that sequence in each panel class. Broader checked-handle and scoped-callback adoption remains open. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. About, changelog, settings, and user-manual close buttons now bind retained destroy-on-click behavior through `src/legacy_ui_overlay_services.*` instead of per-dialog lambdas. Broader checked-handle and scoped-callback adoption remains open. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. Message-box submit/cancel and input-box cancel destroy callbacks now use retained close helpers from `src/legacy_ui_overlay_services.*`. Dialog lifetime still uses raw `Node` callbacks until checked handles and scoped connections land. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. Document open/save/new/ browse/resize, layer-rename, cloud-browse, and PPBR export dialog cancel buttons now bind retained destroy-on-click behavior through `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. App-level new-document, save, and layer-rename cancel callbacks now route retained dialog destruction plus virtual-keyboard hide through `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. Browse accept, resize accept/failure, and what's-new read-later/close callbacks now route retained dialog destruction through `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. Open/Browse delete-confirmation message boxes and shared destroy-on-click bindings now use the retained dialog close helper in `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063/DEBT-0040/DEBT-0041/DEBT-0042 were narrowed again. Document-session overwrite prompts, unsaved-close prompts, save-before-workflow prompts, and accepted new/save document cleanup now route retained dialog closing through `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. Cloud publish prompts, upload/download progress dialogs, and cloud browser download-close now route retained closing through `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. App message-dialog cancel-button removal, layer-rename finish cleanup, and recording export progress cleanup now route retained closing through `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063 was narrowed again. Floating-panel close and drag-outline cleanup now use the retained close helper in `src/legacy_ui_overlay_services.*`; drag placeholder reparenting and dock/drop mutation remain legacy-owned. - 2026-06-12: DEBT-0063 was narrowed again. `NodePopupMenu` mouse-up release and destroy now route through the retained popup close helper in `src/legacy_ui_overlay_services.*`. - 2026-06-12: DEBT-0063 was narrowed again. Layer-row, animation-timeline, and heightmap-overlay drag release paths now route mouse-capture release through `src/legacy_ui_overlay_services.*`, and brush/grid progress or recovery message dialogs now use the retained dialog close helper instead of direct `destroy()` calls. Checked handles and scoped callback ownership remain open. - 2026-06-12: DEBT-0063 was narrowed again. Floating-panel placeholder detach/ destroy, cloud-loading alignment cleanup, brush-preset item removal, and retained popup-panel parent detach now route through named retained node helpers in `src/legacy_ui_overlay_services.*`. Floating-panel dock/drop reparenting still uses legacy child-vector ownership until checked handles land. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. Canvas export/project progress-bar cleanup and remote-page loading placeholder cleanup now route through retained close/destroy helpers in `src/legacy_ui_overlay_services.*`. The long-running canvas workflows still depend on legacy `App::I` UI tasking and raw progress-bar pointers. - 2026-06-12: DEBT-0063 was narrowed again. Toolbar popup restoration from docked floating stroke/layer/grid panels and restored floating color-panel title cleanup now use retained detach/close/destroy helpers in `src/legacy_ui_overlay_services.*`. Dock/drop child ownership and raw panel globals remain legacy-owned. - 2026-06-12: DEBT-0063 was narrowed again. Retained canvas mode mouse-capture release for camera, paint, line, grid, mask, and fill interactions now routes through `src/legacy_ui_overlay_services.*` instead of direct `NodeCanvas` release calls. Capture ownership is still raw until retained `Node` adopts checked handles and scoped event/capture lifetime. - 2026-06-12: DEBT-0063 was narrowed again. Retained button, slider, scroll, color-wheel, color-quad, and canvas gesture-end capture release now route through `src/legacy_ui_overlay_services.*`. The controls still own raw event callback targets and public `Node` capture state. - 2026-06-12: DEBT-0063/DEBT-0058 were narrowed again. Legacy document-open import/discard prompts and brush-package export completion now route retained dialog closing through `src/legacy_ui_overlay_services.*` instead of direct `destroy()` calls from service adapters. - 2026-06-12: DEBT-0063 was narrowed again. Flood-fill tool teardown and checkbox icon removal now route retained node destruction through `src/legacy_ui_overlay_services.*`. The underlying `Node` tree still exposes raw child pointers and public destroy state. - 2026-06-12: DEBT-0063 was narrowed again. Layout unload root destruction and popup tick-overlay close callbacks now route retained node destruction through `src/legacy_ui_overlay_services.*`. `Node::destroy()` itself remains the compatibility boundary until checked tree handles replace raw parent/child mutation. - 2026-06-05: DEBT-0011 was narrowed. The Windows app package smoke target now passes the configure-time CMake executable into `package-smoke.ps1`, so VS 2026 generator validation does not depend on an older `cmake` on PATH, and the smoke checks cover the Windows launch-folder DLL payload. - 2026-06-05: DEBT-0010 was narrowed. `pp_paint_renderer` now consumes `pp_document` directly for pure frame/face compositing, expanding per-layer dirty face payload rectangles into a full renderer-sized RGBA buffer while preserving document layer visibility, opacity, blend mode, and uneven per-layer frame timelines. - 2026-06-05: DEBT-0010 was narrowed again. The same compositor boundary now exposes a pure six-face document frame composite plus `pano_cli simulate-document-render` JSON automation, so headless tests can validate document payloads moving toward renderer/export services without a GL context. - 2026-06-05: DEBT-0010 was narrowed again. `pp_paint_renderer` can now upload a pure document frame's six composited faces through the renderer-neutral `IRenderDevice` texture API, and the recording backend/CLI smoke validate six RGBA8 texture uploads plus explicit shader-read transitions. - 2026-06-05: DEBT-0010/DEBT-0036 were narrowed. `pano_cli simulate-document-render` now also runs those recorded document upload commands through `pp_renderer_gl::plan_recorded_render_commands`, proving the six texture uploads and transitions are accepted by the current OpenGL command planner while live legacy GL execution remains retained. - 2026-06-05: DEBT-0010 was narrowed again. `pp_app_core` now owns a tested metadata-only live-canvas-to-`pp_document::CanvasDocument` snapshot planner, `pano_cli plan-canvas-document-snapshot` exposes the projection as JSON automation, and `src/legacy_document_canvas_services.*` can build the same snapshot from retained `Canvas` dimensions, active layer/frame, layer visibility/opacity/alpha/blend metadata, and frame durations. Renderer-owned cube-face pixel payload readback remains open under DEBT-0010/DEBT-0036. - 2026-06-05: DEBT-0010 was narrowed again. The canvas snapshot planner now accepts captured RGBA8 face payloads and attaches them to `pp_document::CanvasDocument`; `pano_cli plan-canvas-document-snapshot --captured-face-payloads-per-layer` covers payload-bearing automation, and `src/legacy_document_canvas_services.*` exposes an opt-in dirty-face payload snapshot path backed by retained `Layer::snapshot()` readback. Live save, export, and action-command adoption of that payload-bearing snapshot remains open under DEBT-0010/DEBT-0013/DEBT-0036. - 2026-06-05: DEBT-0010/DEBT-0013/DEBT-0040/DEBT-0042 were narrowed. Live Save, Save As, Save Version, and save-before-workflow now prepare a payload-bearing canvas document snapshot plus `pp_app_core` save-readiness report before delegating to retained `Canvas::project_save`. The retained writer still owned PPI serialization, progress/threading, and compatibility quirks, so pure writer replacement remained open. - 2026-06-06: DEBT-0010/DEBT-0013/DEBT-0040/DEBT-0042 were narrowed again. `pp_app_core` now owns a tested save-writer route policy for canvas snapshots, `pano_cli plan-canvas-document-snapshot` emits that route as JSON, and live Save, Save As, Save Version, and save-before-workflow run the pure PPI exporter for payload-complete snapshots and log byte counts before retained `Canvas::project_save` continues. The retained writer still owns actual save serialization, app metadata mutation, progress/threading, and compatibility quirks. - 2026-06-06: DEBT-0040/DEBT-0042 were narrowed again. `pp_app_core` now owns the retained project-save target path planner for the requested PPI path, temporary `.tmp.ppi` path, and timelapse `.pptl` sidecar path; live `Canvas::project_save_thread` consumes it and `pano_cli plan-canvas-project-save-target` exposes it for automation. Actual PPI serialization, temporary-file swap execution, progress/threading, timelapse sidecar serialization, and app metadata mutation remain retained. - 2026-06-06: DEBT-0040/DEBT-0042 were narrowed again. The same app-core project-save planner now owns the direct-write versus temporary-write mode decision for new and existing targets, including the retained compatibility fallback to direct target writes when the temporary file cannot be opened. `Canvas::project_save_thread` consumes that policy before retained serialization, and `pano_cli plan-canvas-project-save-target` reports it in JSON. Actual PPI bytes, temporary-file swap execution, progress/threading, timelapse sidecar serialization, and app metadata mutation remain retained. - 2026-06-06: DEBT-0040/DEBT-0042 were narrowed again. `pp_app_core` now owns the retained project-save commit outcome policy for direct writes, successful temporary swaps, target-remove failures, and rename-after-remove failures, including an explicit `targetMayBeMissing` flag for failed swaps after the original target was removed. Live `Canvas::project_save_thread` consumes that result before retained success metadata mutation, and `pano_cli plan-canvas-project-save-target` reports the same commit plan. Actual PPI serialization, filesystem remove/rename execution, progress/threading, timelapse sidecar serialization, and app metadata mutation remain retained. - 2026-06-06: DEBT-0040/DEBT-0042 were narrowed again. `pp_app_core` now owns the retained project-save post-commit side-effect policy: successful saves mark the document clean, commit new-document state, flush platform storage, optionally write a timelapse sidecar when an encoder exists, and always dismiss visible progress UI plus refresh the title. Live `Canvas::project_save_thread` consumes that plan, and `pano_cli plan-canvas-project-save-target` reports it in JSON. Actual state mutation, progress UI destruction, platform flush execution, title update, and timelapse sidecar serialization remain retained. - 2026-06-05: DEBT-0010/DEBT-0013 were narrowed again. `pp_app_core` now exports payload-complete or metadata-only canvas document snapshots through the pure `pp_document` PPI writer and rejects snapshots that still require renderer readback; `pano_cli plan-canvas-document-snapshot` reports `ppiExport` readiness, byte counts, and decoded dirty-face counts. - 2026-06-05: DEBT-0010/DEBT-0036 were narrowed again. Payload-complete `pano_cli plan-canvas-document-snapshot` automation now feeds the captured `pp_document` snapshot through `pp_paint_renderer::upload_document_frame_faces` and the renderer-neutral recording backend, reporting texture uploads, transitions, command counts, bytes, and active-frame payload counts. - 2026-06-05: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed. Live equirectangular, layer, animation-frame, and cube-face export adapters now prepare the payload-bearing canvas document snapshot and renderer-neutral upload report before retained `Canvas` export execution. Failures are logged and retained export still continues. - 2026-06-05: DEBT-0036 was narrowed again. The renderer-neutral document-frame recorded-upload report now lives in `pp_paint_renderer` as `record_document_frame_upload`, with focused compositor coverage, and both `pano_cli plan-canvas-document-snapshot` plus live export readiness logging consume that shared helper instead of duplicating command counting. - 2026-06-05: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed again. `pp_paint_renderer::export_document_frame_face_pngs` now composites a document frame and encodes all six cube faces to PNG bytes through `pp_assets`, `pano_cli plan-canvas-document-snapshot` reports `facePngExport` readiness, and live image/collection/cube export readiness logging runs the same pure writer primitive before retained `Canvas` writers. - 2026-06-05: DEBT-0036/DEBT-0043 were narrowed again. Live cube-face export now writes the pure document/renderer face PNG bytes to the retained `work_path/document-face.png` filename set and publishes each path through the platform service, falling back to retained `Canvas::export_cube_faces` if snapshot capture, face PNG generation, or file writing fails. - 2026-06-05: DEBT-0043 was narrowed again. The retained cube-face filename set is now planned by tested `pp_app_core` `make_document_cube_face_export_target`, exposed through `pano_cli plan-export-target --kind cube-faces`, and consumed by `src/legacy_document_export_services.*` before writing pure face-PNG bytes. Storage service ownership and non-cube retained export writers remain open. - 2026-06-05: DEBT-0043 was narrowed again. Cube-face export file writes and exported-image publishing now dispatch through tested `pp_app_core` `execute_document_cube_face_export_write`, so the live bridge only adapts the retained filesystem write and `App::publish_exported_image` calls. Dedicated storage/platform service ownership and non-cube retained export writers remain open. - 2026-06-05: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed again. The CLI payload-complete canvas snapshot path and live export adapter now share `pp_paint_renderer::prepare_document_frame_export_readiness`, centralizing the document-frame recorded upload report and six-face PNG export report used before retained writer fallback. - 2026-06-05: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed again. `pp_paint_renderer::export_document_frame_equirectangular_png` now converts the composited document-frame cube faces into an equirectangular PNG with the shader-equivalent cube sampling policy and `pp_assets` PNG encoding. Live PNG equirectangular export writes through that path before retained fallback; then-open JPEG/XMP metadata parity, GPU filtering/golden parity, Web prepared-file handoff, and the then-retained collection/depth/video writers stayed open. - 2026-06-06: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed again. `pp_app_core` now owns the document-snapshot export route decision used by live equirectangular, layer, animation-frame, and cube-face export bridges, including payload-complete writer use, PNG/JPEG equirectangular target support, collection/cube target support, target/platform unsupported fallback, and pending renderer-readback fallback reasons. `pano_cli plan-export-snapshot-route` exposes the same route policy, including unsupported target paths. Retained writers, Web handoff, video/depth export execution, and renderer-owned payload readback remain open. - 2026-06-06: DEBT-0010/DEBT-0043 were narrowed again. `pp_app_core` now owns the current-platform support helper for document-snapshot export writers, and the live retained export bridge consumes that policy for equirectangular, layer, animation-frame, and cube-face snapshot-writer attempts instead of carrying local Web writer gates. `pano_cli plan-export-snapshot-route` now has smoke coverage for unsupported-platform fallback. Retained Web prepared-file handoff, incomplete-readback collection fallback, depth/video export execution, and broader renderer-owned export execution remain open. - 2026-06-06: DEBT-0010/DEBT-0043 were narrowed again. Depth export now logs the same tested `pp_app_core` document-snapshot route fallback as an unsupported writer target before retained depth rendering, and `pano_cli plan-export-snapshot-route --kind depth` has smoke coverage for the unsupported-target JSON contract. The actual depth perspective render, depth-payload readback, write execution, progress/threading parity, and retained Web handoff remain open. - 2026-06-06: DEBT-0010/DEBT-0043 were narrowed again. `pp_app_core` now owns a tested single-file export write/publish executor, and the live PNG/JPEG equirectangular document-snapshot writer uses it before retained fallback. The retained bridge still provides the filesystem/publish adapter, while Web prepared-file handoff, incomplete-readback collection fallback, depth/video export execution, progress/threading parity, and broader storage service ownership remain open. - 2026-06-05: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed again. `pp_paint_renderer` now exports independent layer equirectangular PNGs and merged animation-frame equirectangular PNGs from payload-complete `pp_document` snapshots, while `pp_app_core` owns the retained collection filename suffixes plus collection write/publish execution. Live layer and animation-frame PNG collection export uses those paths before retained fallback; Web handoff, incomplete renderer-readback snapshots, progress and threading parity, exact GPU filtering/goldens, depth, video, and then-open JPEG/XMP remained open. - 2026-06-05: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed again. Depth export now plans the retained image/depth output target paths in tested `pp_app_core`, has a tested `pp_app_core` two-payload write/publish executor ready for later pure payload adoption, and logs a tested `pp_paint_renderer::plan_document_depth_export_render` result from the live document snapshot before retained `Canvas::export_depth` runs. The actual 1024x1024 perspective/depth rendering, readback, worker-thread behavior, and the legacy `.png` path names carrying JPEG-encoded bytes remain open. - 2026-06-05: DEBT-0010/DEBT-0036 were narrowed again. `pano_cli plan-canvas-document-snapshot` now emits the same depth export render-plan readiness as the live adapter. Metadata-only snapshots report depth export unavailable until renderer payload readback is captured; payload-complete snapshots report the 1024x1024 merged-face and per-layer depth draw counts while still marking final depth image readback as renderer-owned. - 2026-06-05: DEBT-0010/DEBT-0036/DEBT-0043 were narrowed again. `pp_assets` now owns RGBA8 JPEG encode/decode plus GPano XMP APP1 injection, `pp_paint_renderer` exports equirectangular JPEG+XMP bytes from the pure document-frame projection, and the live desktop `.jpg`/`.jpeg` equirectangular export bridge writes that payload before retained fallback. Web handoff, incomplete-readback snapshots, progress/threading parity, exact GPU filtering/goldens, depth render/readback, and video remain open. ## Open Debt | ID | Status | Owner | Item | Reason | Validation | Removal Condition | | --- | --- | --- | --- | --- | --- | --- | | DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets | | DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially, including the generated Android package `nanort` compatibility overlay tracked by DEBT-0060 and the retained OpenVR SDK fallback tracked by DEBT-0061 | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation | | DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `App::rec_loop`, `App::update_ui_observer`, `App::render_task*`, `App::ui_task*`, `App::render_thread_*`, `App::ui_thread_*`, file-menu save actions, `NodeCanvas` canvas hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/menu/target naming/path/message/report decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress/worker decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display and renderer diagnostic decisions, app dialog metadata decisions, app frame/UI-observer decisions, app thread/task orchestration decisions, document resize decisions, layer rename/menu decisions, Tools menu/panel decisions, About menu/diagnostic decisions, main toolbar/status decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-file-menu`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-menu`, `pano_cli plan-export-target`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-app-dialog`, `pano_cli plan-app-thread`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, `pano_cli plan-about-menu`, `pano_cli plan-main-toolbar`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-layer-menu`, `pano_cli plan-canvas-hotkey`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/dialog/thread/share/platform-I/O/display/keyboard/cloud/resize/layer/tools/about/toolbar/canvas-command contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, Tools panel creation/execution, About dialog/diagnostic execution, toolbar/status dialog/history/canvas execution, app dialog node creation, status/display UI rendering, renderer diagnostic capability adaptation, app task/thread execution, UI observer parent walking/callback execution, document resize execution, layer rename/menu execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4/PBO execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_file_menu_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_frame_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_app_dialog_tests`; `pp_app_core_app_thread_tests`; `pp_app_core_tools_menu_tests`; `pp_app_core_about_menu_tests`; `pp_app_core_main_toolbar_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pp_app_core_canvas_hotkey_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind license-disabled`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --running --no-encoder`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12 --framebuffer-fetch --float32 --float32-linear --float16`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-frame`; `pano_cli plan-app-thread --kind ui-loop --live-reload`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-canvas-hotkey --event key-up --key z --ctrl --undo-count 2`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries | | DEBT-0004 | Open | Modernization | Android, Linux, and WebGL retained CMake entrypoints now use the CMake 3.10/C++23 modernization baseline, but Android Gradle/APK, Linux app, WebGL app/package, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; `python scripts/dev/check_retained_platform_cmake.py`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path | | DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated | | DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, and root CMake now exposes a focused `panopainter_platform_build_vcpkg_ui_core` target for the vcpkg-backed `pp_ui_core`/tinyxml2 boundary, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_platform_build_vcpkg_ui_core` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions | | DEBT-0008 | Open | Modernization | `windows-msvc-default` and `windows-msvc-vcpkg-headless` explicitly select Visual Studio 18 2026 for local validation, but non-VS2026 CMake executables on PATH may not know that generator | The local machine has VS 2026, but using an older CMake can still default to Ninja or reject the VS 2026 generator | `cmake --preset windows-msvc-default`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug` | The repo automation invokes or locates a CMake executable that supports `Visual Studio 18 2026`, and VS 2026 generator validation is the normal Windows path without manual tool selection | | DEBT-0009 | Open | Modernization | Android root CMake validation currently builds headless targets only, while retained standard/Quest/Focus package CMake paths now have a refreshed CMake 3.10/C++23 baseline outside root CMake; automation queries `sdkmanager`, installs newer or missing SDK Manager NDK/CMake packages, selects the resulting pair before configure, and reports update decisions; root CMake exposes non-default platform-build and retained native package validation targets | Platform app entrypoints still live in legacy Gradle/CMake projects and need Phase 6 alignment | `powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64`; `cmake --build --preset android-x64`; `cmake --build --preset android-quest-arm64`; `cmake --build --preset android-focus-arm64`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_platform_build_android_assets`; `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard`; `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages quest,focus -ConfigureOnly`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly -AndroidNativeChecks -PackageKinds android-standard-apk,android-quest-apk,android-focus-apk`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_android_native_package_smoke` | Android standard, Quest, and Focus/Wave package targets consume shared component targets and have package smoke commands | | DEBT-0010 | Open | Modernization | `pp_document` is a pure layer/frame/document/undo-history model with alpha-lock metadata, snapshot construction, per-layer frame metadata, renderer-free RGBA8 face payload storage, snapshot-embedded face-payload validation, renderer-free alpha8 selection-mask storage, PPI import/export helpers, stroke-script-to-face-payload CLI automation, `pp_paint_renderer` document face/frame compositors, renderer-neutral six-face texture upload, pure six-face PNG export, pure equirectangular PNG export, pure equirectangular JPEG+XMP export, pure layer/animation-frame PNG collection export, pure depth image/depth PNG export for payload-complete snapshots, shared document-frame export readiness reporting, depth export render-plan reporting, OpenGL command-planner validation through CLI render automation, live Canvas snapshot projection through `pp_app_core`/`legacy_document_canvas_services`, captured RGBA8 payload attachment to `pp_document`, live Save/Save As/Save Version/save-before-workflow snapshot-readiness reporting before retained save execution, pure app-core PPI export for payload-complete canvas snapshots, payload-complete canvas-snapshot renderer-upload plus face-PNG export automation, live cube-face face-PNG writer execution using app-core face target planning and write/publish service dispatch with retained fallback, live PNG/JPEG equirectangular writer execution using the paint-renderer equirectangular exports plus app-core file write/publish dispatch with retained fallback, live payload-complete layer/animation-frame collection writer execution using paint-renderer PNG sequences and app-core collection write/publish dispatch with retained fallback, live payload-complete depth export execution using pure paint-renderer PNG payloads plus app-core two-payload writing with retained fallback, tested app-core document-snapshot export route policy for writer versus retained fallback including current-platform support, and live equirectangular/layer/animation-frame/depth/cube-face export snapshot/render/export-readiness reporting through the shared readiness helper plus the depth render plan, but action-command adoption, live save-writer replacement, Web and incomplete-readback collection handoff, progress/threading parity, broader renderer-owned export execution, exact GPU/golden parity, live-camera depth parity, and renderer-owned cube-face readback ownership are not yet wired | Keep extraction incremental while preserving app behavior | `ctest --preset desktop-fast --build-config Debug`; `pano_cli create-document --width 64 --height 32 --layers 2`; `pano_cli load-project --path tests\data\projects\minimal-project.ppi`; `pano_cli simulate-document-render --width 64 --height 32`; `pano_cli plan-canvas-document-snapshot --width 64 --height 32`; `pano_cli plan-canvas-document-snapshot --captured-face-payloads-per-layer 1`; `pano_cli plan-export-snapshot-route --kind layers-collection --captured-face-payloads 3 --pending-face-payloads 6`; `pp_document_tests`; `pp_document_ppi_import_tests`; `pp_document_ppi_export_tests`; `pp_paint_renderer_compositor_tests`; `pp_app_core_document_canvas_tests`; `pp_app_core_document_export_tests`; `pano_cli_simulate_document_edits_smoke`; `pano_cli_simulate_document_export_smoke`; `pano_cli_simulate_document_render_smoke`; `pano_cli_plan_canvas_document_snapshot_smoke`; `pano_cli_plan_canvas_document_snapshot_payload_smoke`; `pano_cli_plan_export_snapshot_route_pending_smoke`; `pano_cli_save_document_project_roundtrip_smoke`; `pano_cli_apply_stroke_script_roundtrip_smoke`; `pano_cli_apply_stroke_script_rejects_tiny_canvas` | Legacy document behavior is represented by `pp_document`/`pp_paint_renderer` tests and the app consumes it through a boundary/facade | | DEBT-0011 | Open | Modernization | `package-smoke` validates the Windows CMake app artifact and launch-folder DLL payload, reports a structured package readiness matrix for Windows AppX, Android standard/Quest/Focus APKs, Apple bundles, Linux app output, and WebGL output, and now classifies each package gate as `validated`, `compile-only`, or `blocked` based on local prerequisites plus root CMake package-target ownership; the Windows app smoke passes the configure-time CMake executable so VS 2026 generator validation does not depend on `cmake` from PATH, retained Android package native CMake paths, retained Linux/WebGL CMake baseline metadata, and per-platform readiness targets are reachable from package validation and root CMake package-readiness targets, but Windows AppX and Apple/WebGL outputs remain blocked where local toolchains or root package targets are missing and Android/Linux package readiness remains compile-only until root CMake owns real package targets | Platform package targets are not migrated to root CMake yet | `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_windows_app_package_smoke`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_windows_appx_package_readiness`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_apple_bundle_package_readiness`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly -AndroidNativeChecks -PackageKinds android-standard-apk,android-quest-apk,android-focus-apk`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_android_standard_apk_package_readiness`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_android_quest_apk_package_readiness`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_android_focus_apk_package_readiness`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_android_native_package_smoke`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_linux_app_package_readiness`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_webgl_package_readiness`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_linux_webgl_package_readiness`; `python scripts/dev/check_package_smoke_readiness.py`; `bash -n scripts/automation/package-smoke.sh` | Package-smoke builds and validates Windows AppX, Android APK variants, Linux app, Apple bundles, and WebGL output where local toolchains are present | | DEBT-0012 | Open | Modernization | `pp_ui_core` uses vcpkg tinyxml2 on `windows-msvc-vcpkg-headless`, but retains `pp_vendor_tinyxml2` for default and unproven platform presets | Mobile/AppX/Apple triplets and app packaging still need validation before removing the vendored fallback | `ctest --preset desktop-fast-vcpkg --build-config Debug`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64` | All supported presets consume vcpkg tinyxml2 or document a permanent vendored exception | | DEBT-0013 | Open | Modernization | `pp_assets`, `pp_document`, `pano_cli inspect-project`, `pano_cli load-project`, and `pano_cli save-project` validate the fixed PPI header, thumbnail/body byte layout, generated multi-layer/multi-frame PPI writing with explicit layer opacity/blend/alpha-lock/visibility metadata, per-layer frame durations, metadata-only and targeted dirty-face-payload save/load round-trips, layer/frame index, dirty-face descriptors, dirty-face PNG payload metadata, asset-level RGBA PNG payload decoding, pure document-to-PPI export, CLI document export automation, file-writing document export automation, stroke-script-generated document payload export, decoded pixel attachment to `pp_document`, live save-path snapshot-readiness reporting, and app-core canvas-snapshot-to-PPI export automation, but full legacy PPI round-trip parity and pure live save writer replacement are not yet extracted | Full PPI save parity requires staged extraction of legacy `Canvas` serialization and image/layer payload handling | `ctest --preset desktop-fast --build-config Debug`; `pp_assets_image_pixels_tests`; `pp_assets_ppi_header_tests`; `pp_document_ppi_import_tests`; `pp_document_ppi_export_tests`; `pano_cli_inspect_project_layout_smoke`; `pano_cli_load_project_metadata_smoke`; `pano_cli_save_project_roundtrip_smoke`; `pano_cli_save_project_payload_roundtrip_smoke`; `pano_cli_simulate_document_export_smoke`; `pano_cli_save_document_project_roundtrip_smoke`; `pano_cli_apply_stroke_script_roundtrip_smoke`; `pano_cli_apply_stroke_script_rejects_tiny_canvas` | Full PPI load/save fixtures cover thumbnails, decoded layer face payloads attached to documents, frames, corrupt payloads, dirty-face payload saving, arbitrary legacy canvas payload/layout combinations, and legacy app round-trip compatibility | | DEBT-0014 | Open | Modernization | `windows-clangcl-asan` now configures as a headless Ninja/clang-cl preset and uses the release MSVC runtime required by ASan, but local builds still fail because installed clang-cl 18.1.8 is paired with VS 2026-preview STL headers that require Clang 20 or newer | Sanitizer validation should be local and repeatable, but this machine's compiler/header pairing is incompatible | `cmake --fresh --preset windows-clangcl-asan`; `cmake --build --preset windows-clangcl-asan --target pp_foundation` | Install/use Clang 20+ with the VS 2026 STL, or point the preset at a compatible VS 2022 toolchain, then make `platform-build.ps1 -Presets windows-clangcl-asan` pass for the headless matrix | | DEBT-0015 | Open | Modernization | Cursor visibility requests now consume pure `pp_app_core` planning through `pano_cli plan-cursor-visibility`, `App::show_cursor`/`App::hide_cursor` dispatch through `PlatformServices` without platform guards, and Windows live execution uses injected `WindowsPlatformServices`, but macOS cursor execution still reaches the retained fallback adapter | Keep canvas cursor behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-cursor-visibility --visible`; `ctest --preset desktop-fast --build-config Debug` | Cursor visibility execution is owned by injected `pp_platform_*` services for every supported platform | | DEBT-0016 | Open | Modernization | Clipboard get/set requests now consume pure `pp_app_core` planning through `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write`; Windows live execution uses injected `WindowsPlatformServices`, Apple clipboard execution now uses `src/platform_apple/apple_platform_services.*`, Android clipboard execution now uses `src/platform_android/android_platform_services.*`, and only Linux/Web fallback behavior still routes through the retained non-Windows adapter | Keep picker/color text clipboard behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-clipboard-write --text #ff00aa`; `ctest --preset desktop-fast --build-config Debug` | Clipboard execution is owned by injected `pp_platform_*` services for every supported platform | | DEBT-0017 | Open | Modernization | Startup storage path preparation, `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, native app/window close, UI-thread lifecycle hooks, render-context acquire/release/present hooks, render-target binding hooks, render platform hint hooks, render debug callback hooks, render-capture frame hooks, recording cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, per-frame platform hooks, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, `App::pick_dir`, working-directory picker/display-path policy, canvas input tip/pressure policy, prepared-file save/download handoff, work-directory document export collection policy, app network TLS verification policy, PPBR export data-directory policy, SonarPen availability/startup, and VR mode start/stop now call the SDK-free `pp::platform::PlatformServices` interface. Windows injects `WindowsPlatformServices` from `src/platform_windows/windows_platform_services.*`, Apple now injects `src/platform_apple/apple_platform_services.*`, Android now injects `src/platform_android/android_platform_services.*`, and the retained non-Windows fallback adapter in `src/platform_legacy/legacy_platform_services.*` is narrowed to Linux/Web behavior plus retained no-op branches, including retained iOS canvas tip behavior, retained non-Windows VR unsupported/no-op behavior, and retained macOS PPBR export directory override; `pp_platform_api` also owns the default network TLS policy helper consumed by retained curl sites that cannot yet depend on injected services | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace `src/platform_legacy/legacy_platform_services.*` with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | | DEBT-0019 | Open | Modernization | Unreferenced-parameter warnings are muted globally through `pp_project_warnings` with MSVC `/wd4100` and Clang/GCC `-Wno-unused-parameter` | Legacy callbacks, virtual hooks, serializer methods, and platform/API compatibility functions carry many intentionally unused parameters during the component split; muting this keeps stricter warning builds focused on higher-signal migration issues | `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset linux-clang --target pp_foundation` | Remove `/wd4100` and `-Wno-unused-parameter`, mark intentionally unused parameters with names/comments or `[[maybe_unused]]`, and make the Windows app plus headless Clang/GCC tests pass without unreferenced-parameter warnings | | DEBT-0020 | Open | Modernization | Document resize dialog state, selected-resolution planning, and execution dispatch now consume pure `pp_app_core` through `NodeDialogResize`, `App::dialog_resize`, `pano_cli plan-document-resize`, and the `DocumentResizeServices` boundary, and live resize shares `src/legacy_document_canvas_services.*` with canvas clear commands; resize history clearing is an explicit app-core execution output implemented directly by the retained `ActionManager`, but the shared live bridge still calls legacy `Canvas::resize`, updates the legacy app title, and owns the retained `ActionManager` call site | Preserve existing layer/frame GPU resize behavior while the document model and canvas execution boundary are extracted incrementally | `pp_app_core_document_resize_tests`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `ctest --preset desktop-fast --build-config Debug` | Document resize execution is owned by injected document/app services with no legacy resize adapter, title shim, or retained `ActionManager` history clearing | | DEBT-0021 | Open | Modernization | Layer rename planning/execution dispatch, layer panel operation planning/execution dispatch, layer panel selected-control/visibility view projection, and explicit layer history-intent helpers now consume pure `pp_app_core` through `App::dialog_layer_rename`, `App::init_sidebar` layer callbacks, `NodePanelLayer::update_attributes()`, `pano_cli plan-layer-rename`, `pano_cli plan-layer-operation`, `pano_cli plan-layer-panel-view`, `DocumentLayerRenameServices`, and `DocumentLayerOperationServices`, and the live execution adapters are centralized in `src/legacy_document_layer_services.*`; rename now records undo before applying the new name through separate app-core service calls, but the shared bridge and panel adapter still mutate legacy `Canvas` layer state, `NodeLayer`/`NodePanelLayer`, and retained `ActionManager` undo entries for add/remove/property-change/clear paths | Preserve existing UI/canvas behavior while document layer commands, panel projection, and undo history are extracted incrementally | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-operation --kind add --layer-count 2 --index 1 --name Paint`; `pano_cli plan-layer-panel-view --layer-count 3 --current-index 1 --hidden-index 2 --locked-index 1 --current-opacity 0.25 --current-blend-mode 4`; `ctest --preset desktop-fast --build-config Debug` | Layer command execution and panel state projection are owned by the document/app command boundary with legacy `Canvas`/UI nodes acting only as adapters or removed entirely | | DEBT-0022 | Open | Modernization | Animation panel frame command planning, panel action planning, panel view-model projection, timeline scrub planning, panel-control/timeline execution dispatch, selected-frame click dispatch, playback tick stepping, and play-mode toggles now consume pure `pp_app_core` through `NodePanelAnimation`, `NodeAnimationTimeline`, `pano_cli plan-animation-operation`, `pano_cli plan-animation-panel-action`, `pano_cli plan-animation-panel-view`, `pano_cli plan-animation-timeline-scrub`, and `DocumentAnimationServices`; live execution is centralized in `src/legacy_document_animation_services.*`, but that bridge still mutates or reads legacy `Canvas`/`Layer` frame state, canvas mode, animation-panel timeline/playback fields, and uses a temporary `NodePanelAnimation` friend adapter | Preserve existing animation panel behavior while timeline/frame commands move toward the document/app command boundary | `pp_app_core_document_animation_tests`; `pano_cli plan-animation-operation --kind add --frame-count 2 --current-frame 0`; `pano_cli plan-animation-operation --kind select --frame-count 3 --selected-frame 1 --layer-index 2 --layer-id 42`; `pano_cli plan-animation-operation --kind playback --total-duration 5 --current-frame 4 --offset 1`; `pano_cli plan-animation-operation --kind toggle-playback --playing`; `pano_cli plan-animation-panel-action --action next --total-duration 5 --current-frame 4`; `pano_cli plan-animation-panel-view --layer-count 2 --frame-count 3 --total-duration 6 --current-layer 1 --current-frame 4`; `pano_cli plan-animation-timeline-scrub --total-duration 5 --cursor-x 174.99`; `ctest --preset desktop-fast --build-config Debug` | Animation frame/timeline/playback execution is owned by injected document/app timeline services with no legacy `Canvas`/`Layer`/canvas-mode adapter and UI nodes acting only as adapters or removed entirely | | DEBT-0023 | Open | Modernization | Brush/color/preset/stroke-settings UI planning, texture-list add/remove/reorder planning, brush preset-list add/select/move/remove/clear planning, stroke-panel slider/toggle/blend/reset planning, stroke-panel view projection, app-level brush refresh view projection, and execution dispatch now consume pure `pp_app_core` through `App::init_sidebar`, `App::brush_update()`, `NodePanelBrush`, `NodePanelBrushPreset`, `NodePanelStroke`, `NodePanelStroke::update_controls()`, restored/docked floating-panel callbacks, `pano_cli plan-brush-operation`, `pano_cli plan-brush-refresh`, `pano_cli plan-brush-texture-list`, `pano_cli plan-brush-preset-list`, `pano_cli plan-brush-stroke-control`, `pano_cli plan-brush-stroke-panel-view`, `BrushUiServices`, `BrushTextureListServices`, `BrushPresetListServices`, and `BrushStrokeControlServices`, and live execution is centralized in `src/legacy_brush_ui_services.*` or narrow legacy service bridges where possible, but preset-list execution still mutates legacy `NodePanelBrushPreset` child nodes directly while the bridge and panel adapter still mutate/read legacy `Brush`/`Canvas::I`, load/save legacy brush texture images, apply retained legacy quick/stroke/color widget writes, own brush thumbnail paths and popup behavior, and use temporary `NodePanelBrush`/`NodePanelBrushPreset` friend adapters to reach private list state | Preserve existing brush UI behavior while brush commands and view projection move toward a brush/app/asset command boundary and asset-managed texture/preset selection | `pp_app_core_brush_ui_tests`; `pano_cli plan-brush-operation --kind color --r 0.25 --g 0.5 --b 0.75 --a 1`; `pano_cli plan-brush-operation --kind pattern --path data/patterns/noise.png --thumb data/patterns/thumbs/noise.png`; `pano_cli plan-brush-refresh --floating-picker --tip-flow 0.8 --tip-size 48 --r 0.2 --g 0.3 --b 0.4 --a 1`; `pano_cli plan-brush-texture-list --kind add --dir brushes --data-path data --source C:/Temp/soft.png`; `pano_cli plan-brush-preset-list --kind remove --item-count 1 --current-index 0`; `pano_cli plan-brush-stroke-control --kind float --setting tip-size --value 42.5`; `pano_cli plan-brush-stroke-control --kind blend --setting pattern --blend-mode 3`; `pano_cli plan-brush-stroke-panel-view --tip-size 64 --jitter-scatter 0.4 --dual-disabled --tip-blend-mode 2 --pattern-blend-mode 5`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset/stroke-settings, texture-list, preset-list, stroke-control execution, stroke-panel projection, and brush refresh projection are owned by injected brush/app/asset/UI services with no legacy brush/canvas adapter, direct `NodePanelBrushPreset` child mutation, brush thumbnail/popup ownership, legacy quick/stroke/color widget writes, or brush-panel friend access | | DEBT-0024 | Open | Modernization | Grid/heightmap/lightmap UI planning and execution dispatch now consume pure `pp_app_core` through `NodePanelGrid`, `pano_cli plan-grid-operation`, and the `GridUiServices` boundary; live execution is centralized in `src/legacy_grid_ui_services.*`, and retained CPU lightmap row dispatch now uses shared `parallel_for` instead of platform-specific Win32/Apple worker APIs, but the bridge still performs legacy image loading, OpenGL texture updates, nanort lightmap baking/progress, and `Canvas::draw_objects` commit execution | Preserve grid/lightmap behavior while moving renderable grid commands toward app/renderer/document boundaries | `pp_app_core_grid_ui_tests`; `pano_cli plan-grid-operation --kind render --float32 --texture-resolution 1024 --samples 32`; `ctest --preset desktop-fast --build-config Debug` | Grid heightmap/lightmap execution is owned by app/renderer/document services with `NodePanelGrid` acting only as UI adapter | | DEBT-0025 | Open | Modernization | Quick brush/color slot, mini-state, and size/flow slider preview planning now consume pure `pp_app_core` through `NodePanelQuick`, `pano_cli plan-quick-operation`, `pano_cli plan-quick-slider-preview`, and the `QuickUiServices` boundary; live slot/popup/restore/reset execution is centralized in `src/legacy_quick_ui_services.*`, and live slider callbacks now consume `pp_app_core` preview cursor/tip planning directly, but the bridge and panel adapter still mutate legacy quick UI widgets, `Brush` previews, color picker popup state, preset popup state, and direct legacy `CanvasModePen`/`CanvasModeLine` fields | Preserve quick-panel behavior while quick brush/color and slider preview commands move toward a brush/app command boundary with safer automation coverage | `pp_app_core_quick_ui_tests`; `pano_cli plan-quick-operation --kind brush --current-index 0 --slot-index 2`; `pano_cli plan-quick-operation --kind restore --brush-index 2 --color-index 1 --fire-event`; `pano_cli plan-quick-slider-preview --slider-x 10 --slider-y 20 --slider-height 40 --zoom 2 --pen-mode --no-line-mode`; `ctest --preset desktop-fast --build-config Debug` | Quick-panel selection, popup, restore, reset, brush preview, color execution, and slider preview mode updates are owned by injected app/brush/UI/canvas services with no legacy quick-panel adapter, popup adapter, direct brush-preview mutation, or direct `CanvasMode*` field writes | | DEBT-0026 | Open | Modernization | Toolbar history command planning and canvas hotkey history dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `NodeCanvas`, `pano_cli plan-history-operation`, and the `HistoryUiServices` boundary, and both live callers share `src/legacy_history_services.*` for saturated legacy history metrics and execution, but the shared live bridge still mutates legacy `ActionManager` stacks directly | Preserve undo/redo/clear behavior while moving action history toward document/app command services | `pp_app_core_history_ui_tests`; `pano_cli plan-history-operation --kind undo --undo-count 2`; `pano_cli plan-history-operation --kind clear --undo-count 2 --redo-count 1 --memory-bytes 4096`; `ctest --preset desktop-fast --build-config Debug` | Undo/redo/clear execution is owned by injected document/app history services with no legacy `ActionManager` adapter | | DEBT-0027 | Open | Modernization | Canvas draw-tool toolbar command, full draw-toolbar binding planning, canvas input mode switching, active-state planning/execution dispatch, canvas cursor visibility planning, and canvas keyboard/touch command planning now consume pure `pp_app_core` through `App::init_toolbar_draw`, `App::update`, `NodeCanvas`, `pano_cli plan-canvas-tool`, `pano_cli plan-canvas-tool-toolbar`, `pano_cli plan-canvas-tool-state`, `pano_cli plan-canvas-cursor`, `pano_cli plan-canvas-hotkey`, `CanvasToolServices`, and `CanvasHotkeyServices`, live toolbar/input/hotkey execution is centralized in `src/legacy_canvas_tool_services.*`, `NodeCanvas::update_cursor()` consumes the cursor planner before retained platform cursor dispatch, and canvas mode tip visibility plus pressure remapping now route through `PlatformServices`, but the bridge still mutates or reads legacy `Canvas` mode state, `CanvasModePen` drawing/resizing/picking state, touch-lock state, transform copy/cut action objects, `ActionManager`, legacy save UI, legacy stroke size controls, retained `NodeButton`/`NodeButtonCustom` lookup, and cursor/UI singletons | Preserve current toolbar, stylus eraser, cursor visibility, keyboard, and touch command behavior while canvas input/tools move toward an app/document command boundary | `pp_app_core_canvas_tool_ui_tests`; `pp_app_core_canvas_hotkey_tests`; `pp_platform_api_tests`; `pano_cli plan-canvas-tool --kind copy`; `pano_cli plan-canvas-tool-toolbar`; `pano_cli plan-canvas-tool-state --mode draw --picking --touch-lock`; `pano_cli plan-canvas-cursor --mode draw --visibility small-brush --brush-size 9.5`; `pano_cli plan-canvas-cursor --visibility small-brush --bad-size`; `pano_cli plan-canvas-hotkey --event key-up --key z --ctrl --undo-count 2`; `pano_cli plan-canvas-hotkey --event key-up --key s --ctrl --shift`; `ctest --preset desktop-fast --build-config Debug` | Canvas tool selection, toolbar binding, toolbar state refresh, picking, touch lock, stylus eraser/key mode switching, cursor visibility planning/execution, hotkey/touch command dispatch, save hotkeys, history hotkeys, brush-size hotkeys, and transform action execution are owned by injected app/document/canvas services with no legacy toolbar/canvas adapter | | DEBT-0028 | Open | Modernization | Canvas clear command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, Layer menu clear, `pano_cli plan-canvas-clear`, and the `DocumentCanvasClearServices` boundary, and toolbar/Layer-menu clear share `src/legacy_document_canvas_services.*`, but the shared live bridge still calls legacy `Canvas::clear`, which records `ActionLayerClear`, clears the current layer/frame, and marks legacy `Canvas::I` unsaved | Preserve clear-current-layer behavior while canvas/document commands move toward document/app command services | `pp_app_core_document_canvas_tests`; `pano_cli plan-canvas-clear --r 0 --g 0.1 --b 0.2 --a 0.3`; `pano_cli plan-canvas-clear --no-canvas`; `pano_cli plan-layer-menu --command clear --current-index 1 --current-name Paint`; `ctest --preset desktop-fast --build-config Debug` | Canvas clear execution, undo recording, dirty-state updates, and clear color handling are owned by injected document/app services with no legacy canvas-clear adapter | | DEBT-0029 | Open | Modernization | Image import route planning and execution dispatch now consume pure `pp_app_core` through the File menu, `pano_cli plan-image-import`, and the `DocumentImageImportServices` boundary, and live File-menu import execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still loads images with legacy `Image`, calls legacy `Canvas::import_equirectangular`, or configures legacy import transform mode directly | Preserve current File > Import behavior while image import moves toward document/app/asset command services | `pp_app_core_document_import_tests`; `pano_cli plan-image-import --width 4096 --height 2048`; `pano_cli plan-image-import --width 1024 --height 1024`; `ctest --preset desktop-fast --build-config Debug` | Image loading, equirectangular import, transform-placement import, and failure reporting are owned by injected document/app/asset services with File-menu callbacks acting only as adapters and no legacy image-import adapter | | DEBT-0030 | Open | Modernization | File export menu action planning and execution dispatch now consume pure `pp_app_core` through the File menu, `pano_cli plan-export-menu`, and the `DocumentExportMenuServices` boundary, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still opens legacy export dialogs and then reaches legacy canvas/render/video export code | Preserve current export menu behavior while export command execution moves toward document/app/renderer/video services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind png`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-menu --kind layers --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Export menu routing, license gating, target creation, image/layer/cube/depth/animation/timelapse execution, and error reporting are owned by injected document/app/renderer/video services with File-menu callbacks acting only as UI adapters and no legacy export adapter | | DEBT-0031 | Open | Modernization | Top-level File menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_file`, `pano_cli plan-file-menu`, and the `FileMenuServices` boundary, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still invokes legacy dialogs, platform pickers, cloud code, share code, and canvas import/export paths directly | Preserve File menu behavior while app workflows move toward app/document/platform command services | `pp_app_core_file_menu_tests`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-file-menu --command import`; `pano_cli plan-file-menu --command cloud-upload`; `ctest --preset desktop-fast --build-config Debug` | File menu routing, picker dispatch, save/share/cloud/resize/export execution, and image/project import execution are owned by injected app/document/platform services with `App::init_menu_file` acting only as a UI adapter and no legacy File menu adapter | | DEBT-0032 | Open | Modernization | Layer menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_layer`, `pano_cli plan-layer-menu`, and the `DocumentLayerMenuServices` boundary; Layer menu clear reuses the `DocumentCanvasClearServices` executor; and Layer menu rename/clear/merge now share `src/legacy_document_layer_services.*`, but the bridge still calls the legacy rename dialog path, `NodePanelLayer::merge`, and reads `Canvas::I` animation/layer state directly | Preserve existing Layer menu behavior while layer commands move toward document/app services | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-menu --command clear --current-index 1 --current-name Paint`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-layer-merge --layer-count 3 --from-index 2 --to-index 1`; `pano_cli plan-layer-merge --layer-count 3 --from-index 2 --to-index 1 --animation-duration 3`; `pano_cli plan-layer-menu --command rename --no-current-layer`; `ctest --preset desktop-fast --build-config Debug` | Layer rename, merge-down execution, animation gating, and selected-layer state are owned by injected document/app services with Layer-menu callbacks acting only as UI adapters and no legacy Layer menu adapter | | DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, `pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`, `pano_cli plan-canvas-view-cursor-mode`, and the `ToolsMenuServices` boundary, direct command execution is centralized in `src/legacy_app_shell_services.*`, SonarPen availability/startup now routes through `PlatformServices`, and reset-camera, viewport-density, and cursor-mode execution now share `src/legacy_canvas_view_services.*`, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, open legacy shortcuts UI, mutate retained `Canvas` camera/density/cursor state, write retained `Settings`, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu and canvas-view behavior while UI shell actions move toward app/UI/platform/canvas services | `pp_app_core_tools_menu_tests`; `pp_app_core_canvas_view_tests`; `pp_platform_api_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `pano_cli plan-canvas-camera-reset`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, viewport density, cursor mode, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform/canvas services with `App::init_menu_tools` and options callbacks acting only as UI adapters and no legacy Tools/canvas-view adapter | | DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter | | DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary; toolbar test-message dialog metadata now lives in `pp_app_core` through `plan_main_toolbar_message_dialog`, retained message-box creation and settings-dialog opening now route through `src/legacy_ui_overlay_services.*`, history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge still opens legacy open/save flows, stores raw compatibility pointers, and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command message-box`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter | | DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::query_opengl_capability_detection`, `detect_opengl_feature_state`, and `render_device_features` as the backend conversion point; that feature snapshot now includes float32-linear filtering, so canvas stroke texture format selection, renderer diagnostics, grid lightmap render planning, and grid bake target selection no longer read `ShaderManager::ext_*` flags directly. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. Pure document-frame compositing, renderer-neutral six-face texture upload, recorded upload-command reporting, pure six-face PNG encoding, pure equirectangular PNG/JPEG+XMP encoding, and pure layer/animation-frame collection PNG encoding are now covered by `simulate-document-render`, payload-complete `plan-canvas-document-snapshot` automation, and the shared `pp_paint_renderer::prepare_document_frame_export_readiness` helper consumed by live export before retained writer fallback. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, retained `RTT::create`/`RTT::destroy` render-target texture parameter setup, optional depth renderbuffer allocation, framebuffer allocation/attachment/status checks, binding restore, and resource deletion now route through tested `pp_renderer_gl` dispatch, retained RTT clear, masked clear with color-write-mask restore, texture bind/unbind, and RGBA8 dirty-region texture writes now route through tested `pp_renderer_gl` dispatch, retained Canvas, NodeCanvas, and NodeStrokePreview texture-unit switches now route through tested active-texture dispatch, retained Canvas, NodeCanvas, NodeStrokePreview, and desktop HMD viewport/scissor/capability execution now route through tested `pp_renderer_gl` dispatch adapters, retained NodeCanvas, CanvasMode, and NodePanelGrid capability-state snapshots now route through tested `pp_renderer_gl` query dispatch, CanvasLayer cube/equirect generation plus frame clears now share `legacy_ui_gl_dispatch` for active-texture selection, cube texture binding, viewport execution, blend capability execution, clear-color query/restore, and color-buffer clear adapter endpoints backed by tested `pp_renderer_gl` dispatch helpers, while the cube-face framebuffer-to-texture copy now uses the shared retained `copy_framebuffer_to_texture_target` utility bridge backed by tested `pp_renderer_gl` dispatch and remains tracked by DEBT-0036 until renderer services own copy execution, `NodePanelGrid` live heightmap draw and bake setup now route depth/blend state, depth clears, color-write-mask toggles, active texture selection, bake viewport execution, sun-overlay viewport query, and desktop texture-resize readback through tested `pp_renderer_gl` dispatch adapters; its active-texture, depth/blend capability query/apply, viewport query/execution, depth-clear, and color-write-mask adapter endpoints now share `legacy_ui_gl_dispatch`, retained CanvasMode overlay/mask/transform paths now route active texture, depth/blend state, transform/cut viewport execution, paint-mode blend/depth state snapshots, and canvas-tip pick framebuffer readback through tested `pp_renderer_gl` dispatch adapters; their active-texture, capability query/apply, viewport, read-framebuffer query, and RGBA8 pixel-readback adapter endpoints now share `legacy_ui_gl_dispatch`, retained simple UI draw paths now share `legacy_ui_gl_dispatch` for blend-state execution, fallback 2D texture unbinds, `NodeViewport` viewport query/restore, color-buffer clears, and clear-color restore, retained `NodeCanvas` plus `NodeStrokePreview` draw-state paths now share `legacy_ui_gl_dispatch` for active-texture selection, fallback texture unbind, viewport/scissor execution, viewport and clear-color queries, color-buffer clears, clear-color restore, and capability query/apply adapter endpoints backed by tested `pp_renderer_gl` dispatch helpers, retained `Canvas` stroke/object/thumbnail/export/frame-clear state endpoints now share `legacy_ui_gl_dispatch` for active-texture selection, fallback texture unbind, viewport/scissor execution, viewport and clear-color query, clear-color restore, and capability query/apply adapter endpoints, retained Canvas and RTT depth renderbuffer allocation/attachment/delete now share `legacy_gl_renderbuffer_dispatch` while renderer-resource ownership remains retained, retained `Texture2D`, `TextureCube`, and RTT texture allocation/delete/bind/parameter/update/mipmap dispatch now share `legacy_gl_texture_dispatch` while renderer-resource ownership remains retained, retained `Texture2D` readback plus RTT framebuffer allocation/delete/bind/restore/blit/readback and PBO readback dispatch now share `legacy_gl_framebuffer_dispatch` while renderer-resource ownership remains retained, retained Sampler create/parameter/border/bind dispatch shares `legacy_gl_sampler_dispatch` and retained PBO allocation/readback/map/unmap/delete dispatch shares `legacy_gl_pixel_buffer_dispatch` while renderer-resource ownership remains retained, retained `Shape`, `TextMesh`, and `NodeColorWheel` mesh buffer/VAO creation/upload/draw/delete dispatch now share `legacy_gl_mesh_dispatch` while renderer-resource ownership remains retained, retained shader source compilation/deletion, program attach/link/use/delete, attribute rebinding/location lookup, active-uniform enumeration, uniform-location discovery, and uniform writes now share `legacy_gl_shader_dispatch` while shader-program ownership remains retained, retained `gl_state` save/restore and `copy_framebuffer_to_texture_target` now reuse shared retained UI/framebuffer/shader/sampler bridge callbacks while renderer state and framebuffer-copy execution remain retained, retained app startup, app clear, app UI viewport/scissor, command-convert renderer state, and desktop VR draw-state endpoints now share `legacy_ui_gl_dispatch` while app/VR renderer execution remains retained, retained RTT clear and masked-clear endpoints now share `legacy_ui_gl_dispatch` while RTT render-target execution remains retained, retained app startup logging, Windows early context logging/window-title detection, and shader capability detection now share `legacy_gl_runtime_dispatch` while runtime/capability probing remains retained, and retained `CanvasLayer` frame-clear draw-state paths route saved clear-color query and restore through the same tested helpers, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, grid lightmap/heightmap mesh rendering, Web/incomplete-readback collection handoff, depth export writer execution, progress/threading parity, exact GPU filtering parity, and the retained `ShaderManager::ext_*` compatibility fields still use legacy OpenGL canvas/UI execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `pano_cli plan-canvas-document-snapshot --captured-face-payloads-per-layer 1`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer/export compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior | | DEBT-0037 | Open | Modernization | Recording lifecycle/export planning, export progress-dialog metadata, and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, retained MP4 export progress bar creation routes through `src/legacy_app_dialog_services.*`, retained `PBO` allocation/readback/map/unmap/delete operations now route through tested `pp_renderer_gl` dispatch, and the retained recording worker is now owned as `std::jthread` through `App::rec_thread` with explicit stop requests before notify/join, but the bridge still owns retained recording loop control, platform recorded-file cleanup, progress lifetime, `App::rec_loop` readback call sites, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pp_renderer_gl_capabilities_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-deletes-recorded-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback scheduling, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters | | DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning, cloud prompt/progress metadata, and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-transfer`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, cloud-browser dialog opening now routes through `src/legacy_ui_overlay_services.*`, retained upload/download CURL setup, progress callback dispatch, TLS-verification bypass policy consumption, upload form construction, and upload post-transfer response/error handling now also live behind dedicated helpers in `src/legacy_cloud_services.*` instead of `App::upload`/`App::download` or inline transfer setup, retained save-before-upload execution now routes through `src/legacy_document_session_services.*` instead of `src/legacy_cloud_services.cpp` calling `Canvas::I->project_save_thread(...)` directly, retained downloaded-project post-open reconciliation now routes through `src/legacy_document_open_services.*` instead of living inline in `src/legacy_cloud_services.cpp`, upload warning/publish/success prompts, upload/bulk progress dialog titles, download-progress prompt metadata, and formatted download progress text come from tested app-core plans, retained cloud prompt/progress creation routes through `src/legacy_app_dialog_services.*` via `App::message_box`/`App::show_progress` or direct bridge calls, and retained `Asset::open_url`, `LogRemote::net_init`, and `NodeDialogCloud::load_thumbs_thread` curl sites consume the `pp_platform_api` default TLS policy helper instead of spelling Android branches locally; the initial cloud-browser loading placeholder setup, file-list request/response handling, and slot creation/selection wiring in `NodeDialogCloud::load_thumbs_thread()` now also route through focused helpers in `src/node_dialog_cloud.*`, but the bridge still uses retained prompt/progress lifetime, OpenGL context guarding, `NodeDialogCloud` thumbnail execution, and transfer-thread execution while downloaded-project open, layer refresh, and action-history reset remain retained in the document-open bridge | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pp_platform_api_tests`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli plan-cloud-transfer --direction download --progress --disable-tls-verification`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, TLS policy, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters | | DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`; document-open history clearing is now an explicit app-core output, but the bridge still opens ABR/PPBR import prompts before delegating import execution to `src/legacy_brush_package_import_services.*`, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and executes retained history services locally | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter | | DEBT-0040 | Open | Modernization | Close request, document save, save-before-workflow planning/execution dispatch, close/save-before/save-error prompt metadata, and close/save/save-before history effects now consume pure `pp_app_core` through `App::request_close`, `App::save_document`, `App::continue_document_workflow_after_optional_save`, `pano_cli simulate-app-session`, `pano_cli plan-document-session-prompt`, `DocumentSaveServices`, `CloseRequestServices`, `DocumentWorkflowServices`, and `src/legacy_document_session_services.*`; close/save-before prompt creation now uses `src/legacy_app_dialog_services.*`, Save dialog working-directory picker visibility/path formatting now dispatches through `PlatformServices`, and existing-project save/save-before-workflow execution prepares a payload-bearing canvas snapshot report before retained saving, but the bridge still opens retained save dialogs, wires prompt callbacks directly, delegates actual writing to `Canvas::project_save`, mutates the unsaved flag on close confirmation, invokes native app close, and routes save-version through the retained legacy dialog | Preserve current close/save/dirty-workflow behavior while document session execution moves toward app/document/UI/platform services | `pp_app_core_document_session_tests`; `pp_platform_api_tests`; `pano_cli plan-document-session-prompt --kind close-unsaved`; `pano_cli plan-document-session-prompt --kind save-before-workflow`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `ctest --preset desktop-fast --build-config Debug` | Close prompt execution, native close requests, dirty-workflow save prompts, existing-project saves, save dialogs, save-version execution, and unsaved-flag mutation are owned by injected app/document/UI/platform services with `App` methods acting only as adapters | | DEBT-0041 | Open | Modernization | Accepted new-document planning/execution dispatch and new-document overwrite prompt metadata now consume pure `pp_app_core` through `App::dialog_newdoc`, `pano_cli plan-new-document`, `pano_cli plan-document-session-prompt`, `NewDocumentServices`, and `src/legacy_document_session_services.*`; new-document overwrite prompt creation now uses `src/legacy_app_dialog_services.*`, and New Document dialog working-directory picker visibility/path formatting now dispatches through `PlatformServices`, but the bridge still mutates legacy app document fields, clears legacy layer UI, resizes legacy `Canvas`, clears legacy history, creates the default layer through legacy UI, mutates unsaved/new-document flags, updates the title, wires overwrite callbacks directly, and handles keyboard/dialog cleanup directly | Preserve current New Document dialog behavior while document creation moves toward app/document/UI services | `pp_app_core_document_session_tests`; `pp_platform_api_tests`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-session-prompt --kind new-document-overwrite`; `pano_cli simulate-app-session --save-intent save`; `ctest --preset desktop-fast --build-config Debug` | New document creation, overwrite confirmation, canvas/document allocation, default layer creation, history clearing, title updates, dirty/new-document state, and keyboard/dialog cleanup are owned by injected app/document/UI services with `App::dialog_newdoc` acting only as a UI adapter | | DEBT-0042 | Open | Modernization | Accepted Save As and Save Version planning/execution dispatch, Save As overwrite prompt metadata, and Save As/Save Version history effects now consume pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-document-session-prompt`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`; Save As overwrite prompt creation now uses `src/legacy_app_dialog_services.*`, and accepted Save As/Save Version execution prepares a payload-bearing canvas snapshot report before retained saving, but the bridge still wires overwrite callbacks directly, delegates actual writing to legacy `Canvas::project_save`, mutates app document name/path/directory fields, marks version saves dirty before saving, updates the title, and handles keyboard/dialog cleanup directly | Preserve current Save As and Save Version behavior while document persistence moves toward app/document/storage/UI services | `pp_app_core_document_session_tests`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-session-prompt --kind file-overwrite --name demo`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli simulate-app-session --save-intent save-as`; `pano_cli simulate-app-session --save-intent save-version`; `ctest --preset desktop-fast --build-config Debug` | Save As overwrite prompting, project-save execution, app document metadata updates, title updates, version-save dirty-state handling, and keyboard/dialog cleanup are owned by injected app/document/storage/UI services with `App::dialog_save` and `App::dialog_save_ver` acting only as UI adapters | | DEBT-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`; layer/frame dialogs also consume `plan_document_export_collection_target` plus `PlatformServices::uses_work_directory_document_export_collections()` instead of spelling local iOS branches, export success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`, equirectangular/layer/animation-frame/depth/cube-face execution prepares a payload-bearing document snapshot plus the shared `pp_paint_renderer::prepare_document_frame_export_readiness` report, document-snapshot writer-versus-retained fallback routing now comes from tested `pp_app_core` policy including current-platform support consumed by the live bridge, depth export target naming and two-payload write order are covered by tested `pp_app_core` helpers, cube-face export writes the pure face PNG bytes to `pp_app_core` planned work-directory face paths through `execute_document_cube_face_export_write` before falling back to retained Canvas execution on failure, PNG/JPEG equirectangular export writes the pure `pp_paint_renderer` equirectangular payload through `execute_document_export_file_write` before retained fallback, and payload-complete layer/animation-frame collections write pure `pp_paint_renderer` PNG sequences through `execute_document_export_collection_write` before retained fallback, but the bridge still adapts retained filesystem writes/exported-image publishing locally, still calls legacy `Canvas` export methods for Web/incomplete-readback collection exports and depth rendering, creates export directories, handles picker-selected stems, performs Web prepared-file handoff directly, and leaves depth render/readback plus the legacy `.png`/JPEG payload mismatch on the retained path | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pp_platform_api_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli plan-export-target --kind cube-faces --work-dir D:/Paint --doc-name demo`; `pano_cli plan-export-message --kind equirectangular --destination work --detail D:/Paint`; `pano_cli plan-export-report --kind license-disabled`; `pano_cli plan-export-snapshot-route --kind layers-collection --captured-face-payloads 3 --pending-face-payloads 6`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and remaining retained export execution, export-directory creation, Web file handoff, picker-selected stem handling, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters | | DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, and success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`; the asynchronous timelapse path now uses a service-owned `std::jthread` queue with UI-thread success-dialog handoff instead of a detached worker, but the bridge still calls `App::rec_export`, calls `Canvas::export_anim_mp4`, and owns mobile/Web save callbacks | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind animation-mp4 --message "video export path must not be empty"`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, and mobile/Web save callbacks are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters | | DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, and retained preference reads/writes for UI scale, UI-state/RTL, whats-new dialog state, viewport density, cursor mode, VR controllers, and auto-timelapse now route through `src/legacy_preference_storage.*` snapshots/helpers without direct `settings.h` includes or retained preference keys in the UI/dialog/canvas call sites, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and retained `Settings` storage through that adapter; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, whose desktop runtime policy prefers OpenXR while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` under DEBT-0061 | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters | | DEBT-0046 | Open | Modernization | Startup preference/runtime execution and startup resource sequencing now consume pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `pano_cli plan-app-startup-resources`, `AppStartupServices`, `AppStartupResourceServices`, and `src/legacy_app_startup_services.*`, and startup preference load/read/write now routes through `src/legacy_preference_storage.*` with retained startup keys hidden behind `LegacyStartupPreferenceSnapshot`, but the bridge still calls legacy `Settings` storage through that adapter, `App::rec_start`, app VR-controller state mutation, message-box license warning execution, shader loading, asset initialization, layout creation, title updates, and UI render-target creation directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI/renderer services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `pano_cli plan-app-startup-resources --width 1280 --height 720`; `pano_cli plan-app-startup-resources --bad-size`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, startup resource initialization, title updates, and UI render-target allocation are owned by injected app/preferences/storage/recording/UI/renderer services with `App::init` acting only as orchestration | | DEBT-0047 | Open | Modernization | PPBR brush package export request validation, success-dialog metadata, and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, the macOS data-directory override now routes through `PlatformServices`, and the desktop async path now uses a service-owned `std::jthread` worker with UI-thread dialog close/message handoff, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, and handles mobile/Web completion directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and mobile/Web completion are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter | | DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, and the retained bridge now uses a service-owned `std::jthread` worker with UI-thread completion handoff instead of detached `NodePanelBrushPreset::import_abr`/`import_ppbr` launches, but it still depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent | | DEBT-0049 | Open | Modernization | `pp_assets::validate_ppbr_header` intentionally preserves the legacy PPBR version check from `NodePanelBrushPreset::import_ppbr`, which accepts files when either major is `0` or minor is `1` instead of requiring exactly version `0.1` | Avoid rejecting existing brush packages before compatibility fixtures prove the stricter rule is safe | `pp_assets_brush_package_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add PPBR compatibility fixtures for accepted/rejected historical package versions, then require canonical `0.1` or an explicit supported-version matrix and update live import accordingly | | DEBT-0050 | Open | Modernization | iOS exported-image photo-library publishing and WebGL persistent-storage flushing now dispatch through platform service boundaries; the iOS/Web policy decision lives in tested `pp_platform_api::platform_policy`, iOS execution now lives in injected `src/platform_apple/apple_platform_services.*`, and WebGL flushing now goes through injectable `pp::platform::WebPlatformServices`, but retained Web execution still forwards to `webgl_sync` bridges | Preserve current iOS/Web export and save behavior while the Apple/Web platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; platform package smoke once Apple/Web root builds exist | Exported-image publishing and persistent-storage flushing are owned by injected Apple/Web `pp_platform_*` services with no legacy adapter branch | | DEBT-0051 | Open | Modernization | Document browser search roots, Apple file/image/save/directory picker dispatch, Browse dialog working-directory picker visibility/path formatting, iOS Inbox roots, macOS empty-selection filtering, and macOS display-path formatting now dispatch through the tested `src/platform_apple/apple_platform_services.*` boundary consumed by `PlatformServices`; retained `src/platform_legacy/legacy_platform_services.*` still owns other non-Apple fallback behavior | Preserve current iOS document import/browse and desktop browse picker behavior while Apple platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Apple package smoke once root Apple builds exist | Document browse roots and browse-directory picker/display formatting are owned by injected Apple and desktop `pp_platform_*` services with no legacy adapter branch | | DEBT-0052 | Open | Modernization | Native UI/window state saving now dispatches through `PlatformServices`; Windows/macOS save policy lives in tested `pp_platform_api::platform_policy`, macOS execution now lives in injected `src/platform_apple/apple_platform_services.*`, and Windows placement reads/writes now use `LegacyWindowPreferenceSnapshot` plus `src/legacy_preference_storage.*`, but Windows still stores placement through retained `Settings` behind the adapter | Preserve current Windows/macOS UI persistence while platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Windows app build; Apple package smoke once root Apple builds exist | UI/window state persistence is owned by injected platform services with no legacy adapter branch | | DEBT-0053 | Open | Modernization | Prepared-file writable target selection and prepared-file export-dialog policy now dispatch through platform service boundaries; iOS temporary-file policy plus prepared-file save handoff now live in injected `src/platform_apple/apple_platform_services.*`, WebGL data-path planning lives in tested `pp_platform_api::platform_policy`, and WebGL prepared-file handoff now goes through injectable `pp::platform::WebPlatformServices`, but retained Web save/download handoff execution still lives in fallback adapters | Preserve mobile/Web export handoff behavior while platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; Windows app build; Apple/Web package smoke once root package builds exist | Prepared-file target selection, export-dialog policy, and save/download handoff are owned by injected platform services with no legacy adapter branch | | DEBT-0054 | Open | Modernization | Layout XML file read/reload decisions now consume `pp_platform_api::plan_asset_file_load`; platform-family reload behavior lives in tested `pp_platform_api::platform_policy` and pure probed planning, but the live wrapper still performs direct `stat` probing for Windows/macOS mtime reload checks until platform storage/file-watch services exist | Preserve current layout hot-reload and mobile/Web single-load behavior while removing platform guards from the shared `LayoutManager` parser | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build | Layout reload decisions are owned by injected platform storage/file-watch services or an asset manager boundary with platform-specific file watching removed from compile-time helpers | | DEBT-0055 | Open | Modernization | `src/app.h` now forward-declares retained iOS/macOS/Android/Linux/Web platform handles instead of including platform SDK headers, and full SDK includes are isolated in `src/platform_legacy/legacy_platform_services.cpp`, but the `App` singleton still stores those platform handles directly | Reduce central header platform coupling incrementally without rewriting non-Windows platform entrypoints before Phase 6 | Windows app build; Apple/Android/Linux/Web package smoke once platform root builds are active | Platform handles are owned by injected `pp_platform_*` shell state or services, and `App` has no platform SDK handle fields or platform conditional members | | DEBT-0056 | Open | Modernization | `src/asset.h` is now Android-SDK-free and uses opaque Android asset handles behind `Asset::set_android_asset_manager`, but retained `Asset` still owns a static Android asset-manager bridge and `src/asset.cpp` still performs Android `AAssetManager` reads directly; the current `android-arm64` root preset is headless and does not expose `pp_legacy_assets_io`, though the retained Android standard package `native-lib` now builds through its refreshed C++23 CMake path | Reduce legacy asset I/O header coupling without rewriting Android asset loading before the asset manager/storage boundary exists | Windows app build; `powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64 -Targets pp_assets`; `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard` | Android asset loading is owned by injected asset storage/platform services or `pp_assets` file providers, with no static Android asset manager on `Asset` | | DEBT-0061 | Open | Modernization | Desktop XR runtime selection now lives in tested `pp_platform_api` policy and prefers OpenXR, but `WindowsPlatformServices` still reports OpenXR unavailable and reaches the retained OpenVR SDK bridge as a legacy fallback; Windows runtime deployment copies `openvr_api.dll` beside `PanoPainter.exe` until that fallback is removed | Preserve current desktop VR behavior while replacing OpenVR with OpenXR behind the platform/renderer boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add an OpenXR SDK/package target, implement desktop OpenXR startup/shutdown/pose/controller submission behind `pp_platform_vr` or `PlatformServices`, validate parity with mocked/runtime smoke coverage, and remove `libs/openvr` plus the OpenVR link/include paths from root CMake | | DEBT-0063 | Open | Modernization | `pp_ui_core` now owns tested `NodeLifetimeTree` and `UiOverlayLifetime` models for checked handles, scoped callback connections, subtree destruction, capture release, and mutation-safe dispatch. `src/legacy_ui_overlay_services.*` now keeps a root-scoped checked-overlay registry for retained nodes; app-owned progress/message/input dialogs created through that service now also open through checked overlay handles when the main layout anchor is available; the main-toolbar settings dialog and cloud browser dialog now open through that same checked-overlay seam; and `src/node_panel_layer.h/.cpp`, `src/node_combobox.cpp`, `src/node_dialog_open.cpp`, `src/node_dialog_browse.cpp`, `src/node_panel_stroke.cpp`, `src/node_panel_brush.cpp`, `src/node_panel_color.cpp`, `src/node_panel_grid.cpp`, `src/node_dialog_picker.cpp`, `src/legacy_quick_ui_services.cpp`, and `src/node_popup_menu.h/.cpp` now route popup/dialog/menu lifetime through checked overlay handles or handle-based close instead of raw attach-and-destroy callbacks. Layer-panel selection/order state also stays on shared ownership rather than raw child pointers while mutation-safe close remains open in other legacy panel/dialog families | Preserve current UI behavior while completing safe panel/dialog lifetime migration incrementally | `pp_ui_core_layout_xml_tests`; `pp_ui_core_node_lifetime_tests`; `pp_ui_core_overlay_lifetime_tests`; `tests/ui_core/node_lifetime_tests.cpp:destroy_subtree_clears_child_connections`; `tests/ui_core/overlay_lifetime_tests.cpp:double_close_overlay_returns_invalid_argument`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_app pp_ui_core_node_lifetime_tests pp_ui_core_overlay_lifetime_tests` | Remaining legacy popup/dialog families still use non-handle ownership and open lifetimes; migration stays open until their surfaces are converted, including lifecycle safety parity checks | | DEBT-0057 | Open | Modernization | Default canvas allocation size now dispatches through `PlatformServices::default_canvas_resolution`, removing the `CANVAS_RES` platform macro from `src/canvas.h`; WebGL's retained 512 default now lives in tested `pp_platform_api` policy behind injectable `pp::platform::WebPlatformServices`, but the Web shell still reaches the default implementation through the retained fallback until a dedicated Web service is injected directly | Preserve WebGL memory behavior while moving canvas creation policy out of shared canvas headers and into the platform boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build; WebGL package smoke once root Web build exists | Default canvas resolution is owned by injected `pp_platform_*` services for every supported platform, with no WebGL branch in the legacy fallback | | DEBT-0058 | Open | Modernization | App-level progress/message/input dialog metadata, including message-dialog OK/cancel captions, now consumes pure `pp_app_core` through `App::show_progress`, `App::message_box`, `App::input_box`, `pano_cli plan-app-dialog`, and `pp_app_core_app_dialog_tests`; live execution is centralized in `src/legacy_app_dialog_services.*`, retained dialog opening now routes through `src/legacy_ui_overlay_services.*` with checked-handle registration when the main layout anchor exists, and whats-new dialog state persistence routes through `src/legacy_preference_storage.*`, but the bridge still creates retained `NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` instances and still relies on legacy callback/node implementations for final close behavior | Preserve current app-shell dialog behavior while moving shared dialog policy toward UI/app services | `pp_app_core_app_dialog_tests`; `pano_cli plan-app-dialog --kind progress --total -4`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-dialog --kind input --ok-caption Save`; `ctest --preset desktop-fast --build-config Debug`; Windows app build | Progress/message/input dialog creation, callback wiring, layout insertion, lifetime ownership, and headless automation are owned by injected app/UI services with `App` methods acting only as adapters | | DEBT-0059 | Open | Modernization | iOS root CMake headless builds assign generated bundle identifiers and disable code signing for executable test/tool targets | The current Apple gate is compile validation for shared component targets; signed iOS app/package validation is not migrated to root CMake yet | `powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos,ios-simulator,ios-device`; `sh scripts/automation/platform-build.sh "ios-device"` on `panopainter-mac` | Root CMake owns the signed Apple app/package targets, package-smoke validates Apple bundles where signing material is available, and headless iOS test/tool targets are either excluded from signed package builds or use explicit test-runner signing policy | | DEBT-0064 | Open | Modernization | Stroke-preview result copy now routes through the renderer-facing `pp::paint_renderer::copy_stroke_preview_result_to_texture(...)` callback-only contract in `src/paint_renderer/compositor.h`; the node-level copy wrapper, final-composite wrapper, and pass-sequence wrapper are removed, and `pp_paint_renderer_compositor_tests` no longer needs either a test-local `Texture2D::bind()` definition. Live preview still owns concrete texture binding and OpenGL execution around the remaining preview flow | `tests` are configured before `pp_legacy_engine`, and linking the app legacy texture object into this planner test would still couple the renderer-neutral test target back to the app target graph | `cmake --build --preset windows-msvc-default --config Debug --target pp_paint_renderer_compositor_tests`; `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor" --output-on-failure` | Keep preview-copy behavior on renderer-facing callback-only seams and move the remaining live preview copy execution behind renderer-owned services so the remaining preview flow can become fully app-texture-free | ## Closed Debt | ID | Status | Owner | Item | Reason | Validation | Removal Condition | | --- | --- | --- | --- | --- | --- | --- | | DEBT-0060 | Closed | Modernization | Retained Android package CMake generated a patched `nanort.h` overlay in the build tree for `native-lib` | Current SDK Manager NDK/Clang rejects `TriangleSAHPred::operator=` assigning to a `const size_t` member | Closed on 2026-06-12: `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard` | Closed on 2026-06-12: Android package configure applies the tracked nanort source compatibility patch, standard/Quest/Focus package CMake files include `libs/nanort` directly, Android/GLES avoids desktop debug callback symbols, and the standard retained package links with the current app-service source list | | DEBT-0062 | Closed | Modernization | VS 2026 builds generated a patched fmt `format.h` overlay in the build tree for `pp_legacy_vendor` | VS 2026's STL no longer exposes the legacy checked-array iterator used by the old fmt release | Closed on 2026-06-12: VS-bundled CMake build of `PanoPainter` and `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure` | Closed on 2026-06-12: the generated overlay was removed, stale overlay directories are deleted during configure, and VS 2026 retained fmt sources use a generated forced-include compatibility header to keep fmt out of the removed checked-array-iterator branch | | DEBT-0006 | Closed | Modernization | `pano_cli create-document` validates and emits JSON command contracts but does not yet invoke the legacy document/app model | The document model had not been extracted from `Canvas`/`App` yet | `ctest --preset desktop-fast --build-config Debug`; `pano_cli_create_document_smoke` | Closed on 2026-05-31: command now constructs a real `pp_document::CanvasDocument` | | DEBT-0018 | Closed | Modernization | `pp_renderer_gl` owned a tested `OpenGlInitialState` plan for PanoPainter startup depth/blend policy, but `App::init` still executed the plan through direct OpenGL calls | Preserve behavior while moving renderer policy into the backend boundary before a live `IRenderDevice`/command context owns startup execution | `pp_renderer_gl_capabilities_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Closed on 2026-06-03: `pp_renderer_gl::apply_panopainter_initial_state` now applies the startup state through a tested backend dispatch contract consumed by `App::init` |