Files
panopainter/docs/modernization/debt.md

2682 lines
270 KiB
Markdown

# 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-0039` was narrowed again. `App::dialog_browse()` in
`src/app_dialogs_workflow.cpp` now delegates retained browse-dialog button
wiring and selected-path open execution through
`src/legacy_document_open_services.*`, so the app dialog shell no longer
owns that document-open handoff inline while retained dialog creation,
unsaved-project prompting, project-open execution, and title/layer refresh
still remain.
- 2026-06-17: `DEBT-0003` was narrowed again. `App::update()` in
`src/legacy_app_runtime_shell_services.cpp` no longer owns retained
app-frame layout-update and canvas-toolbar refresh execution inline;
`src/legacy_app_frame_services.*` now owns that frame-update execution
behind an explicit `App&` plus `AppFrameUpdatePlan` seam, while draw-time
execution, UI observer walking, and broader app-frame ownership still
remain.
- 2026-06-17: `DEBT-0003` was narrowed again. `App::tick()` and
`App::resize()` in `src/app_events.cpp` no longer own retained app-frame
tick and surface-resize execution inline; `src/legacy_app_frame_services.*`
now owns the tick/resize execution against explicit `App&` plus frame plans,
while broader event dispatch and runtime/platform frame ownership still
remain.
- 2026-06-17: `DEBT-0045` was narrowed again.
`src/app_layout_ui_state.cpp` no longer owns retained floating/docked panel
save/restore serialization inline; that panel-persistence family now lives in
`src/legacy_app_ui_state_services.*`, while RTL direction execution and
broader preference/runtime ownership still remain.
- 2026-06-17: `DEBT-0035` was narrowed again.
`src/app_layout_main_toolbar.cpp` no longer owns the retained main-toolbar
button wiring inline; `src/legacy_main_toolbar_binding_services.*` now owns
the per-button bindings behind explicit `App&` plus toolbar-root
dependencies, while the app-layout file remains a thin layout-root adapter
and retained toolbar execution still lives in
`src/legacy_app_shell_services.*`.
- 2026-06-17: `DEBT-0031`/`DEBT-0030` were narrowed again.
`src/app_layout_file_menu.cpp` no longer owns the retained File-menu popup,
export-submenu popup, or button wiring inline; that binding now lives in
`src/legacy_file_menu_binding_services.*`, while retained file/export command
execution still lives in `src/legacy_app_shell_services.*`.
- 2026-06-17: `DEBT-0034` was narrowed again.
`src/app_layout_about_layer_menu.cpp` no longer owns the retained About-menu
popup creation, version-label setup, or button wiring inline; that binding
now lives in `src/legacy_about_menu_binding_services.*`, while retained About
command execution and diagnostics still live in
`src/legacy_app_shell_services.*`.
- 2026-06-17: `DEBT-0033` was narrowed again.
`src/app_layout_tools_menu.cpp` no longer owns the retained Tools > Panels
submenu popup flow or panel-item wiring inline; that binding now lives in
`src/legacy_tools_menu_binding_services.*`, while retained Tools command
execution and options-menu preference wiring still remain in the app shell.
- 2026-06-17: `DEBT-0063` was narrowed again.
`src/app_layout_sidebar.cpp` no longer owns the retained color-popup open /
close wiring inline; that binding now lives in
`src/legacy_sidebar_color_popup_services.*` with explicit `App&`,
popup-root, trigger-button, and panel dependencies, while retained
stroke/grid/layer popup families still remain on the sidebar shell.
- 2026-06-17: `DEBT-0038` was narrowed again. The retained cloud upload and
download background execution in `src/legacy_cloud_services.cpp` now routes
through `AppRuntime::canvas_async_task` instead of a file-static worker
singleton, while retained prompt/progress lifetime, OpenGL context guards,
thumbnail loading, and transfer execution still remain in the cloud bridge.
- 2026-06-17: `DEBT-0038` was narrowed again. The retained cloud-browse dialog
in `src/node_dialog_cloud.cpp` no longer mutates the legacy UI tree directly
from its thumbnail loader worker; file-list population, error text updates,
and thumbnail attachment now queue onto `AppRuntime`'s UI task path and drop
safely when the dialog is already closed, reducing a cancel-time deadlock
risk while the broader retained cloud dialog and transfer flow still remain.
- 2026-06-17: `DEBT-0031`/`DEBT-0030` were narrowed again.
`src/legacy_file_menu_binding_services.cpp` no longer stores File-menu popup
callbacks that capture a stack-local binding service object through `this`;
retained File-menu and export-submenu actions now capture explicit `App&`,
popup root, and overlay handles, removing a startup/runtime lifetime hazard
while retained file/export execution still lives in the app shell.
- 2026-06-17: `DEBT-0048` was narrowed again. The retained ABR/PPBR import path
in `src/legacy_brush_package_import_services.cpp` now uses
`AppRuntime::canvas_async_task` instead of a file-static worker singleton,
while the legacy preset panel remains the importer/storage owner and broader
brush asset/UI ownership transfer still remains open.
- 2026-06-17: `DEBT-0044` was narrowed again. The retained asynchronous
timelapse export path in `src/legacy_document_export_services.cpp` now routes
through `AppRuntime::canvas_async_task` instead of a file-static worker
singleton, while retained `App::rec_export`, animation MP4 execution, and
mobile/Web completion paths still remain in the video-export bridge.
- 2026-06-17: `DEBT-0036` was narrowed again. `src/canvas_layer.cpp` now
routes retained `Layer` / `LayerFrame` render queueing through
`src/renderer_gl/render_runtime_dispatch.h` instead of direct
`App::I->render_task*` calls; retained `Canvas::I` draw-state use and the
broader canvas renderer execution remain.
- 2026-06-17: `DEBT-0034` was narrowed again.
`LegacyAboutMenuServices::run_performance_test(...)` in
`src/legacy_app_shell_services.cpp` now delegates the retained stroke/timing
workflow to `src/legacy_app_shell_performance_test_services.*`, so the About
menu adapter no longer owns the inline render-task/canvas execution body.
- 2026-06-17: `DEBT-0040`/`DEBT-0042` were narrowed again. `App::dialog_save()`
and `App::dialog_save_ver()` in `src/app_dialogs_workflow.cpp` now delegate
Save As / Save Version acceptance through
`src/legacy_document_session_services.*`, so the app dialog shell no longer
owns the retained plan/target lookup entrypoints for those save paths.
- 2026-06-17: `DEBT-0047` was narrowed again. The desktop async PPBR export
path in `src/legacy_brush_package_export_services.cpp` now uses
`AppRuntime::canvas_async_task` plus named completion helpers instead of a
file-static worker singleton, while retained preset-panel execution and
dialog-owned request extraction still remain.
- 2026-06-17: `DEBT-0036` was narrowed again. Retained `Shape`,
`src/shader.cpp`, and `src/font.cpp` now queue render-thread create/update/
destroy work through `src/renderer_gl/render_runtime_dispatch.h` instead of
calling `App::I` directly, and `Shader` render-thread entry points now assert
that explicit runtime contract while shader-program and mesh ownership remain
retained.
- 2026-06-17: `DEBT-0036` was narrowed again. `NodeStrokePreview` no longer
owns static worker-thread, queue, or render-mutex state directly;
`src/legacy_node_stroke_preview_runtime_services.*` now owns the retained
preview worker lifecycle and queue helpers, leaving the node as a thinner
adapter while the retained preview runtime still reaches app/render helpers
internally.
- 2026-06-17: `DEBT-0029` was narrowed again.
`LegacyFileMenuServices::pick_image_for_import()` in
`src/legacy_app_shell_services.cpp` now delegates retained import execution
to `src/legacy_document_image_import_services.*`, so the File-menu adapter no
longer defines the live `DocumentImageImportServices` implementation inline;
legacy `Image` loading and retained canvas/import-mode execution still remain.
- 2026-06-17: `DEBT-0003` was narrowed again. `src/app_runtime.cpp` and
`src/app_runtime.h` now synchronize render/UI worker running state, reject
cross-thread work once those workers stop, and drain queued work through
explicit runtime-owned post/shutdown semantics consumed by
`src/legacy_app_runtime_shell_services.cpp`; broader singleton reach, app
shell ownership, and remaining runtime service adoption still remain.
- 2026-06-17: `DEBT-0017` was narrowed again.
`cmake/PanoPainterSources.cmake` no longer compiles
`${PP_PLATFORM_WEB_SOURCES}` into `PP_PANOPAINTER_APP_SOURCES`, so root app
source ownership no longer mixes live Web platform implementation files into
the retained app group; broader platform entrypoint/package cleanup remains.
- 2026-06-17: `DEBT-0043` was narrowed again.
`src/legacy_canvas_document_io_services.cpp` now delegates the live
equirectangular export family through `src/legacy_document_export_services.*`
instead of carrying that orchestration inline with the broader canvas
document-I/O shell; retained export directory creation, Web handoff, and
remaining legacy `Canvas` export execution still remain.
- 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<Vive>` 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-17: `DEBT-0058`/`DEBT-0063` were narrowed again. The retained
usermanual, changelog, about, What's New, and shortcuts openers in
`src/app_dialogs_info_openers.cpp` now delegate retained dialog construction,
overlay close wiring, and remote-page/button ownership through
`src/legacy_info_dialog_services.*` with explicit `App&` plus overlay-anchor
dependencies, so the app dialog shell no longer owns that info-dialog family
inline.
- 2026-06-17: `DEBT-0058`/`DEBT-0063` were narrowed again. The retained New
Document and Save dialog openers in `src/app_dialogs_workflow.cpp` now
delegate retained dialog construction and button wiring through
`src/legacy_document_session_services.*` with explicit `App&` ownership, so
the app dialog shell no longer owns that document-session dialog family
inline.
- 2026-06-17: `DEBT-0058`/`DEBT-0063` were narrowed again. The retained Open,
Browse, and Resize dialog openers in `src/app_dialogs_workflow.cpp` now
delegate retained dialog construction and overlay/button wiring through
`src/legacy_document_open_services.*` and
`src/legacy_document_session_services.*`, so the app dialog shell no longer
owns those workflow dialog families inline.
- 2026-06-17: `DEBT-0035` was narrowed again. `App::title_update()` and the
draw-toolbar/stroke-popup/grid-popup binding families now delegate through
`src/legacy_app_status_services.*`,
`src/legacy_draw_toolbar_binding_services.*`, and
`src/legacy_sidebar_stroke_popup_services.*`, and
`src/legacy_sidebar_grid_popup_services.*`, so
`src/app_layout.cpp`, `src/app_layout_draw_toolbar.cpp`, and part of
`src/app_layout_sidebar.cpp` are thinner adapters while retained app-shell
execution and the remaining layer popup family still stay open under the same
debt.
- 2026-06-17: `DEBT-0030`/`DEBT-0043`/`DEBT-0044` were narrowed again.
`src/app_dialogs_export.cpp` is now a thin forwarding adapter. Retained
document export start/branching flows now live in
`src/legacy_document_export_services.*`, and the PPBR export dialog opener
now lives in `src/legacy_brush_package_export_services.*`; retained export
execution still remains behind those legacy bridges.
- 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 `<android/asset_manager.h>` 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 global CMake can still default to Ninja or reject the VS 2026 generator even though the VS-bundled CMake already supports it | `cmake --preset windows-msvc-default`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan"` | 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` |