# Build And Platform Inventory

Status: live
Last updated: 2026-06-17

This inventory records the known build surfaces during the CMake migration.
Keep it updated as platform paths move to shared CMake targets.

## Existing Build Entrypoints

| Platform/Target | Current Entrypoint | Notes |
| --- | --- | --- |
| Windows desktop | Root `CMakeLists.txt`, preset `windows-msvc-default`; target preset `windows-vs2026-x64` retained as the explicit VS 2026 name | Raw `.sln/.vcxproj` files removed on 2026-05-31; local Windows builds use Visual Studio 18 2026 through the VS 2026-bundled CMake, and `windows-msvc-default` names that generator explicitly so CMake does not default to Ninja with an invalid x64 platform cache; `PanoPainter` now links through `pp_platform_windows` and `panopainter_app`, with Windows/vendor link dependencies owned by the platform shell, runtime payload deployment in `cmake/PanoPainterRuntime.cmake`, tested app-level document-open routing plus open/close/save session decisions owned by `pp_app_core`, SDK-free clipboard/cursor/virtual-keyboard/display/share/picker service contracts owned by `pp_platform_api`, and injected `WindowsPlatformServices` now isolated in `src/platform_windows/windows_platform_services.*`; retained third-party source dependencies contained by `pp_legacy_vendor`, retained asset/file/serialization sources contained by `pp_legacy_assets_io`, retained paint/document/canvas sources contained by `pp_legacy_paint_document`, retained OpenGL runtime sources contained by `pp_legacy_renderer_gl` and folded into `pp_legacy_engine`, retained runtime shell sources contained by `pp_legacy_engine`, retained base UI controls contained by `pp_legacy_ui_core` and folded into `pp_legacy_app`, app orchestration/version metadata owned by `panopainter_app`, and app-specific modal/dialog/panel/canvas workflow nodes owned by `pp_panopainter_ui` |
| Windows AppX | `PanoPainterPackage/Package.appxmanifest`, `.wapproj` referenced by solution | Distribution packaging |
| macOS | `PanoPainter-OSX/` project files and `Info.plist` | Uses `NSOpenGLView` today |
| iOS | `PanoPainter/Info.plist`, related Apple sources | Uses OpenGL ES today |
| Android standard | `android/android/build.gradle`, `android/android/CMakeLists.txt` | Retained native library target `native-lib`; CMake 3.10/C++23 baseline now links the standard arm64 package path with modern component/service sources and the generated `nanort` overlay helper |
| Android Quest | `android/quest/build.gradle`, `android/quest/CMakeLists.txt` | OVR SDK imported libraries; CMake 3.10/C++23 baseline and current Yoga source list configure with the shared Android package compatibility helper |
| Android Focus/Wave | `android/focus/build.gradle`, `android/focus/CMakeLists.txt` | Wave SDK imported libraries; CMake 3.10/C++23 baseline and current Yoga source list configure with the shared Android package compatibility helper |
| Linux | `linux/CMakeLists.txt` | Retained app target now uses CMake 3.10 and target-level C++23 while package/root target migration remains open; the modern root CMake path now splits Linux helper code into `pp_platform_linux` instead of leaving it inside `pp_platform_api` |
| WebGL/Emscripten | `webgl/CMakeLists.txt` | Retained WebGL app target now uses CMake 3.10 and target-level C++23 with retained WebGL2/Emscripten link flags |

## Existing Version Generation

- Script: `scripts/pre-build.py`
- Output: `src/version.gen.h`
- Current behavior: derives version from git branch, latest tag, short hash,
  commit count, and configuration argument.
- Migration requirement: root CMake should call this script through a custom
  command and avoid unnecessary tracked-file churn where possible.

## Existing Dependency Sources

Hybrid policy: migrate reliable packages to vcpkg and retain SDK/patched
dependencies until each platform triplet is proven.

| Dependency | Current Source | Initial Policy |
| --- | --- | --- |
| fmt | `libs/fmt` | Move to vcpkg |
| GLM | `libs/glm` | Move to vcpkg |
| tinyxml2 | `libs/tinyxml2` | Move to vcpkg |
| stb | `libs/stb` | Move to vcpkg or interface target if package friction |
| CURL | `libs/curl-win`, `libs/curl-android-ios` | Move to vcpkg where triplets work |
| SQLite | `libs/sqlite3` | Move to vcpkg |
| GLAD | `libs/glad` | Move to vcpkg or generated backend target |
| Catch2 | none yet | Add through vcpkg |
| OpenXR | Not wired yet | Target desktop XR backend; add SDK/package behind `pp_platform_vr` |
| OpenVR | `libs/openvr` | Retain only as a temporary desktop compatibility fallback; remove under `DEBT-0061` |
| OVR Platform/Mobile | `libs/ovr_platform`, `libs/ovr_mobile` | Retain initially |
| Wave SDK | `libs/wave_sdk` | Retain initially |
| Wacom WinTab | `libs/wacom` | Retain initially |
| AppCenter Apple | `libs/appcenter-apple` | Retain initially |
| openh264/mp4v2/libyuv | `libs/openh264`, `libs/mp4v2`, `libs/libyuv` | Retain initially |
| jpeg helpers | `libs/jpeg` | Evaluate after image tests exist |
| poly2tri/nanort/base64/hash-library | `libs/*` | Evaluate after component split |

## Current Validation Commands

These commands are the current local baseline.

Prefer the bundled quiet wrapper for checkpoint validation. It captures full
logs in `out/logs/quiet-validation`, writes one run summary JSON there, applies
the filters in `scripts/automation/quiet-validation-ignore.txt`, and can fold
the Windows, platform-build, and Apple remote steps into one compact result:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan"
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets pp_app_core_app_dialog_tests,pp_ui_core_overlay_lifetime_tests -TestRegex "pp_(app_core_app_dialog|ui_core_(node_lifetime|overlay_lifetime))"
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan" -IncludePlatformBuild
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan" -IncludePlatformBuild -IncludeAppleRemote
```

Use the standalone quiet helpers only when you need to isolate those gates from
the bundled run. `platform-build.ps1 -Quiet` writes per-preset logs under
`out/logs/platform-build`. `apple-remote-build.ps1 -Quiet` writes the local SSH
session log under `out/logs/apple-remote-build` and reports the remote
`out/logs/apple-platform-build-*.log` path in its JSON output.

```powershell
cmake --preset windows-msvc-default
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
ctest --preset desktop-fast --build-config Debug
ctest --preset fuzz --build-config Debug
ctest --preset stress --build-config Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli
cmake --build --preset windows-msvc-default --target panopainter_validate_shaders
powershell -ExecutionPolicy Bypass -File scripts\automation\analyze.ps1 -Preset windows-msvc-default -NoApp
$env:VCPKG_ROOT = "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"
cmake --preset windows-msvc-vcpkg-headless
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets 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
cmake --preset android-arm64
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Quiet -Presets android-arm64
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 -Preset windows-msvc-default -Configuration Debug
powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly
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_windows_app_package_smoke
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_webgl_package_readiness
cmake --build --preset windows-msvc-default --config Debug --target panopainter_platform_build_android_assets
cmake --fresh --preset windows-clangcl-asan
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name execute_brush
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
python scripts/dev/clangd_nav.py symbols --file src/app_core/document_export.h --detail-regex "Export.*Plan"
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 511 --column 39
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 783 --column 45 --path-regex "src[\\/]app_core"
python scripts/dev/clangd_nav.py self-test
python scripts/dev/check_platform_build_targets.py
python scripts/dev/check_package_smoke_readiness.py
powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos,ios-simulator,ios-device
powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Quiet -Presets macos,ios-simulator,ios-device
```

Known local toolchain state:

- CMake: 4.0.0-rc4
- Local Visual Studio generator selected by the VS 2026-bundled CMake: Visual
  Studio 18 2026
- Bundled vcpkg: `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg`
  (`vcpkg version` reports 2025-11-19)
- External vcpkg: `D:\vcpkg` is currently resolved by
  `scripts/automation/platform-build.ps1` when `VCPKG_ROOT` is already set on
  this host; the wrapper falls back to bundled Visual Studio vcpkg roots when
  needed.
- Android SDK: `C:\Users\omara\AppData\Local\Android\Sdk`
- Android NDK: latest SDK Manager package checked/installed/selected by
  `scripts/automation/android-sdk-env.ps1`; currently
  `C:\Users\omara\AppData\Local\Android\Sdk\ndk\30.0.14904198`
- Android CMake: latest SDK Manager package checked/installed/selected by
  `scripts/automation/android-sdk-env.ps1`; currently
  `C:\Users\omara\AppData\Local\Android\Sdk\cmake\4.1.2`
- clang-cl: `C:\Program Files\LLVM\bin\clang-cl.exe` reports 18.1.8, but the
  selected VS 2026-preview STL expects Clang 20 or newer; see DEBT-0014 before
  treating `windows-clangcl-asan` as a passing sanitizer gate.
- clangd/clang-query/clang-tidy are installed under `C:\Program Files\LLVM\bin`.
  `scripts/dev/clangd_nav.py` provides agent-friendly symbol, definition,
  declaration, implementation, reference, and hover lookup through clangd using
  the current `compile_commands.json` from `windows-clangcl-asan`,
  `android-arm64`, or a caller-provided build directory. Symbol, hover,
  declaration, definition, and implementation lookups are the reliable use case.
  Symbol listing supports substring filtering with `--name`, regex filtering
  against `qualifiedName` with `--name-regex`, and symbol detail/signature
  regex filtering with `--detail-regex`. Definition, declaration,
  implementation, and reference location output supports `--path-regex` for
  path/URI filtering. Regex matching is case-insensitive by default and can be
  made case-sensitive with `--no-ignore-case`. The helper exposes
  `python scripts/dev/clangd_nav.py self-test`, also registered as
  `panopainter_clangd_nav_regex_self_test`, so regex filter behavior can be
  validated without clangd or a compile database.
  Agents must use the repo skill `panopainter-code-navigation` before broad
  text searches when a C++ refactor needs symbol identity, symbol-family lookup,
  generated-style names, signatures, override groups, declarations/definitions,
  or platform/backend path slices. Keep `rg` for docs, build files, string
  literals, comments, and exact non-symbol text.
  Reference lookup is guarded because a one-shot clangd process may not have a
  complete project index: pass `--background-index` for broader best-effort
  results or `--allow-incomplete-references` for explicitly
  current-translation-unit-only results, and do not treat incomplete references
  as proof that no other users exist.
- Apple headless compile validation runs on the local Mac mini SSH alias
  `panopainter-mac` with user `builder`. The host uses a Gitea SSH key for
  `ssh://git@git.omar.synology.me:3022/omar/panopainter.git`, Homebrew
  CMake/Ninja/Git, and full Xcode selected per command with
  `DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer`. The repeatable
  Windows-side bundled quiet command is:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan" -IncludeAppleRemote
```

  For Apple-only isolation, run
  `scripts/automation/apple-remote-build.ps1 -Quiet -Presets macos,ios-simulator,ios-device`.
  The wrapper updates `~/Dev/panopainter`, initializes the source submodules
  required by the headless CMake matrix, delegates to
  `scripts/automation/platform-build.sh`, stores the local SSH transcript under
  `out/logs/apple-remote-build`, and reports the remote Apple platform-build
  log path in its JSON output. macOS, iOS simulator, and iOS device currently
  validate shared component/test/tool targets only; signed Apple app bundles
  remain tracked by DEBT-0011 and the iOS compile-only signing shortcut is
  tracked by DEBT-0059.
- Android standard arm64/x64, Quest arm64, and Focus/Wave arm64 headless
  configure/build pass through root CMake and the `platform-build` automation
  wrapper defaults for `pp_foundation`, `pp_assets`,
  `pp_paint`, `pp_document`, `pp_renderer_api`, `pp_renderer_gl`,
  `pp_paint_renderer`,
  `pp_ui_core`, `pano_cli`, and their current headless test binaries,
  including foundation binary-stream/event/logging/task queue coverage, image
  metadata/decode/encode coverage, PPI header/layout/body coverage, settings
  document coverage, brush-package path/header coverage, document
  snapshot/frame/layer/history/PPI import-export coverage, paint
  brush/final-blend/stroke-alpha-blend/stroke spacing/stroke stress/
  stroke-script coverage, renderer shader descriptor and OpenGL capability
  coverage, UI color/layout/XML parse coverage, platform service coverage, and
  the current `pp_app_core` automation surface for startup, file menus,
  document session/open/import/export/animation/canvas/layer/resize/cloud/
  sharing/platform-I/O, brush-package import/export, brush UI, canvas tools and
  hotkeys, history, grid, quick UI, main toolbar, Tools, About, preferences,
  and app status planning. The PowerShell wrapper accepts repeated, array, or
  comma-separated `-Presets`/`-Targets` values so scripted callers can narrow
  the same matrix without accidentally passing a single combined target.
  `scripts/dev/check_platform_build_targets.py`, registered as
  `panopainter_platform_build_target_matrix_self_test`, verifies the PowerShell
  and shell wrapper defaults include every current CMake test executable plus
  the required component and `pano_cli` targets, and that both wrappers default
  to the standard arm64/x64, Quest arm64, and Focus/Wave arm64 Android presets.
  It also guards the root CMake platform automation target names and the
  PowerShell/shell Android SDK Manager update hooks.
- `package-smoke.ps1 -ReadinessOnly` and `package-smoke.sh --readiness-only`
  emit the Windows AppX, Android standard/Quest/Focus APK, Apple bundle, Linux
  app, and WebGL package readiness matrix without first building an app
  artifact. The full package smoke command still builds/checks the selected app
  target before reporting the same readiness matrix. `package-smoke.ps1
  -AndroidNativeChecks` also runs the retained Android standard `native-lib`
  build and Quest/Focus configure helper for selected Android package kinds,
  then reports those native results beside the APK readiness matrix. Both
  wrappers now classify each platform gate as `validated`, `compile-only`, or
  `blocked` based on local prerequisites and whether a real root CMake package
  target exists.
  `scripts/dev/check_package_smoke_readiness.py`, registered as
  `panopainter_package_smoke_readiness_self_test`, verifies both wrappers keep
  the same seven package kinds, readiness status modes, readiness-only mode,
  retained Android native-check prerequisite metadata, retained Linux/WebGL
  CMake baseline metadata, and root CMake package validation target names.
- `scripts/dev/check_component_boundaries.py`, registered as
  `panopainter_component_boundary_self_test`, checks pure component source files in
  `pp_foundation`, `pp_assets`, `pp_paint`, `pp_document`, `pp_renderer_api`,
  `pp_paint_renderer`, `pp_ui_core`, and `pp_app_core` for forbidden platform
  SDK/backend includes, `App::I`/`Canvas::I` leakage, and disallowed `pp_*`
  target dependencies before desktop-fast validation. Temporary allowed-platform
  edges remain explicitly tracked by DEBT-0003 and DEBT-0008.
- `scripts/dev/check_renderer_conformance_matrix.py`, registered as
  `panopainter_renderer_conformance_matrix_self_test`, verifies renderer conformance
  tests are labeled with the shared `renderer-conformance` matrix profile.
- `scripts/dev/check_renderer_api_contract.py`, registered as
  `panopainter_renderer_api_contract_self_test`, enforces backend-neutral boundary
  rules for `pp_renderer_api` and `pp_paint_renderer`, including forbidden
  backend symbols/includes in these components before renderer contract milestones.
- Root CMake exposes non-default package validation targets:
  `panopainter_package_readiness`,
  `panopainter_windows_app_package_smoke`,
  `panopainter_windows_appx_package_readiness`,
  `panopainter_apple_bundle_package_readiness`,
  `panopainter_android_standard_native_package`,
  `panopainter_android_standard_apk_package_readiness`,
  `panopainter_android_quest_apk_package_readiness`,
  `panopainter_android_focus_apk_package_readiness`,
  `panopainter_android_vr_native_package_configure`, and
  `panopainter_android_native_package_smoke`, and
  `panopainter_linux_app_package_readiness`,
  `panopainter_webgl_package_readiness`, and
  `panopainter_linux_webgl_package_readiness`. These targets call the
  automation scripts from CMake but do not convert APK/AppX/Linux/Apple/WebGL
  package outputs to root CMake package targets yet.
- Root CMake exposes non-default platform validation targets:
  `panopainter_platform_build_headless`,
  `panopainter_platform_build_android_assets`,
  `panopainter_platform_build_vcpkg_ui_core`, and
  `panopainter_platform_build_apple_remote`. These targets call the existing
  platform automation scripts from CMake and keep platform validation discoverable
  from the CMake target graph while app/package target migration remains open.
- Retained Linux and WebGL app CMake entrypoints are still separate from root
  CMake, but now share the modernization baseline of CMake 3.10 and
  target-level `cxx_std_23`. `panopainter_retained_platform_cmake_self_test`
  guards those baselines until these entrypoints are replaced by shared root
  CMake package/app targets.
- Root CMake exposes named `fuzz` and `stress` CTest presets. `fuzz` currently
  runs deterministic parser/serializer edge tests for binary streams, image
  metadata, PPI, stroke scripts, and layout XML; `stress` currently runs the
  stroke sampler stress coverage.
- `pano_cli inspect-image` reports PNG IHDR metadata as JSON and is covered by
  `pano_cli_inspect_png_metadata_smoke` with a tiny IHDR fixture.
- `pp_assets_image_pixels_tests` decodes PNG payloads, encodes RGBA8 pixels to
  PNG, round-trips encoded pixels back through the decoder, and rejects corrupt
  or malformed image payloads.
- `pano_cli import-image` accepts a PNG path, decodes RGBA8 pixels through
  `pp_assets`, attaches them to a pure `pp_document` face payload, and is
  covered for checked-in decodable PNG import by `pano_cli_import_image_smoke`
  and metadata-valid truncated PNG rejection by
  `pano_cli_import_image_rejects_truncated_png`.
- `pano_cli export-image` writes a deterministic RGBA8 PNG through `pp_assets`
  and is covered by `pano_cli_export_image_roundtrip_smoke`, which imports the
  generated file back through `pano_cli import-image`.
- `pano_cli inspect-project` reports validated PPI thumbnail/body byte layout,
  body summary fields, layer/frame descriptors, and dirty-face PNG payload
  metadata, and is covered by `pano_cli_inspect_project_layout_smoke` with a
  minimal PPI fixture.
- `pp_document_ppi_import_tests` attaches decoded PPI dirty-face payloads to
  `pp_document` layer/frame storage and rejects payloads outside document
  layers.
- `pp_document_ppi_export_tests` exports pure `pp_document` metadata,
  per-layer frame durations, and RGBA8 face payloads to PPI bytes through
  `pp_assets`, then decodes and reimports them for round-trip coverage.
- `pp_paint_renderer_compositor_tests` now covers pure `pp_document` face and
  six-face frame compositing by expanding per-layer dirty face payload
  rectangles into full renderer-sized RGBA buffers with layer visibility,
  opacity, and blend mode applied in document order, then uploading those six
  faces through the renderer-neutral `IRenderDevice` texture API using the
  recording backend. It also covers the shared
  `prepare_document_frame_export_readiness` report consumed by CLI and live
  export-readiness bridges, including the recorded upload command summary and
  pure six-face PNG export bytes encoded through `pp_assets`. The compositor
  tests now also cover equirectangular PNG export from that same composited
  document frame using the shader-equivalent cube sampling policy and
  `pp_assets` PNG encoding, independent layer equirectangular PNG export for
  layer collections, and merged animation-frame equirectangular PNG export for
  frame collections.
- `pano_cli simulate-document-export` exposes the same pure document-to-PPI
  export, asset-level decode, and document reimport path through JSON
  automation and is covered by `pano_cli_simulate_document_export_smoke`.
- `pano_cli simulate-document-render` exposes the pure document-to-renderer
  frame compositor plus renderer texture-upload command stream through JSON
  automation, including the OpenGL command-plan support counts for that upload
  stream, and is covered by
  `pano_cli_simulate_document_render_smoke`.
- `pano_cli plan-canvas-document-snapshot` exposes the bridge from live canvas
  state toward `pp_document::CanvasDocument`, including dimensions, active
  layer/frame, layer visibility/opacity/alpha/blend metadata, frame durations,
  captured RGBA8 face payloads, and remaining renderer payload-readback counts,
  plus the save-readiness and save-writer route reports now consumed before
  retained live saves. For payload-complete or metadata-only snapshots, the
  same app-core boundary now exports through the pure `pp_document` PPI writer
  and reports generated byte counts plus decoded dirty-face counts in
  `ppiExport` JSON. Payload-complete
  snapshots also feed the active frame through the shared `pp_paint_renderer`
  export-readiness report, reporting texture, transition, command, byte, and
  active-frame payload counts in `rendererUpload` JSON plus `facePngExport`
  readiness, face count, byte count, and payload count. They also report
  `depthExport` readiness from the tested paint-renderer depth plan: pending
  snapshots keep it unavailable, and payload-complete snapshots expose the
  1024x1024 draw/readback plan counts. It is covered by
  `pano_cli_plan_canvas_document_snapshot_smoke` plus the payload-bearing
  snapshot smoke.
- `pano_cli plan-canvas-project-save-target` exposes the app-core planner for
  retained project-save target paths, including the requested PPI path,
  temporary `.tmp.ppi` path, and timelapse `.pptl` sidecar path. The live
  `Canvas::project_save_thread` consumes the same planner before retained
  serialization. The command also reports the app-core write-mode plan for
  direct first saves versus existing-target temporary writes, including the
  retained direct-write fallback when the temporary file cannot be opened, and
  the app-core commit plan for direct saves, successful temporary swaps,
  target-remove failures, and rename-after-remove failures. It is covered by
  forward-slash, Windows backslash, existing-target, remove-failure,
  rename-failure, and invalid-path smokes. The same command now reports the
  app-core post-commit side-effect plan for clean/new-document metadata
  updates, timelapse sidecar gating, platform flush, progress cleanup, and
  title refresh.
- Live equirectangular, layer, animation-frame, and cube-face export adapters
  now prepare and log the same payload-bearing canvas document snapshot plus
  shared paint-renderer export-readiness report.
  Cube-face export writes the pure document/renderer PNG bytes through the
  app-core write/publish executor to the planned retained face filename set and
  falls back to `Canvas::export_cube_faces` if snapshot capture, PNG generation,
  or file writing fails. PNG equirectangular export writes a pure
  document/paint-renderer equirectangular PNG through the app-core file
  write/publish executor before falling back to the retained writer.
  Payload-complete layer and animation-frame PNG collections
  write pure document/paint-renderer equirectangular PNG sequences through the
  app-core collection write/publish executor before retained fallback. JPEG
  equirectangular export writes a pure document/paint-renderer JPEG with GPano
  XMP metadata through the same app-core file write/publish executor before
  retained fallback; Web handoff, incomplete-readback collection cases, and
  video export remain on retained writer paths. Depth
  export now plans the retained image/depth output paths in `pp_app_core` and
  logs a tested `pp_paint_renderer` depth render plan plus the shared
  document-frame renderer-readiness report before retained
  `Canvas::export_depth` execution.
- `pano_cli save-document-project` writes that pure document export to a PPI
  file and is covered by `pano_cli_save_document_project_roundtrip_smoke`,
  which inspects and loads the generated file.
- `pano_cli load-project` creates a `pp_document` projection with per-layer
  frame counts, durations, and decoded face-pixel payloads when present; the
  metadata-only minimal fixture remains covered by
  `pano_cli_load_project_metadata_smoke`.
- `pp_assets::create_ppi_project` writes generated multi-layer, multi-frame
  PPI files with explicit per-layer names, opacity, blend mode, alpha lock,
  visibility, per-layer frame durations, and targeted dirty-face layer/frame
  payloads. `pano_cli save-project` exposes that path for automation and is
  covered by `pano_cli_save_project_roundtrip_smoke` and
  `pano_cli_save_project_payload_roundtrip_smoke`, which reload generated
  metadata-only and targeted dirty-face-payload projects through
  `pano_cli load-project`, plus
  `pano_cli_save_project_rejects_non_finite_opacity`, which verifies rejected
  automation floats do not create output files.
- `pano_cli create-document` supports `--frames` and `--frame-duration-ms` and
  is covered by `pano_cli_create_animation_document_smoke`.
- `pano_cli simulate-document-edits` exercises pure document layer/frame edit
  operations, renderer-free face payloads, and renderer-free selection masks,
  and is covered by `pano_cli_simulate_document_edits_smoke`.
- `pano_cli simulate-document-history` exercises pure document history
  apply/undo/redo behavior and is covered by
  `pano_cli_simulate_document_history_smoke`.
- `src/legacy_document_layer_services.*` is the current app-shell bridge for
  layer rename, layer menu clear/rename/merge, layer merge, and layer-panel
  operations. It keeps those live paths on the `pp_app_core` contracts while
  legacy `Canvas`, `NodeLayer`, `NodePanelLayer`, and `ActionManager`
  execution remain tracked by `DEBT-0021` and `DEBT-0032`.
- `NodePanelLayer::update_attributes()` now consumes the tested
  `pp_app_core` layer panel view model for current opacity, alpha-lock, blend
  mode, and per-layer visibility projection. `pano_cli plan-layer-panel-view`
  exposes the same view model for automation; retained canvas mutation, UI node
  ownership, and undo behavior remain tracked by `DEBT-0021`.
- `src/legacy_app_shell_services.*` is the current app-shell bridge for File
  menu routing, export-menu routing, main-toolbar commands, About menu
  commands, and direct Tools menu commands. It keeps those live paths on the
  `pp_app_core` contracts while legacy dialogs, pickers, cloud/share/export,
  Tools, About, history, canvas-clear, settings, and platform SonarPen startup
  execution remain tracked by `DEBT-0029`, `DEBT-0030`, `DEBT-0031`,
  `DEBT-0033`, `DEBT-0034`, and `DEBT-0035`. The main-toolbar test message
  dialog metadata now lives in `pp_app_core` and its retained creation routes
  through `src/legacy_app_dialog_services.*`. `src/legacy_canvas_view_services.*`
  is the shared bridge for reset-camera, viewport-density, and cursor-mode
  execution; live Tools reset-camera, document open/new-document reset, cloud
  download reset, and options viewport/cursor callbacks consume the tested
  `pp_app_core` canvas-view plans exposed through
  `pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`,
  and `pano_cli plan-canvas-view-cursor-mode` before retained canvas mutation
  and settings writes.
- `src/legacy_app_startup_services.*` is the current main-app startup bridge for
  run-counter persistence, startup runtime side effects, and startup resource
  sequencing. `App::init` now consumes `pp_app_core` plans exposed through
  `pano_cli plan-app-startup` and `pano_cli plan-app-startup-resources`, while
  retained shader loading, asset initialization, layout creation, title updates,
  UI render-target creation, recording startup, VR-controller state mutation,
  settings writes, and license-warning dialogs remain legacy execution.
- `src/app_core/app_frame.h` owns the current initial surface, update-gating,
  tick, resize, and draw-pass decisions consumed by `App::create`,
  `App::tick`, `App::resize`, `App::update`, `App::draw`, and
  `pano_cli plan-app-frame`; retained layout traversal, toolbar widget writes,
  UI render-target recreation, canvas stroke drawing, VR UI render-target
  drawing, main target binding, and OpenGL/UI drawing remain in the legacy app.
- `src/app_core/app_shutdown.h` owns the current shutdown cleanup staging
  consumed by `App::terminate` and `pano_cli plan-app-shutdown`; retained
  UI-state save, recording stop, resource invalidation, layout unload,
  render-target/mesh destruction, and panel-node release remain in the legacy
  app.
- `src/legacy_app_preference_services.*` is the current app-shell bridge for
  options-menu preference execution. It keeps UI scale, viewport scale, RTL,
  VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks on
  the `pp_app_core` `AppPreferenceServices` contract while retained settings
  persistence, recording lifecycle, and legacy canvas/UI execution remain
  tracked by `DEBT-0045`; the VR mode callbacks now call `App` wrappers that
  dispatch through `PlatformServices` before reaching the selected platform XR
  runtime. `pp_platform_api` now prefers OpenXR in its tested desktop runtime
  selection policy and marks the retained Windows OpenVR SDK path as a legacy
  fallback under `DEBT-0061`.
- `src/legacy_app_startup_services.*` is the current app-shell bridge for
  startup preference/runtime execution. It keeps run-counter persistence,
  startup preference save, auto-timelapse startup, stored VR-controller state,
  and license-warning decisions on the `pp_app_core` `AppStartupServices`
  contract while retained `Settings`, `App::rec_start`, and message-box
  execution remain tracked by `DEBT-0046`.
- `src/legacy_canvas_tool_services.*` is the current app-shell bridge for
  canvas toolbar tool selection, NodeCanvas stylus/input mode switching, and
  canvas hotkey/touch execution. It keeps those live paths on the `pp_app_core`
  contracts; `App::init_toolbar_draw()` now consumes the tested
  `pp_app_core` draw-toolbar binding plan exposed through
  `pano_cli plan-canvas-tool-toolbar` before retained button lookup and legacy
  execution dispatch. Canvas mode tip visibility and pressure remapping now ask
  `PlatformServices`; `NodeCanvas::update_cursor()` now consumes
  `pp_app_core` canvas cursor visibility planning exposed through
  `pano_cli plan-canvas-cursor` before retained platform cursor dispatch.
  Legacy `Canvas` mode state, transform actions, picking, touch-lock,
  save/UI/cursor calls, brush-size controls, and history execution remain
  tracked by `DEBT-0027`.
- `src/legacy_document_animation_services.*` is the current UI-shell bridge for
  animation frame commands, timeline/selected-frame execution, playback ticks,
  onion-size updates, and play-mode toggles. It keeps those live paths on the
  `pp_app_core` contracts. `NodeCanvas` onion-skin panorama drawing now also
  consumes the tested `pp_app_core` onion frame range and alpha falloff helper,
  and `NodeAnimationTimeline` mouse scrubbing consumes tested `pp_app_core`
  cursor-to-frame planning exposed through `pano_cli plan-animation-timeline-scrub`,
  and `NodePanelAnimation` layer/frame/timeline display state consumes the
  tested `pp_app_core` panel view model exposed through
  `pano_cli plan-animation-panel-view`,
  while legacy `Canvas`/`Layer` frame state, canvas mode, animation-panel
  timeline/playback fields, and the temporary `NodePanelAnimation` friend
  adapter remain tracked by `DEBT-0022`.
- `src/legacy_brush_ui_services.*` is the current UI-shell bridge for brush
  color, texture, preset, stroke-refresh, brush texture-list, and stroke-control
  execution. `NodePanelBrushPreset` now consumes `pp_app_core` preset-list
  planning and `BrushPresetListServices` execution for add/select/move/remove/
  clear before the retained legacy bridge mutates child nodes.
  `NodePanelStroke::update_controls()` now consumes the tested `pp_app_core`
  stroke-panel view model for brush float settings, toggles, blend modes, and
  thumbnail paths, and `pano_cli plan-brush-stroke-panel-view` exposes that
  projection for automation. `App::brush_update()` now consumes the tested
  `pp_app_core` brush refresh view before updating retained stroke, quick, and
  floating color widgets, and `pano_cli plan-brush-refresh` exposes that
  fan-out for automation. These paths stay on the `pp_app_core` contracts while
  legacy `Brush`, `Canvas::I`, image load/save, `NodePanelBrush`,
  `NodePanelStroke`, quick/color refreshes, direct preset child-node mutation,
  brush thumbnail/popup ownership, and the temporary `NodePanelBrush` friend
  adapter remain tracked by `DEBT-0023`.
- `src/legacy_grid_ui_services.*` is the current UI-shell bridge for grid
  heightmap picker/load/reload/clear, lightmap render, and heightmap commit
  execution. It keeps those live paths on the `pp_app_core` contracts while
  legacy image loading, OpenGL texture updates, shared `parallel_for` CPU
  lightmap baking/progress, and `Canvas::draw_objects` execution remain tracked
  by `DEBT-0024`.
- `src/legacy_quick_ui_services.*` is the current UI-shell bridge for quick
  brush/color slot selection, popup routing, mini-state restore, and mini-state
  reset execution. Quick size/flow slider preview cursor placement and pen/line
  mode tip flags now consume the tested `pp_app_core` planner directly from
  `NodePanelQuick`, and `pano_cli plan-quick-slider-preview` exposes that path
  for automation. It keeps those live paths on the `pp_app_core` contracts
  while legacy quick widgets, brush previews, color picker state, preset popup
  execution, and direct legacy `CanvasMode*` field writes remain tracked by
  `DEBT-0025`.
- `pano_cli simulate-image-import` decodes an embedded tiny PNG through
  `pp_assets`, attaches it to `pp_document`, and is covered by
  `pano_cli_simulate_image_import_smoke`.
- `pano_cli simulate-blend` exposes deterministic final RGBA and stroke-alpha
  blend reference vectors through JSON automation and is covered by
  `pano_cli_simulate_blend_smoke`.
- `pano_cli simulate-stroke` exposes the pure stroke sampler for scripted
  automation and is covered by `pano_cli_simulate_stroke_smoke`.
- `pano_cli simulate-stroke-script` loads a text stroke script fixture and is
  covered by `pano_cli_simulate_stroke_script_smoke`.
- `pano_cli apply-stroke-script` parses a text stroke script fixture, samples
  every stroke through `pp_paint`, maps the samples into a bounded
  `pp_document` RGBA8 face payload, writes a PPI file, and is covered by
  `pano_cli_apply_stroke_script_roundtrip_smoke`, which inspects the dirty-face
  box and loads the generated file back as decoded document pixel data, plus
  `pano_cli_apply_stroke_script_rejects_tiny_canvas` for invalid dimension
  rejection.
- `panopainter_validate_shaders` validates the current combined GLSL shader
  files for one vertex stage marker, one fragment stage marker, valid marker
  order, and existing relative includes.
- `pp_renderer_api` owns the canonical PanoPainter shader catalog consumed by
  the legacy OpenGL app initialization path; `pp_renderer_api_tests` validates
  catalog size, key entries, duplicate rejection, and bad path rejection.
- `pp_renderer_gl` owns headless OpenGL runtime capability detection consumed
  by the legacy app initialization path; `pp_renderer_gl_capabilities_tests`
  validates framebuffer fetch, map-buffer alignment, desktop GL float support,
  GLES float/half-float extensions, WebGL exclusion behavior, and the
  upload-type mapping used by legacy `Texture2D` and `RTT` creation, plus the
  RGBA pixel-format mapping used by `RTT` texture allocation. It also validates
  image channel-count to OpenGL texture format mapping, including
  invalid channel counts rejected by `Texture2D::create(Image)`, renderer API
  texture-format to OpenGL internal/pixel/component token mapping including
  depth-stencil formats, RGBA8/RGBA32F
  readback formats, checked byte-count math, PBO pixel-buffer target/usage/access
  mapping, and PBO allocation/readback/map/unmap/delete dispatch sequences used
  by retained `PBO` recording readbacks, and framebuffer status naming
  used by `RTT` and `Texture2D` diagnostics. It also owns the 2D texture target,
  framebuffer setup, readback format, mipmap target, and update component-type
  tokens used by `Texture2D`, plus cube-map binding and allocation face targets
  used by `TextureCube`. Retained `Texture2D`, `TextureCube`, and RTT texture
  allocation, bind, parameter, update, mipmap, and delete dispatch now share
  the retained `legacy_gl_texture_dispatch` raw callback bridge. 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`. It also owns and
  validates framebuffer blit color mask and linear/nearest filters used by
  `RTT::resize` and `RTT::copy`, renderer API blit-filter to OpenGL token
  mapping, plus the default linear clamp-to-edge render-target texture
  parameters and parameter dispatch, texture/renderbuffer targets, depth format,
  framebuffer targets, binding queries, attachment points, render-target
  framebuffer allocation/delete, binding restore, and completion status used by
  `RTT::create`/`RTT::destroy` and framebuffer bind/restore paths, plus RTT
  clear color/depth masks. Retained `Texture2D` readback and RTT framebuffer
  allocation, deletion, bind/restore, blit, readback, and PBO readback dispatch
  now share the retained `legacy_gl_framebuffer_dispatch` raw callback bridge.
  RTT render-target clear, masked color clear with
  color-write-mask restore, and texture bind/unbind now execute through tested
  dispatch contracts here. Optional RTT depth renderbuffer
  allocation/storage/delete and framebuffer depth attach/detach, plus canvas
  object-drawing depth renderbuffer setup, also execute through tested dispatch
  contracts here and share the retained `legacy_gl_renderbuffer_dispatch` raw
  callback bridge. Renderer API render-pass color/depth/stencil
  clear-mask and clear-value mapping, and color-write-mask query tokens. `RTT` no longer
  spells GL enum names directly. `RTT` also exposes a retained RGBA8
  region-readback helper that uses the tested framebuffer readback dispatch for
  canvas pick/history/snapshot and transform history paths, plus a retained
  RGBA8 region-update helper that uses the tested texture-update dispatch for
  canvas undo, layer restore, and flood-fill texture writes. 2D
  framebuffer-to-texture copies used by retained canvas, transform,
  layer-conversion, panorama UI, brush-preview, and CanvasLayer cube-face paths
  now execute through a tested `pp_renderer_gl` dispatch via the target-aware
  retained framebuffer-copy utility bridge. That bridge remains retained until
  renderer services own copy execution under `DEBT-0036`. It also
  validates renderer API primitive-topology to OpenGL draw-mode mapping, Shape
  index-type, fill/stroke primitive-mode, buffer target, static upload usage,
  and vertex attribute component/normalization mapping used by
  the legacy mesh draw path. Retained `Shape`, `TextMesh`, and
  `NodeColorWheel` mesh buffer/VAO creation, zero-byte dynamic-buffer creation,
  dynamic vertex/index uploads, fill/stroke/text draw calls, and buffer/VAO
  deletion now consume tested dispatch contracts here through
  `legacy_gl_mesh_dispatch`, plus the PanoPainter cube-face to OpenGL
  texture-target mapping used by `TextureCube`.
  It also owns and validates sampler wrap S/T/R, min/mag filter, and desktop
  border-color parameter mapping used by legacy `Sampler`, plus renderer API
  sampler filter/address-mode to OpenGL token mapping including mirrored-repeat
  and aggregate renderer API sampler-state to OpenGL min/mag/wrap mapping.
  Legacy `TextureCube` allocation/bind/delete and legacy `Sampler`
  create/configure/border/bind/unbind calls now consume those resource dispatch
  contracts directly from the retained app utilities.
  The PanoPainter
  shader attribute binding catalog, shader stage tokens, compile/link status
  queries, active-uniform count query, and matrix-uniform transpose token used
  by legacy `Shader` creation also live here. Renderer API blend factor/op to
  OpenGL token mapping is tested here with explicit support flags so `GL_ZERO`
  stays distinguishable from unsupported enum values. Aggregate renderer API
  blend-state to OpenGL enable/factor/equation/color-mask mapping, depth
  compare-op to OpenGL depth-function mapping, and aggregate renderer API
  depth-state to OpenGL enable/write/compare mapping are tested here too.
  `Shader` no longer spells GL enum
  names directly. It also owns the PanoPainter shader uniform catalog and legacy hash
  mapping used by `Shader` active-uniform discovery and the uniform uniqueness
  check. Legacy `Shader` program use/delete, uniform writes,
  attribute-location lookup, shader source compilation, shader deletion,
  program attach/link, attribute rebinding, active-uniform count/enumeration,
  and uniform-location discovery now consume tested dispatch contracts here
  through `legacy_gl_shader_dispatch`.
  App OpenGL initialization debug severity, debug output, GL info string,
  renderer API viewport/scissor rect conversion, default depth/program-point/
  line-smooth state, blend factor/equation, and UI render-target RGBA8 format
  tokens are cataloged and tested here too. The legacy convert command now
  applies its depth, program-point-size, source-alpha blend, and add-equation
  startup policy through a tested dispatch contract here; the resize path
  consumes the backend-owned mapping. App clear color-buffer masks, default
  framebuffer binding, scissor state, and sampler filter/wrap tokens also
  consume the backend mapping.
  OpenGL extension enumeration query tokens
  used before runtime capability detection are cataloged here. Legacy font
  atlas texture formats, text mesh buffer targets, attribute component and
  normalization tokens, draw primitive/index type, upload usage, and active
  texture unit selection also consume the backend mapping. Text mesh
  buffer/VAO creation, deferred index/vertex uploads, indexed draw calls, and
  draw-time texture-unit activation now consume tested `pp_renderer_gl`
  dispatch contracts through the shared retained mesh bridge too. Canvas undo/redo dirty-region texture updates and
  readbacks also consume the backend-owned 2D texture target, RGBA pixel
  format, and unsigned-byte component mapping; canvas stroke commit, thumbnail,
  and object-draw history paths now consume tested capability-state query
  dispatch for saved blend state.
  `NodeViewport` preview rendering also consumes backend-owned viewport query,
  clear-color query, color-buffer clear mask, and blend-state tokens.
  `NodeImageTexture` preview drawing also consumes backend-owned fallback 2D
  texture bind and blend-state tokens.
  `NodeImage` drawing and remote-image texture creation also consume
  backend-owned mipmapped sampler filters, blend-state tokens, and RGBA8/RGBA
  texture format mapping.
  `NodeColorWheel` triangle-buffer setup now shares the retained mesh bridge,
  and its draw-state handling also consumes backend-owned blend-state tokens.
  Simple UI text, text-input, border, scroll, and animation timeline draw
  paths also consume backend-owned blend-state tokens.
  Canvas layer cube/equirect generation, clear, restore, and snapshot paths
  also consume backend-owned cube/2D texture targets, active texture units,
  cube texture binding, blend/clear state, viewport execution, color-buffer
  clear, clear-color query/restore, and RGBA8 read/write pixel mapping. Their
  active-texture, cube-texture binding, viewport, blend capability,
  clear-color, and color-buffer clear adapter endpoints now share
  `legacy_ui_gl_dispatch`; the cube-face framebuffer-to-texture copy now uses
  the shared retained target-aware utility bridge and remains tracked by
  DEBT-0036.
  `NodePanelGrid` heightmap preview and lightmap baking also consume
  backend-owned texture readback formats, sampler filters, depth/blend state,
  depth clears, viewport queries, color-mask booleans, active texture units,
  and float render-target formats; its desktop texture-resize readback now
  reuses `Texture2D::get_image()` and the tested framebuffer-backed texture
  readback dispatch instead of direct `glGetTexImage`; CPU lightmap row dispatch
  now uses shared `parallel_for` instead of platform-specific worker APIs. Its
  live heightmap draw and bake paths now execute depth/blend state changes,
  depth clears, color-write-mask toggles, active texture selection, and bake
  viewport changes through tested `pp_renderer_gl` dispatch adapters. Grid
  depth-state snapshots and sun overlay viewport queries now use tested backend
  query dispatch as well.
  Legacy `util.cpp` OpenGL error naming, framebuffer-to-texture copy helper,
  and `gl_state` save/restore also consume backend-owned error codes, state
  queries, framebuffer targets, texture binding targets, active texture units,
  shader program use, and sampler binding through the shared retained bridge
  headers instead of local raw OpenGL callbacks.
  `NodeStrokePreview` brush preview rendering also consumes backend-owned
  depth/scissor/blend state, tested viewport/clear-color query dispatch,
  clear-color restore, active texture unit execution, fallback 2D texture
  unbinds, 2D texture targets, copy targets, and sampler filters/wraps. Its
  live stroke-mixer and brush-preview viewport, scissor, and depth/blend state
  changes now execute through tested `pp_renderer_gl` dispatch via the shared
  `legacy_ui_gl_dispatch` bridge.
  Retained `Canvas` stroke/thumbnail/object/export paths and `NodeCanvas`
  panorama rendering use the same tested active-texture dispatch for
  texture-unit switches, and their live viewport, scissor, and generic
  depth/blend/scissor capability changes now route through the same backend
  dispatch contracts. `NodeCanvas` saved blend/depth/scissor capability-state
  queries now use tested backend query dispatch too. Retained `Canvas` stroke draw/commit, thumbnail
  generation, object rendering, and `LayerFrame::clear` saved viewport or
  clear-color query plus clear-color restore paths also use tested
  `pp_renderer_gl` dispatch helpers. Retained `Canvas` active texture, fallback
  texture unbind, viewport/scissor execution, viewport and clear-color query,
  clear-color restore, and capability query/apply adapter endpoints now share
  `legacy_ui_gl_dispatch`; Canvas and RTT depth renderbuffer
  allocation/attachment/delete now share `legacy_gl_renderbuffer_dispatch`
  while renderer-resource ownership remains retained. `NodeCanvas` saved
  viewport/clear-color query, density/offscreen color clear, and clear-color
  restore paths use the same helpers. `NodeCanvas` and `NodeStrokePreview` now share
  `legacy_ui_gl_dispatch` for active texture, texture unbind, viewport/scissor,
  clear-color, color-buffer clear, and capability query/apply adapter endpoints.
  `CanvasMode` overlay, mask, transform, and canvas-tip pick paths now also
  share that bridge for active texture, capability query/apply, viewport,
  read-framebuffer query, and RGBA8 pixel-readback adapter endpoints.
  `CanvasLayer` cube/equirect generation and frame clears also share the bridge
  for active texture, cube texture binding, viewport execution, blend
  capability execution, clear-color query/restore, and color-buffer clear
  adapter endpoints, while its cube-face framebuffer-to-texture copy uses the
  shared retained target-aware utility bridge.
  `NodePanelGrid` heightmap draw and bake setup also shares it for active
  texture, depth/blend capability query/apply, viewport query/execution, depth
  clears, and color-write-mask adapter endpoints.
  Retained RTT clear and masked-clear endpoints also share it for color-mask
  query/apply, clear-color, and buffer-clear callbacks instead of owning a
  local raw clear callback cluster.
  App startup logging, Windows early context logging/window-title detection,
  and shader capability detection now share `legacy_gl_runtime_dispatch` for
  runtime string and extension enumeration callbacks.
  Retained desktop HMD eye rendering also routes viewport
  execution through tested backend dispatch.
  Legacy `Texture2D`, `TextureManager`, `Sampler`, and `RTT` public headers no
  longer expose raw OpenGL enum defaults; default texture formats, sampler
  filters/wraps, and render-target formats resolve through backend-owned
  overloads.
  The Windows entrypoint also consumes backend-owned generic OpenGL
  error-code/info-string tokens, runtime string query ordering, and WGL
  core-context/pixel-format attribute catalogs.
  The headless OpenGL command planner consumes `pp_renderer_api` recorded
  commands and maps render-pass clear masks/values, viewport/scissor state,
  blend/depth/sampler state, texture formats, primitive modes, draw counts, and
  blit filters into GL-facing planned command data while rejecting unsupported
  enum tokens before a real GL context is needed. It also plans whole recorded
  command streams, preserving per-command planned data while counting render
  passes, draws, shader binds, shader uniforms, texture/sampler binds, texture
  uploads, mipmap generation, texture transitions, texture copies, texture
  readbacks, frame captures, passthrough commands, trace commands, unsupported
  commands, and render-pass ordering errors such as state changes outside a
  pass, nested passes, texture I/O or blits inside a pass, and unclosed passes.
  It also validates executable command dependencies, including
  shader-before-uniform and shader-plus-mesh before draw within each render
  pass, and rejects invalid texture/sampler bind slots in malformed recorded
  streams. `pano_cli record-render` emits the OpenGL plan texture/sampler bind
  counts so automation can assert backend interpretation without an OpenGL
  context.
  Desktop VR drawing also consumes backend-owned scissor/depth/blend state,
  blend/depth state query-restore, depth clear masks, active texture unit
  dispatch, and fallback 2D texture unbind dispatch; XR SDK start/stop now
  dispatches through `PlatformServices` and the tested desktop runtime policy
  prefers OpenXR before falling back to the existing Windows OpenVR bridge. Its
  retained callback endpoints now share `legacy_ui_gl_dispatch`
  with app startup, app clear, app UI viewport/scissor, and command-convert
  renderer state callbacks, so those files no longer duplicate local raw GL
  adapter clusters for capability, blend, clear, viewport, scissor, active
  texture, or 2D texture-unbind execution. 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 also now reuse the shared retained
  dispatch headers instead of file-local raw entrypoint adapters.
  Canvas mode overlay, mask, and transform paths also consume backend-owned
  blend/depth state execution, active texture unit dispatch, transform/cut
  viewport execution, 2D texture copy targets, and RGBA8 readback format tokens;
  canvas-tip pick readback now routes through tested framebuffer readback
  dispatch using the active read framebuffer, with only a local OpenGL adapter
  endpoint retained in `src/canvas_modes.cpp`. Paint-mode blend/depth state
  snapshots now use tested capability-state query dispatch.
  `NodeCanvas` panorama UI rendering also consumes backend-owned sampler
  defaults, tested viewport/clear-color query dispatch, blend/depth/scissor
  state, color-buffer clear dispatch, clear-color restore, active texture
  units, fallback 2D texture unbind targets, copy targets, and RGBA8
  render-target formats.
  Canvas resource setup also consumes backend-owned stroke-buffer
  RGBA8/RGBA16F/RGBA32F formats, flood-fill texture upload format/type,
  brush/stencil/mix sampler filters and wraps, and image channel-count texture
  formats for cube-strip imports. Clamp-to-border sampler wrap is now part of
  the backend capability catalog and test coverage.
  Early canvas draw helpers also consume backend-owned pick readback
  format/type and RTT-backed region-readback execution, stroke mixer
  depth/scissor/blend state, saved viewport and clear-state queries, active
  texture units, fallback 2D texture unbind targets, and stroke background
  framebuffer-copy dispatch.
  Canvas stroke commit also consumes backend-owned saved viewport/clear/blend
  state, history readback format/type and RTT-backed region-readback execution,
  active texture units, fallback 2D texture unbind targets, and layer
  compositing framebuffer-copy dispatch.
  Canvas layer merge rendering and explicit layer-merge compositing also consume
  backend-owned depth/blend state, active texture units, fallback 2D texture
  unbind targets, and merge framebuffer copy targets.
  Canvas equirectangular import drawing and depth export rendering also consume
  backend-owned depth/blend state and active texture units.
  Canvas thumbnail generation and object-drawing helpers also consume
  backend-owned saved viewport/clear/blend state, active texture units,
  readback format/type, framebuffer copy targets, and depth renderbuffer
  allocation plus framebuffer depth attach/detach dispatch contracts;
  `src/canvas.cpp` no longer contains raw `GL_*` constants.
  Windows desktop OpenGL context creation now consumes a tested
  `windows_wgl_core_context_3_3_config()` catalog from `pp_renderer_gl` instead
  of owning active WGL context/pixel-format attribute literals in `main.cpp`.
- `windows-msvc-vcpkg-headless` validates manifest install/configure/build/test
  for the current headless component matrix; see DEBT-0007 for remaining app
  and platform triplet migration. Root CMake target
  `panopainter_platform_build_vcpkg_ui_core` focuses that gate on the
  vcpkg-backed `pp_ui_core`/tinyxml2 boundary and `pp_ui_core_layout_xml_tests`.
- `pp_ui_core` now owns a pure `NodeLifetimeTree` target model with checked
  node handles, hidden parent/child invariants, scoped callback connections,
  pointer/keyboard capture ownership, mutation-safe event dispatch, whole-tree
  reload invalidation, and focused destroy-during-callback/layout-reload
  coverage in `pp_ui_core_node_lifetime_tests`. It also owns a pure
  `UiOverlayLifetime` coordinator with root/nested popup, modal/modeless
  dialog, capture-restore, branch-close, and layout-reload coverage in
  `pp_ui_core_overlay_lifetime_tests`. Retained `Node`/popup/dialog code still
  needs to adopt those semantics under DEBT-0063.
- `pp_legacy_app` now owns `src/legacy_ui_overlay_services.*` as the retained
  app-dialog overlay adapter. App-level progress/message/input dialogs and the
  retained about/manual/changelog, document open/save/new/browse/resize/
  layer-rename, PPBR export, shortcuts, and what's-new overlays route their root
  attachment through that helper. Retained app-menu popup template cloning and
  root attachment for File/Export/Edit/Tools/Panels/Options/About/Layers menus
  plus quick/stroke/brush panel popup root attachment also route through the same
  helper while raw `Node` ownership and callbacks remain legacy debt.
- `scripts/automation/analyze.*` runs shader validation plus a
  renderer-boundary guard that reports JSON and fails if active non-backend
  source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed
  legacy OpenGL implementation files.
- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that reports
  renderer feature flags and validates backend-owned resource creation,
  explicit texture usage flags, command order,
  render-pass color/depth/stencil clear intent, scissor state, depth state,
  blend state, texture-slot binding, sampler-state binding, texture-upload byte
  counts, texture mip-level counts, texture/mesh/shader resource debug labels, mipmap-generation commands,
  texture-state transitions, shader-uniform writes, explicit draw descriptor ranges, texture-copy regions,
  readback/frame-capture/blit descriptor validation, readback bounds, destination buffer sizes, and
  render-target blit regions, records
  render-pass-clear/scissor/depth/blend/shader-uniform/texture-bind/
  sampler-bind/draw/upload/mipmap-generation/texture-transition/texture-copy/readback/
  frame-capture/blit commands, draw mesh inputs, explicit draw ranges, and
  records trace markers and scopes without a window or GL context. Recorder
  `clear()` also resets active render-pass and trace-scope state so automation
  can reuse the same recording device after an interrupted frame.
- `pano_cli record-render` exposes the recording renderer through JSON
  automation, including backend feature flags, render-pass/depth-clear counts, scissor/depth/blend/
  shader-uniform/texture-bind/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/
  frame-capture/blit command and byte totals, trace marker/scope counts,
  labeled descriptor counts, backend resource creation counts, plus draw
  descriptor vertex/index totals. When `pp_renderer_gl` is available, it also
  emits an `openGlPlan` JSON object with the planned command count, support
  status, render-pass/draw/shader-bind/uniform/texture-upload/mipmap/
  transition/copy/readback/capture/passthrough/trace counts, unsupported
  command count, render-pass order error count, dependency error count, and
  unclosed-pass state. Its
  `--exercise-clear` mode verifies
  interrupted-frame recorder clear/reuse behavior and reports the result in
  JSON, and is covered by `pano_cli_record_render_smoke`,
  `pano_cli_record_render_exercises_clear_reset`, plus
  `pano_cli_record_render_rejects_oversized_target`.
- `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory`
  apply/undo/redo state through JSON automation and is covered by
  `pano_cli_simulate_document_history_smoke`.
- `pano_cli simulate-document-edits` exposes `pp_document` layer metadata,
  frame order, active-index, tiny face-payload state, and selection-mask state
  through JSON automation and is covered by
  `pano_cli_simulate_document_edits_smoke`.
- `pano_cli simulate-image-import` exposes embedded PNG decode and document
  face-payload attachment through JSON automation and is covered by
  `pano_cli_simulate_image_import_smoke`.
- `pano_cli import-image` exposes file-driven PNG decode and document
  face-payload attachment through JSON automation and is covered by
  `pano_cli_import_image_smoke` and
  `pano_cli_import_image_rejects_truncated_png`.
- `pano_cli export-image` exposes deterministic RGBA8 PNG writing through JSON
  automation and is covered by `pano_cli_export_image_roundtrip_smoke`; full
  legacy canvas export remains a future CLI task.
- `pano_cli save-project` exposes generated multi-layer, multi-frame PPI
  writing with layer metadata and targeted dirty-face layer/frame payloads
  through JSON automation and is covered by metadata-only and
  dirty-face-payload round-trip smoke tests; full legacy canvas save parity
  remains tracked by DEBT-0013.
- `pp_document::export_ppi_project_document` exposes pure document-to-PPI byte
  export through CTest coverage; legacy Canvas save integration remains a
  future DEBT-0010/DEBT-0013 task.
- `pano_cli simulate-document-export` exposes document export round-trip state
  through JSON automation for agent-driven checks.
- `pano_cli save-document-project` exposes file-writing document export
  automation for inspect/load round trips.
- `pano_cli apply-stroke-script` exposes file-driven stroke-script application
  to a pure document face payload and writes a PPI artifact for inspect/load
  round-trip automation.
- `pano_cli classify-open` exposes the `pp_app_core` document-open route
  contract as JSON and is covered for project files, ABR imports, PPBR
  imports, and malformed path rejection.
- `pano_cli plan-open-route` exposes `pp_app_core` document-open action
  planning as JSON and is covered for clean project open, dirty project
  discard-prompt, and ABR import-prompt states.
- `pano_cli plan-new-document` exposes `pp_app_core` new-document target,
  legacy resolution-index mapping, and overwrite-prompt planning as JSON and is
  covered for save-now, existing-target overwrite, and invalid-resolution
  states.
- `pano_cli plan-document-file` exposes `pp_app_core` document-name
  validation, legacy `.ppi` path construction, and overwrite-prompt decisions
  as JSON through the same combined save-file plan consumed by the live save-as
  dialog; it is covered for save-now and existing-target overwrite states.
- `pano_cli plan-document-version` exposes `pp_app_core` save-version suffix
  parsing, candidate path generation, collision skipping, and no-slot failure
  behavior as JSON and is covered for first-version and existing-path skip
  states.
- `pano_cli plan-export-target` exposes `pp_app_core` export target planning
  for image file exports, layer/frame collection directories, picked-directory
  stems, cube-face work-directory file sets, and MP4 suggested names as JSON
  and is covered for file, collection, cube-face, and suggested-name states.
- `pano_cli plan-export-message` exposes `pp_app_core` export completion
  dialog metadata for equirectangular image, layer/frame collection,
  depth/cube, animation MP4, and timelapse success paths as JSON, including
  platform-style destinations and suppressed/no-message paths.
- `pano_cli plan-export-report` exposes `pp_app_core` export failure and
  license-disabled dialog metadata as JSON; live export dialogs consume the
  same metadata before retained legacy export execution or logging continues.
- `pano_cli plan-export-snapshot-route` exposes the `pp_app_core` route policy
  that decides whether payload-complete document snapshots use pure
  document/renderer export writers or retained legacy exporters, including
  PNG/JPEG equirectangular target support, collection/cube target support,
  depth unsupported-target fallback, platform unsupported, and pending
  renderer-readback fallback reasons. The live retained export bridge consumes
  the same current-platform support helper for snapshot-writer attempts instead
  of carrying local Web writer gates, and depth export logs that route fallback
  before retained depth rendering.
- `pano_cli plan-export-start` exposes `pp_app_core` export availability
  planning for license-gated, demo-blocked, and missing-canvas states as JSON;
  the live image, layer, animation-frame, depth, and cube-face export dialogs
  plus MP4 animation and timelapse export dialogs consume the same start
  contract before reaching legacy canvas/recording export execution.
- `pano_cli plan-recording-session` exposes `pp_app_core` recording start,
  stop, clear, platform cleanup, frame-count reset, and export progress-total
  plus export progress-dialog planning as JSON; the live recording controls
  consume those contracts before reaching legacy recording threads, retained
  PBO readback call sites, and MP4 encoder execution.
- `src/legacy_recording_services.*` is the current app-shell bridge for
  recording start/stop/clear and MP4 export execution. It keeps those live paths
  on the `pp_app_core` contracts while legacy recording thread ownership,
  retained PBO readback call sites, progress UI lifetime, platform cleanup, and
  `MP4Encoder` execution remain
  tracked by `DEBT-0037`.
- `pano_cli plan-share-file` exposes `pp_app_core` share availability planning
  as JSON for unsaved and saved document paths; the live platform share command
  consumes the same contract before reaching iOS/macOS sharing bridges or
  retained no-op platform branches.
- `pano_cli plan-picked-path` exposes `pp_app_core` selected-path planning as
  JSON for empty and non-empty file picker results; live image/file/save/
  directory picker branches consume the same contract before invoking retained
  platform callbacks or legacy picker bridges.
- `pano_cli plan-display-file` exposes `pp_app_core` external file presentation
  planning as JSON for empty and non-empty paths; the live display-file command
  consumes the same contract before retained platform open-file bridges.
- `pano_cli plan-keyboard-visibility` exposes `pp_app_core` virtual keyboard
  visibility planning as JSON for hidden and visible states; live show/hide
  keyboard requests consume the same contract before retained mobile platform
  keyboard bridges.
- `pano_cli plan-cursor-visibility` exposes `pp_app_core` cursor visibility
  planning as JSON for hidden and visible states; live canvas cursor requests
  consume the same contract before retained desktop platform cursor bridges.
- `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write` expose
  `pp_app_core` clipboard text planning as JSON, including empty text writes;
  live clipboard get/set requests consume the same contracts before retained
  platform clipboard bridges.
- `pp_platform_api` exposes the SDK-free `PlatformServices` interface for
  startup storage path preparation, clipboard text, cursor visibility,
  virtual-keyboard visibility, external file display, file sharing, native
  app/window close, UI-thread lifecycle hooks, render-context lifecycle hooks,
  render-target binding hooks, render platform hint hooks, render-capture frame
  hooks, render debug callback hooks, per-frame platform hooks, picker
  callbacks, recording cleanup, exported-image publishing, persistent storage
  flushing, document browse roots, working-directory picker policy and
  display-path formatting, canvas input tip visibility and pressure remapping,
  native UI/window state saving, live asset/layout reload policy, diagnostic
  stacktrace/crash hooks,
  SonarPen availability/startup, VR mode lifecycle,
  PPBR export data-directory policy,
  prepared-file writable target selection, network TLS verification policy, and
  prepared-file save/download handoff; PPBR
  and MP4 export dialogs consume
  the same prepared-file policy at runtime instead of spelling mobile/Web
  branches locally, layer/frame collection export dialogs consume the
  work-directory collection policy before `pp_app_core` plans immediate
  collection export versus directory-picker stem export, and app-owned curl
  upload/download/license helpers consume the TLS policy instead of spelling
  Android branches locally; retained `Asset::open_url`, `LogRemote::net_init`,
  and cloud browse-dialog curl sites now consume the same default platform TLS
  policy helper in `pp_platform_api` instead of spelling Android branches
  locally;
  retained platform-family decisions for exported-image publishing,
  persistent-storage flushing, document browse roots, working-directory picker
  availability, prepared-file target planning, work-directory collection export
  policy, PPBR data-directory override policy, SonarPen availability, live
  asset reload policy, native UI/window state saving, layout XML file mtime
  reload policy, recording cleanup policy, default canvas resolution, and canvas
  tip visibility now live in the tested `platform_policy` catalog and are
  consumed by both `WindowsPlatformServices` and the retained non-Windows
  fallback;
  Windows
  live app execution now uses injected
  `WindowsPlatformServices` from
  `src/platform_windows/windows_platform_services.*` in `pp_platform_windows`,
  while non-Windows platforms still reach retained platform bridges through
  the debt-tracked adapter isolated in
  `src/platform_legacy/legacy_platform_services.*`.
- `src/app.h` now forward-declares retained platform handles instead of
  including Objective-C, Android, or GLFW SDK headers. The full platform SDK
  headers needed by retained non-Windows bridges are included in
  `src/platform_legacy/legacy_platform_services.cpp`, keeping central app
  header consumers lighter while `DEBT-0055` tracks removal of those handles
  from `App`.
- `pp_platform_api` also owns `plan_asset_file_load`, the SDK-free file-load
  policy consumed by `LayoutManager` for XML layout reload decisions. The pure
  probed decision and platform-family policy preserve desktop mtime-based
  reload behavior and non-desktop already-loaded skip behavior while keeping
  direct platform guards out of the shared layout parser; the live wrapper still
  owns the retained `stat` probe until platform storage/file-watch services
  replace it.
- `pp_renderer_gl` owns the tested `OpenGlInitialState` startup depth/blend
  policy and dispatch application consumed by `App::init`, tested runtime
  version/vendor/renderer/GLSL string query dispatch consumed by `App::init`
  and Windows startup logging/title construction, tested default clear
  color/buffer dispatch consumed by `App::clear`, tested app UI
  viewport/scissor dispatch consumed by `App::draw` and `App::vr_draw_ui`,
  tested generic capability query/apply and buffer-clear dispatch consumed by
  VR draw state setup and restore, tested saved-state snapshot/restore dispatch
  consumed by the retained `gl_state` utility, tested texture lifecycle/readback dispatch consumed by
  the retained `Texture2D` utility, tested framebuffer blit/readback dispatch
  consumed by retained `RTT` resize/copy/readback and RGBA8 region-readback
  paths, tested texture-update dispatch consumed by retained `RTT` RGBA8
  dirty-region writes, tested render-target texture parameter, framebuffer allocation/delete,
  color/depth attachment, status-check, and binding-restore dispatch consumed
  by retained `RTT::create`/`RTT::destroy`, tested RTT render-target clear,
  masked color clear with color-write-mask restore, and texture-bind dispatch,
  tested active-texture dispatch consumed by retained Canvas, Font, NodeCanvas,
  and NodeStrokePreview texture-unit switches,
  tested viewport/scissor/capability dispatch consumed by retained Canvas,
  NodeCanvas, NodeStrokePreview, and HMD render-state paths,
  tested capability-state query dispatch consumed by retained `Canvas`,
  `NodeCanvas`, `CanvasMode`, and `NodePanelGrid` draw-state restore paths,
  tested viewport query, clear-color query, and clear-color restore dispatch
  consumed by retained `Canvas`, `CanvasLayer`, `NodeCanvas`, and
  `NodeStrokePreview` draw-state paths,
  tested color-write-mask dispatch consumed by retained `NodePanelGrid`
  transparent heightmap rendering, tested texture readback dispatch consumed by
  `NodePanelGrid` texture-resize preservation,
  tested pixel-buffer allocation/readback/map/unmap/delete dispatch
  consumed by retained `PBO` recording readbacks, tested framebuffer-to-texture
  copy dispatch consumed by retained canvas/UI paint paths and CanvasLayer
  cube-face generation, tested cube texture bind dispatch consumed by
  CanvasLayer equirect export, tested framebuffer bind/restore dispatch consumed by
  retained `RTT` render-target pass entry
  and exit paths, tested depth renderbuffer allocation/delete and framebuffer
  depth attach/detach dispatch consumed by retained RTT and canvas object-drawing helpers,
  shared legacy UI GL dispatch consumed by retained `NodeBorder`, `NodeImage`,
  `NodeImageTexture`, `NodeColorWheel`, `NodeAnimationTimeline`, `NodeScroll`,
  `NodeText`, `NodeTextInput`, and `NodeViewport` blend-state, fallback
  texture unbind, viewport, color clear, and clear-color restore paths,
  plus retained `NodeCanvas` and `NodeStrokePreview` active-texture, fallback
  texture unbind, viewport/scissor, clear-color, color-buffer clear, and
  capability query/apply draw-state adapter endpoints,
  plus retained `CanvasMode` active-texture, capability query/apply, viewport,
  read-framebuffer query, and RGBA8 pixel-readback adapter endpoints,
  plus retained `NodePanelGrid` active-texture, depth/blend capability
  query/apply, viewport query/execution, depth-clear, and color-write-mask
  adapter endpoints,
  tested convert-command state dispatch consumed by
  `App::cmd_convert`, tested render platform hint dispatch consumed by
  `WindowsPlatformServices` and the retained macOS legacy fallback, tested
  debug-output state dispatch consumed by `WindowsPlatformServices`, plus
  renderer API to OpenGL token mapping and command-planning contracts used by
  the OpenGL parity work.
- `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability,
  new-document warning, publish prompt, prompt title/message/captions, and
  save-before-upload planning as JSON;
  the live cloud upload command consumes the same start contract before
  reaching legacy UI, canvas save, and network upload execution.
- `pano_cli plan-cloud-upload-all` exposes bulk cloud upload file-count,
  progress UI availability, progress-total clamping, and progress dialog
  metadata as JSON; the live upload-all command consumes the same contract
  before reaching legacy asset file listing, OpenGL context guard, progress UI,
  and network upload
  execution.
- `pano_cli plan-cloud-browse` exposes `pp_app_core` cloud browse availability
  and selected-file download planning as JSON; the live cloud browse command
  consumes those contracts before reaching legacy dialog, network download,
  canvas project-open, layer UI, and action-history execution.
- `src/legacy_cloud_services.*` is the current app-shell bridge for cloud
  upload, bulk upload, browse dialog, and download execution. It keeps those
  live paths on the `pp_app_core` `CloudServices` contract while the app-owned
  curl upload/download/license helpers now ask `PlatformServices` for TLS
  verification policy and retained dialog/network curl sites use the shared
  default platform TLS helper. Cloud upload warning/publish/success copy, bulk
  upload progress title, and download-progress copy now come from
  `pp_app_core` dialog/progress plans before retained app-dialog bridge
  execution. Legacy save-before-upload, progress/message UI lifetime,
  network upload/download helper ownership, OpenGL context guarding,
  `NodeDialogCloud`, project open, layer refresh, and action-history reset
  remain tracked by `DEBT-0038`.
- `pano_cli simulate-app-session` exposes `pp_app_core` project-open,
  app-close, save, save-as, save-version, and save-before-workflow decisions
  as JSON and is covered for clean, dirty, already-prompting, missing-canvas,
  new-document, save-as, save-version, and dirty-save-version states.
- Save, New Document, and Browse dialogs now use `PlatformServices` for
  working-directory picker availability and displayed absolute-path formatting,
  so `src/node_dialog_open.cpp` and `src/node_dialog_browse.cpp` no longer own
  desktop-only path picker branches.
- `pp_app_core_document_route_tests` covers the app document-open route
  contract for PPI/project files, ABR imports, PPBR imports, inner-dot names,
  and malformed paths before the live `App::open_document` performs UI or
  legacy canvas work.
- `pp_app_core_document_export_tests` covers export file targets, collection
  directory/stem targets, picked-directory stems, work-directory versus
  picker-stem collection target planning, cube-face legacy work-path sets, MP4
  suggested names, cube-face write/publish service execution order and failure
  handling, and invalid export naming inputs, plus export-start license/canvas availability decisions,
  export menu executor dispatch, file/stem/collection export execution
  dispatch, failed directory creation preservation, named depth/cube export
  dispatch, malformed export target rejection, document-snapshot export route
  policy, video export dispatch for animation MP4/timelapse paths, and empty
  video-path rejection.
- `pp_app_core_document_recording_tests` covers recording start/stop, clear,
  platform recorded-file cleanup, frame-count reset, export progress totals,
  oversized progress-total clamping, and recording-worker encode-wake
  eligibility when recording, encoder, and canvas-document state vary.
- `pp_app_core_document_sharing_tests` covers saved-path gating before platform
  share execution.
- `pp_app_core_document_platform_io_tests` covers empty selected-path filtering
  and non-empty picked-path callback planning, plus empty/non-empty display-file
  planning before platform picker/display callbacks, plus virtual keyboard
  show/hide planning before platform keyboard callbacks, plus cursor visibility
  planning before platform cursor callbacks, plus clipboard read/write
  planning before platform clipboard callbacks.
- `pp_app_core_app_preferences_tests` covers UI scale/font-scale planning,
  scale-option selection, viewport scale planning, RTL direction planning,
  timelapse start/stop/no-op decisions, VR mode success/failure dispatch,
  simple stored preferences, and `AppPreferenceServices` execution dispatch for
  options-menu side effects.
- `pp_app_core_app_dialog_tests` covers app-level progress/message/input dialog
  metadata planning, progress initialization, negative progress-total clamping,
  message cancel-button/caption policy, input OK-caption propagation, and malformed
  empty OK-caption rejection without requiring legacy `Node*` dialogs.
- `src/legacy_app_dialog_services.*` is the current app-shell bridge between
  `pp_app_core` app dialog plans and retained `NodeProgressBar`,
  `NodeMessageBox`, and `NodeInputBox` creation. `App::show_progress`,
  `App::message_box`, and `App::input_box` now act as thin adapters while
  layout insertion, callback wiring, and dialog lifetime remain tracked by
  `DEBT-0058`.
- `pp_app_core_app_startup_tests` covers startup run-counter increment
  planning, optional auto-timelapse/license/VR-controller decisions, negative
  and overflow run-counter rejection, stable full startup dispatch ordering,
  split persistence/runtime dispatch, and malformed startup-plan rejection.
- `pp_app_core_app_frame_tests` covers the legacy initial surface default,
  idle/redraw/animation update gating, canvas-stroke draw eligibility, VR UI
  visibility, main UI suppression in VR-only mode, tick layout selection,
  resize render-target/redraw projection, invalid resize rejection, redraw
  reset planning, UI observer clipping, on-screen enter/leave transition
  decisions, scissor projection, and malformed observer geometry rejection.
- `pp_app_core_app_thread_tests` covers render/UI task dispatch, immediate
  same-thread execution, unique queued-task replacement, stopped-worker
  no-wait behavior, render queue context wrapping, UI tick redraw scheduling,
  UI-loop frame/FPS/reload timer thresholds, malformed timer rejection, redraw
  frame-count projection, and thread start/stop intents.
- `pp_app_core_app_input_tests` covers pointer coordinate normalization,
  invalid pointer/gesture inputs, designer-first mouse routing, mouse-cancel
  routing, gesture midpoint/distance/delta math, main-layout routing, key state
  mutation intent, VR spacebar camera-sync intent, UI visibility toggle target
  selection, malformed UI-toggle layout rejection, and stylus touch-lock intent.
- `pp_app_core_app_shutdown_tests` covers legacy shutdown cleanup staging for
  UI-state save, stroke-preview renderer shutdown, recording stop,
  texture/shader invalidation, layout unload, render-target/mesh destruction,
  panel-node release, and quick-mode cleanup.
- `pp_app_core_command_convert_tests` covers command-line panorama conversion
  sequencing for renderer-state setup, temporary canvas allocation, project
  open, equirectangular export, malformed input rejection, malformed-plan
  rejection, and exact executor dispatch order.
- `pp_platform_api_tests` covers service dispatch for clipboard read/write,
  empty clipboard writes, cursor visibility, virtual-keyboard visibility,
  external file display, file sharing, VR lifecycle, layout/asset file load
  policy including pure probed reload behavior, platform-family
  export/storage/browse/prepared-file/UI-state/canvas policies, and picker
  callbacks without platform SDK headers or a window.
- `pp_app_core_document_cloud_tests` covers cloud upload no-canvas,
  new-document warning, clean publish prompt, and dirty save-before-upload
  decisions, plus cloud browse no-canvas/show-browser and selected-download
  decisions, plus bulk upload progress visibility, zero-file, and clamped
  progress-total decisions, plus cloud download/upload transfer request
  validation, progress-callback enablement, TLS-verification policy, and
  zero/negative/overrun transfer-progress fraction guards.
- `pp_app_core_document_session_tests` covers clean and dirty app session,
  document-open action planning and executor dispatch/rejection, save-request,
  close-request executor dispatch/no-op preservation, document-save executor
  dispatch/no-op preservation, document-session prompt metadata for close,
  save-before-workflow, overwrite, and save-error dialogs,
  save-before-workflow executor dispatch,
  new-document target/resolution/overwrite planning and executor dispatch,
  document file target, combined save-file overwrite planning and executor
  dispatch, plus save-version target decisions and executor validation without
  requiring a window, canvas, or message box.
- `src/legacy_document_open_services.*` is the current app-shell bridge between
  `pp_app_core` document-open plans and live ABR/PPBR import prompts,
  unsaved-project discard prompts, project opening, layer UI refresh, title
  updates, and action-history clearing. Accepted brush import prompts now
  delegate import execution to `src/legacy_brush_package_import_services.*`;
  remaining legacy document-open ownership is tracked by `DEBT-0039`.
- `src/legacy_brush_package_import_services.*` is the current app-shell bridge
  between `pp_app_core` ABR/PPBR brush package import requests and live
  `NodePanelBrushPreset::import_abr`/`import_ppbr` execution. It preserves the
  detached legacy import worker threads and preset-panel storage ownership while
  brush asset/paint/UI ownership is tracked by `DEBT-0048`.
- `pp_app_core_brush_package_import_tests` covers ABR and PPBR import kind
  naming, path validation, service dispatch, and empty-path rejection without
  requiring a window, brush preset panel, or filesystem read.
- `src/legacy_document_session_services.*` is the current app-shell bridge
  between `pp_app_core` document-session decisions and live close prompts,
  save dialogs, save-version routing, existing-project saves, and
  save-before-workflow prompts. Close, save-before-workflow, new-document
  overwrite, Save As overwrite, and save-error prompt text/captions now come
  from the pure document-session prompt catalog exposed through
  `pano_cli plan-document-session-prompt`; retained prompt boxes are created
  through `src/legacy_app_dialog_services.*` before the document-session bridge
  wires callbacks. It also bridges accepted new-document plans to legacy canvas
  resize/layer setup, overwrite prompts, title updates, and keyboard/dialog
  cleanup. Accepted Save As and Save Version plans now also
  route through this bridge before reaching legacy project-save execution,
  overwrite prompts, document field updates, title updates, and keyboard/dialog
  cleanup. Existing Save, Save As, Save Version, and save-before-workflow
  prepare and log a payload-bearing canvas document snapshot report, run the
  app-core pure PPI save-writer route for payload-complete snapshots, log
  generated byte counts, and derive project-save target/tmp/timelapse paths
  plus direct-vs-temporary write-mode decisions through `pp_app_core` before
  delegating to retained `Canvas::project_save`. Direct save, successful
  temporary swap, remove-failure, and rename-after-remove commit outcomes are
  now classified by the same app-core planner before retained success metadata
  mutation. Post-commit clean/new-document metadata, timelapse sidecar,
  platform flush, progress cleanup, and title-refresh decisions also come from
  `pp_app_core`, while retained code still performs the mutations and sidecar
  serialization.
  Retained legacy UI/canvas
  execution and actual live save serialization remain tracked by `DEBT-0040`,
  `DEBT-0041`, and `DEBT-0042`; the pure snapshot-to-PPI export handoff is
  already validated in `pp_app_core_document_canvas_tests` and
  `pano_cli_plan_canvas_document_snapshot_payload_smoke`.
- `src/legacy_document_export_services.*` is the current app-shell bridge
  between `pp_app_core` document export execution plans and live equirectangular,
  layers, animation-frame, depth, and cube-face export calls. It preserves
  platform-specific export messages, directory creation, picker-selected stem
  exports, Web prepared-file handoff, and legacy `Canvas` export execution while
  retained renderer/document/platform ownership is tracked by `DEBT-0043`.
  Equirectangular, layer, animation-frame, and cube-face execution now prepare
  the document snapshot plus shared paint-renderer export-readiness report
  before retained calls; cube-face export writes pure face-PNG bytes to the
  app-core planned legacy work-directory face paths through the app-core
  write/publish executor before falling back to the retained writer, PNG
  equirectangular export writes the pure document/paint-renderer
  equirectangular PNG through the app-core file write/publish executor before
  retained fallback, payload-complete layer and
  animation-frame collections write pure document/paint-renderer PNG sequences
  through the app-core collection write/publish executor, depth export plans
  image/depth targets and logs the paint-renderer render plan plus shared
  renderer readiness before retained execution, and JPEG equirectangular export
  writes a pure document/paint-renderer JPEG with GPano XMP metadata through
  the same app-core file write/publish executor before retained fallback while
  Web/incomplete-readback collection cases remain on older retained paths. It
  also bridges timelapse and animation MP4 export picker-selected paths while
  preserving desktop worker-thread timelapse behavior, mobile/Web save
  callbacks, `App::rec_export`, animation `Canvas::export_anim_mp4`, and
  success messages; retained video/export ownership is tracked by `DEBT-0044`.
- `src/legacy_brush_package_export_services.*` is the current app-shell bridge
  between `pp_app_core` PPBR brush package export requests and live
  `NodePanelBrushPreset::export_ppbr` execution. It preserves dialog metadata,
  the retained legacy `Image` header object, desktop worker-thread export,
  mobile/Web save completion, and dialog lifetime while the PPBR preview
  data-directory override and export success-dialog metadata now come from
  `PlatformServices`/`pp_app_core`; remaining brush asset/storage/UI/platform
  ownership is tracked by `DEBT-0047`.
- `src/assets/brush_package.*` owns the first headless PPBR package helpers:
  header validation, legacy-compatible version acceptance, export path
  normalization, preview-data-directory planning, and imported brush
  tip/pattern image target paths. Live export maps the active platform PPBR
  data-directory policy into these helpers, and live
  `NodePanelBrushPreset::export_ppbr`/`import_ppbr` and ABR import image writes
  consume these helpers, but legacy Serializer/Image payload parsing, preview
  rendering, preset storage, duplicate policy, and strict-version cleanup remain
  tracked by `DEBT-0047`, `DEBT-0048`, and `DEBT-0049`.
- `pp_assets_brush_package_tests` covers PPBR header parsing, truncated/bad
  magic rejection, legacy version tolerance, export package/data path planning,
  legacy extension containment, paths the legacy regex could not match, brush
  tip/pattern image target planning, and invalid imported image target inputs.
- `pp_app_core_brush_package_export_tests` covers PPBR export request path
  validation, metadata preservation, legacy-flexible destination/export-data
  combinations, service dispatch, malformed request rejection, and legacy
  success-dialog metadata without requiring a window, brush preset panel, or
  filesystem write. `pano_cli plan-brush-package-export` emits the same
  success-dialog plan for automation.
- `src/legacy_history_services.*` is the current app-shell bridge between
  `pp_app_core` history plans and legacy `ActionManager`; toolbar and
  `NodeCanvas` hotkeys share it while document-history extraction remains
  tracked by DEBT-0026.
- `src/legacy_document_canvas_services.*` is the current app-shell bridge
  between `pp_app_core` canvas-clear/resize plans and legacy `Canvas`;
  toolbar clear, Layer menu clear, and the resize dialog share it while
  document/canvas execution extraction remains tracked by DEBT-0020 and
  DEBT-0028.
- `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON`
  through the vcpkg preset; default and Android validation still use the
  retained vendored fallback tracked by DEBT-0012.

Known warnings after the current CMake app build:

- Legacy code/vendor warnings under `/W4`.
- `pp_legacy_vendor` intentionally owns retained third-party source builds for
  now, including JPEG, SQLite, Yoga, poly2tri, GLAD, fmt, Wacom utilities, and
  other patched/embedded sources. Each dependency should either move to vcpkg,
  an SDK import target, or a documented permanent vendored target.
- `pp_legacy_assets_io` is an object-library containment boundary for retained
  ABR, asset/file, binary stream, image, serializer, and settings code. It
  should shrink as app I/O consumes `pp_assets` directly. `src/asset.h` is now
  Android-SDK-free and exposes `Asset::set_android_asset_manager` with opaque
  handles instead of public mutable Android asset-manager state or SDK forward
  declarations; concrete Android asset-manager headers remain in `asset.cpp`
  and the retained Android entrypoint while DEBT-0056 tracks replacing the
  static Android asset bridge with injected asset storage. The retained Android
  standard package now links `native-lib` for arm64 through the refreshed C++23
  package CMake path; DEBT-0060 tracks the generated `nanort` overlay that keeps
  that package gate clean without modifying the vendor submodule.
- `pp_legacy_paint_document` is an object-library containment boundary for
  retained action, bezier, brush, canvas, canvas-layer, and event code. It
  should shrink as app painting and document behavior consume `pp_paint` and
  `pp_document` directly. Shared `canvas.h` no longer owns the platform
  `CANVAS_RES` macro; default canvas allocation now asks `PlatformServices`,
  preserving the WebGL 512 default through the tested `pp_platform_api`
  platform policy while DEBT-0057 tracks moving that policy into an injected
  Web platform service.
- `pp_legacy_engine` intentionally contains retained legacy runtime shell
  sources for now, so it concentrates existing legacy tablet, video, HMD, log,
  and low-level utility warnings until those paths move to cleaner component
  ownership.
- `pp_legacy_renderer_gl` is an object-library containment boundary because
  the retained OpenGL runtime classes still include legacy app/engine headers
  and are still consumed directly by canvas and UI classes. It should become a
  normal backend library once those call sites depend on `pp_renderer_api`.
  `pp_renderer_gl` now owns OpenGL runtime build-target classification through
  CMake target compile definitions and `opengl_runtime_for_current_build()`,
  so app shader startup asks the backend for desktop GL/GLES/WebGL policy
  instead of carrying local platform branches. It also owns headless-tested
  OpenGL extension enumeration through `query_opengl_extensions`, moving the
  extension count/string query loop out of `app_shaders.cpp`. Startup feature
  negotiation now uses `query_opengl_capability_detection`, so extension
  enumeration, runtime capability policy, and renderer-neutral feature
  conversion are validated together before the retained `ShaderManager` static
  flags are updated. `RenderDeviceFeatures` now includes float32-linear
  filtering, and live canvas, diagnostics, and grid lightmap/bake decisions
  consume that renderer-neutral snapshot rather than reading retained
  `ShaderManager::ext_*` fields directly.
- `pp_legacy_ui_core` is an object-library containment boundary because the
  retained base `Node` controls still depend on legacy renderer and app
  headers. It should shrink as layout parsing, colors, generic controls,
  tested node lifetime handles/connections, and text/image primitives move to
  `pp_ui_core`.
- `pp_panopainter_ui` currently surfaces existing legacy `Node`/`Serializer`
  header and static-analysis warnings while it still depends on
  `pp_legacy_app`; these should be reduced as the UI core/app UI boundary is
  tightened instead of suppressed globally.
- `pp_app_core` is the first pure app-engine target consumed by
  `panopainter_app`; it should grow only with UI-free command routing,
  validation, and app service contracts that can be tested without a window.
- `panopainter_app` currently surfaces existing app orchestration, GLM,
  base64, VR, and serializer warnings now that app sources live in the
  composition target; warning cleanup should follow component ownership rather
  than be hidden with target-wide suppressions.
- Visual Studio vcpkg manifest warning because manifest mode is not enabled.
- `LNK4099` missing `yuv.pdb` for retained libyuv binaries.
- `LNK4098` runtime library conflict from retained vendor binaries.

Platform-specific commands should be added here when verified locally.
