# Modernization Task Tracker

Status: live
Last updated: 2026-06-15

This file turns the modernization roadmap into small, measurable work items.
The roadmap explains direction, the debt log explains why shortcuts remain, and
this tracker is the execution queue. Prefer closing one task here over adding a
new broad roadmap paragraph.

## Operating Rules

- Pick one `Ready` task at a time unless the user asks for planning only.
- Keep each slice small enough to validate and commit in one session.
- Do not claim percentage progress for "narrowed" debt. Points move only when a
  task row is changed to `Done`.
- A task is `Done` only when its listed checks pass, the debt log is updated or
  closed as applicable, and the roadmap/task score is updated.
- If a task proves too large, split it before editing code. The original task
  stays `Ready` or becomes `Blocked` with the reason.
- After a verified task is committed and pushed, reset conversation context
  before starting the next task when practical.
- When the user asks for subagents or delegation, follow
  `docs/modernization/director-workflow.md` and keep each delegated task mapped
  to a row in this tracker.

## Progress Scorecard

The current score is intentionally conservative. It should move in visible,
auditable steps rather than by subjective estimates.

| Area | Weight | Current | Progress Rule |
| --- | ---: | ---: | --- |
| Build and CMake ownership | 15 | 13 | Root CMake owns active source lists, app/tool targets, and retained package entrypoints. |
| Test and automation coverage | 15 | 9 | Headless, platform, package, and focused validation commands exist and are current. |
| Pure component behavior ownership | 15 | 8 | Behavior lives in `pp_*` components and is consumed by live adapters. |
| Legacy adapter retirement | 20 | 12 | `legacy_*_services` and singleton bridges are deleted or reduced to trivial composition. |
| Renderer boundary and OpenGL parity | 15 | 11 | Live render/export/readback paths execute through renderer interfaces with parity checks. |
| Platform and package parity | 10 | 8 | Required platforms have root CMake/package validation and injected platform services. |
| Hardening and future backend readiness | 10 | 4 | Edge, fuzz, golden, stress, and backend-lab gates exist for high-risk paths. |
| **Total** | **100** | **67** | Only completed tasks below may change this number. |

When updating `Current`, add a dated note under "Completed Task Log" with the
task id, points moved, validation command, and commit hash.

## Task States

| State | Meaning |
| --- | --- |
| `Ready` | Clear enough for an agent to execute. |
| `In progress` | Actively being changed in the current slice. |
| `Blocked` | Needs a user decision, missing toolchain, or a prior task. |
| `Done` | Validated, documented, committed, and pushed. |

## Planning Audit - 2026-06-14

Current progress covers many retained stroke, draw-merge, export, platform,
and app-core planner seams, but it is not sufficient to declare the
modernization roadmap final. Missing coverage before Vulkan, Metal, and broad
UI rewrites:

- Renderer contract freeze: `pp_renderer_api` needs an explicit backend
  conformance contract before any Vulkan or Metal lab can be useful.
- Backend conformance automation: OpenGL and recording backends need the same
  command/resource/readback/transition fixtures that future Vulkan/Metal
  targets must pass.
- Backend lab scaffolds: Vulkan and Metal must be opt-in, non-production
  targets with validation-layer or command-encoding smoke tests only.
- UI safety: retained `Node` migration needs thread-affinity, checked handles,
  scoped callbacks, and mutation-safe dispatch gates, not only one popup family.
- Threading safety: render/UI queue behavior needs race/regression tests around
  cancellation, shutdown, nested dispatch, and cross-thread misuse.
- Platform gates: Apple/Linux/WebGL/AppX package readiness must be part of the
  backend/UI gate because renderer and UI boundaries are platform-sensitive.
- Task hygiene: the historical tracker hygiene was normalized under `AUD-001`;
  new agents should still treat historical score rows cautiously before using
  them for accounting.

## Ready Queue

### MT-001 - Adopt Measurable Task Tracking

Status: Done
Score: no score movement
Debt: none
Scope: `docs/modernization/tasks.md`, `docs/modernization/roadmap.md`

Steps:

- Add this tracker.
- Link it from the roadmap.
- Make the scorecard the source for percentage claims.

Done Checks:

- `docs/modernization/roadmap.md` points agents to this file.
- The tracker has task states, scoring rules, and at least one ready queue.

Validation:

```powershell
git diff -- docs\modernization\roadmap.md docs\modernization\tasks.md
```

### ADP-001 - Remove History Bridge From Document Resize And Canvas Clear

Status: Done
Score: +1 legacy adapter retirement
Debt: `DEBT-0020`, `DEBT-0027`
Scope: `src/legacy_document_canvas_services.*`,
`src/app_core/document_resize.h`, `tests/app_core/document_resize_tests.cpp`,
related canvas-clear tests only

Goal:

Make document resize and canvas-clear execution consume app-core history
commands directly instead of routing through `legacy_history_services`.

Done Checks:

- `src/legacy_document_canvas_services.*` no longer includes
  `legacy_history_services.h`.
- Resize still executes in order: resize, title update, history clear.
- Canvas clear still records undo and marks the document unsaved when a canvas
  exists.
- `docs/modernization/debt.md` narrows or closes the affected removal condition.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_resize|pp_app_core_document_canvas|pano_cli_plan_document_resize|pano_cli_plan_canvas_clear" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### ADP-002 - Remove History Bridge From Layer Operations

Status: Done
Score: +1 legacy adapter retirement
Debt: `DEBT-0021`
Scope: `src/legacy_document_layer_services.*`,
`src/app_core/document_layer.h`, `tests/app_core/document_layer_tests.cpp`

Goal:

Move layer add/remove/merge/clear/rename history side effects into tested
app-core execution plans so the live layer bridge no longer calls
`legacy_history_services`.

Done Checks:

- `src/legacy_document_layer_services.*` no longer includes
  `legacy_history_services.h`.
- Layer operations still preserve undo/history behavior covered by
  `pp_app_core_document_layer_tests`.
- `pano_cli plan-layer-operation`, `plan-layer-menu`, and
  `plan-layer-rename` JSON remains compatible.
- `docs/modernization/debt.md` records the narrowed or closed layer-history
  adapter dependency.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_layer|pano_cli_plan_layer" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### ADP-003 - Remove History Bridge From Document Open And Session Save

Status: Done
Score: +1 legacy adapter retirement
Debt: `DEBT-0039`, `DEBT-0040`, `DEBT-0042`
Scope: `src/legacy_document_open_services.*`,
`src/legacy_document_session_services.*`,
`src/app_core/document_session.*`, `src/app_core/document_route.*`,
matching tests only

Goal:

Make document-open, close, save, save-before-workflow, Save As, and Save Version
history effects explicit app-core outputs instead of direct
`legacy_history_services` calls in the live bridges.

Done Checks:

- `src/legacy_document_open_services.*` and
  `src/legacy_document_session_services.*` no longer include
  `legacy_history_services.h`.
- Existing dirty-document, save-before, new-document, Save As, and Save Version
  plans preserve their JSON contracts.
- The debt log is updated for every debt id listed above.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_route|pp_app_core_document_session|pano_cli_plan_open_route|pano_cli_simulate_app_session|pano_cli_plan_document_file|pano_cli_plan_document_version" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### ADP-004 - Make Dialog Creation A UI Factory Boundary

Status: Done
Score: +2 legacy adapter retirement
Debt: `DEBT-0058`, `DEBT-0063`
Scope: `src/legacy_app_dialog_services.*`,
`src/legacy_ui_overlay_services.*`, `src/app_dialogs.cpp`,
`src/app_core/app_dialog.h`, dialog tests only

Goal:

Keep app-core dialog metadata pure, but move retained
`NodeProgressBar`/`NodeMessageBox`/`NodeInputBox` construction behind one
`pp_panopainter_ui` or retained UI factory function. `App` should ask for a
dialog object through an interface instead of knowing individual node creation
details.

Done Checks:

- `App::show_progress`, `App::message_box`, and `App::input_box` still preserve
  captions, cancel behavior, and keyboard behavior.
- New factory path has focused tests or existing `pp_app_core_app_dialog_tests`
  plus a smoke command proving the live adapter still builds.
- The debt log states exactly which raw-node lifetime hazards remain.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_app_dialog|pano_cli_plan_app_dialog" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### ADP-005 - Convert One Popup/Dialog Family To Checked Overlay Lifetime

Status: Done
Score: +2 legacy adapter retirement
Debt: `DEBT-0063`
Scope: choose exactly one family from `src/node_dialog_open.cpp`,
`src/node_dialog_browse.cpp`, `src/node_panel_quick.cpp`,
`src/node_panel_stroke.cpp`, or `src/node_combobox.cpp`, plus
`src/legacy_ui_overlay_services.*`

Goal:

Adopt `pp_ui_core` overlay lifetime semantics for one retained popup/dialog
family before trying to rewrite all retained UI nodes.

Done Checks:

- The chosen family no longer owns open-coded root insertion, outside-click
  release, close callback wiring, or destroy-during-callback behavior.
- Existing UI behavior is preserved.
- Tests cover missing root/template handling and close/release behavior for the
  chosen family.
- The debt log names the completed family and the remaining families.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_ui_core_overlay_lifetime|pp_ui_core_node_lifetime" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### ADP-006 - Move App-Owned Dialog Overlays To Checked Handles

Status: Done
Score: +1 legacy adapter retirement
Debt: `DEBT-0058`, `DEBT-0063`
Scope: `src/legacy_ui_overlay_services.*`, `src/legacy_app_dialog_services.*`,
dialog tests only

Closeout: `33ff4b9b`

Goal:

Move the retained app-owned progress/message/input dialog overlays off the
attach-only root insertion path so the factory-owned live dialogs participate in
the checked overlay lifetime registry before broader dialog cleanup.

Done Checks:

- `App::show_progress`, `App::message_box`, and `App::input_box` still preserve
  existing captions, cancel behavior, and keyboard behavior.
- `create_legacy_progress_dialog_overlay`, `create_legacy_message_dialog_overlay`,
  and `create_legacy_input_dialog_overlay` open through checked overlay handles
  when the main layout anchor is available instead of only attaching raw root
  children.
- `DEBT-0058` and `DEBT-0063` describe the reduced app-dialog lifetime surface
  and the remaining retained dialog families.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_app_dialog|pp_ui_core_node_lifetime|pp_ui_core_overlay_lifetime" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-006 | +1 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_app_dialog\|pp_ui_core_node_lifetime\|pp_ui_core_overlay_lifetime" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64` | `33ff4b9b` |

### ADP-007 - Move Settings Dialog To Checked Overlay Lifetime

Status: Done
Score: +1 legacy adapter retirement
Debt: `DEBT-0035`, `DEBT-0063`
Scope: `src/legacy_app_shell_services.cpp`, `src/legacy_ui_overlay_services.*`,
settings dialog path only

Closeout: `8db859cb`

Goal:

Move the main-toolbar settings dialog opening path off raw
`layout[main_id]->add_child(...)` ownership so the retained settings dialog
participates in the same checked overlay lifetime seam already used by the
other app-owned dialogs.

Done Checks:

- `MainToolbarServices::show_settings_dialog()` still preserves current toolbar
  settings-dialog behavior.
- The settings dialog now opens through `src/legacy_ui_overlay_services.*`
  instead of direct raw child insertion in `src/legacy_app_shell_services.cpp`.
- `DEBT-0035` and `DEBT-0063` describe the reduced settings
  dialog lifetime surface and the remaining retained families.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_app_dialog|pp_ui_core_node_lifetime|pp_ui_core_overlay_lifetime|pp_app_core_main_toolbar" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### ADP-008 - Move Cloud Browser Dialog To Checked Overlay Lifetime

Status: Done
Score: +1 legacy adapter retirement
Debt: `DEBT-0038`, `DEBT-0063`
Scope: `src/legacy_cloud_services.cpp`, `src/legacy_ui_overlay_services.*`,
cloud browser dialog path only

Closeout: `5bf0a4f6`

Goal:

Move the cloud browser dialog opening path off raw
`layout[main_id]->add_child(...)` ownership so the retained cloud browser node
participates in the same checked overlay lifetime seam already used by other
app-owned dialogs.

Done Checks:

- Cloud browser open/close behavior stays unchanged for the live adapter path.
- `LegacyCloudServices::show_browser()` now opens through
  `src/legacy_ui_overlay_services.*` instead of direct raw child insertion.
- `DEBT-0038` and `DEBT-0063` describe the reduced dialog-lifetime surface and
  the remaining cloud/retained UI work.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud|pp_ui_core_node_lifetime|pp_ui_core_overlay_lifetime" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-008 | +1 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_ui_core_node_lifetime\|pp_ui_core_overlay_lifetime" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_app_core_document_cloud_tests.vcxproj /p:Configuration=Debug /p:Platform=x64`; `D:\Dev\panopainter\out\build\windows-msvc-default\tests\Debug\pp_app_core_document_cloud_tests.exe`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64` | `5bf0a4f6` |

### ADP-009 - Move Cloud Transfer Helpers To Legacy Cloud Services

Status: Done
Score: +1 legacy adapter retirement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.*`, `src/app.cpp`, `src/app.h`,
cloud transfer helper path only

Goal:

Move the retained upload/download CURL execution off `App` ownership and into
`src/legacy_cloud_services.*` so the cloud bridge owns the live transfer helper
path directly without changing prompt flow, TLS policy, progress callbacks, or
downloaded-project refresh behavior.

Done Checks:

- Live cloud upload/download behavior stays unchanged for the retained adapter
  path.
- `App::upload` and `App::download` no longer own the retained CURL setup and
  progress callback execution.
- `src/legacy_cloud_services.*` owns the retained upload/download transfer
  helpers directly, and `DEBT-0038` describes the reduced remaining cloud
  bridge surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_app_core_document_cloud_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
.\out\build\windows-msvc-default\tests\Debug\pp_app_core_document_cloud_tests.exe
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-009 | +1 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_app_core_document_cloud_tests.vcxproj /p:Configuration=Debug /p:Platform=x64`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `.\out\build\windows-msvc-default\tests\Debug\pp_app_core_document_cloud_tests.exe` | `ccde4d69` |

### ADP-010 - Extract Cloud Upload Form Construction Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Extract the retained cloud upload form-construction code from
`execute_cloud_upload_transfer(...)` into a dedicated local helper so the live
upload path keeps less inline CURL setup while preserving current behavior.

Done Checks:

- Upload form construction no longer lives inline in
  `execute_cloud_upload_transfer(...)`.
- Field naming, upload URL construction, TLS policy, progress callbacks, and
  retained cloud execution behavior stay unchanged.
- `DEBT-0038` and the roadmap note the reduced remaining upload-form setup
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-010 | no score movement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64` | `9efb0802` |

### ADP-011 - Extract Cloud Upload Response Handling Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Extract the retained upload response/error handling from
`execute_cloud_upload_transfer(...)` into a dedicated local helper so the live
upload path keeps less inline CURL post-transfer handling while preserving
current behavior.

Done Checks:

- Upload response/error handling no longer lives inline in
  `execute_cloud_upload_transfer(...)`.
- Current `UPLOAD RESULT` output and retained cloud upload behavior stay
  unchanged.
- `DEBT-0038` and the roadmap note the reduced remaining upload post-transfer
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-011 | no score movement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64` | `58ff3015` |

### ADP-012 - Route Cloud Save-Before-Upload Through Document Session Services

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp`,
`src/legacy_document_session_services.*`

Goal:

Move the retained save-before-upload execution used by cloud publish out of
`src/legacy_cloud_services.cpp` and behind a focused legacy document-session
helper so the cloud bridge no longer reaches `Canvas::I->project_save_thread`
directly while keeping current runtime behavior unchanged.

Done Checks:

- `LegacyCloudServices::prompt_publish(...)` no longer calls
  `Canvas::I->project_save_thread(...)` directly.
- Retained save-before-upload execution now routes through
  `src/legacy_document_session_services.*`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud execution
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-012 | no score movement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64` | `3e0a6b2c` |

### ADP-013 - Route Cloud Downloaded-Project Reconciliation Through Document Open Services

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp`,
`src/legacy_document_open_services.*`

Goal:

Move the retained downloaded-project post-open reconciliation used by cloud
download out of `src/legacy_cloud_services.cpp` and behind a focused legacy
document-open helper so the cloud bridge no longer owns inline camera reset,
layer refresh, title update, and history-clear behavior.

Done Checks:

- `LegacyCloudServices::start_download(...)` no longer owns inline
  downloaded-project post-open reconciliation.
- Retained cloud downloaded-project open, layer refresh, and action-history
  reset now route through `src/legacy_document_open_services.*`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-013 | no score movement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64` | `80533fae` |

### ADP-014 - Extract Cloud Browser Loading Placeholder Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/node_dialog_cloud.*` only

Goal:

Reduce the inline retained cloud-browser loader surface in
`NodeDialogCloud::load_thumbs_thread()` by extracting the initial loading
placeholder setup into a focused helper while preserving current behavior.

Done Checks:

- The initial loading placeholder setup no longer lives inline in
  `NodeDialogCloud::load_thumbs_thread()`.
- The retained cloud-browser loading placeholder now routes through a focused
  helper in `src/node_dialog_cloud.*`.
- `DEBT-0038` and the roadmap note the reduced remaining `NodeDialogCloud`
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-014 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `e9db32f2` |

### ADP-015 - Extract Cloud Browser File-List Request Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/node_dialog_cloud.*` only

Goal:

Reduce the inline retained cloud-browser loader surface in
`NodeDialogCloud::load_thumbs_thread()` by extracting the file-list
request/response handling into a focused helper while preserving current
behavior.

Done Checks:

- The cloud-list request/response handling no longer lives inline in
  `NodeDialogCloud::load_thumbs_thread()`.
- The retained cloud-browser file-list request now routes through a focused
  helper in `src/node_dialog_cloud.*`.
- `DEBT-0038` and the roadmap note the reduced remaining `NodeDialogCloud`
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-015 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `b06211fa` |

### ADP-016 - Extract Cloud Browser Item Creation Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/node_dialog_cloud.*` only

Goal:

Reduce the inline retained cloud-browser loader surface in
`NodeDialogCloud::load_thumbs_thread()` by extracting the slot-creation and
selection-wiring loop into a focused helper while preserving current behavior.

Done Checks:

- The cloud-browser slot-creation and selection-wiring loop no longer lives
  inline in `NodeDialogCloud::load_thumbs_thread()`.
- The retained cloud-browser item creation path now routes through a focused
  helper in `src/node_dialog_cloud.*`.
- `DEBT-0038` and the roadmap note the reduced remaining `NodeDialogCloud`
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-016 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `45f08ee8` |

### ADP-017 - Extract Cloud Browser Thumbnail Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/node_dialog_cloud.cpp` only

Goal:

Reduce the inline retained cloud-browser loader surface in
`NodeDialogCloud::load_thumbs_thread()` by extracting the per-item thumbnail
fetch, decode, and texture-apply path into a focused helper while preserving
current behavior.

Done Checks:

- The per-item thumbnail fetch/decode/apply path no longer lives inline in
  `NodeDialogCloud::load_thumbs_thread()`.
- The retained cloud-browser thumbnail path now routes through a focused
  helper in `src/node_dialog_cloud.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-017 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `9b6fe73a` |

### ADP-018 - Extract Cloud Download Thread Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Reduce the inline retained cloud-download worker surface by extracting the
detached `LegacyCloudServices::start_download()` thread body into a focused
helper while preserving current behavior.

Done Checks:

- The detached cloud-download worker body no longer lives inline in
  `LegacyCloudServices::start_download()`.
- The retained download-thread execution path now routes through a focused
  helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-018 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `54b6aee2` |

### ADP-019 - Extract Cloud Publish Worker Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Reduce the inline retained cloud-publish worker surface by extracting the
upload thread body built inside `LegacyCloudServices::prompt_publish()` into a
focused helper while preserving current behavior.

Done Checks:

- The retained cloud-publish worker body no longer lives inline in
  `LegacyCloudServices::prompt_publish()`.
- The retained upload-thread execution path now routes through a focused
  helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-019 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `34e9789f` |

### ADP-020 - Extract Cloud Publish Prompt Wiring Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Reduce the inline retained cloud-publish prompt surface by extracting the
OK/cancel button wiring built inside `LegacyCloudServices::prompt_publish()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained cloud-publish prompt button wiring no longer lives inline in
  `LegacyCloudServices::prompt_publish()`.
- The retained publish prompt lifetime path now routes through a focused
  helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-020 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `a3c7af71` |

### ADP-021 - Extract Cloud Browser OK Wiring Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Reduce the inline retained cloud-browser dialog surface by extracting the
OK-button wiring built inside `LegacyCloudServices::show_browser()` into a
focused helper while preserving current behavior.

Done Checks:

- The retained cloud-browser OK-button wiring no longer lives inline in
  `LegacyCloudServices::show_browser()`.
- The retained browser action-launch path now routes through a focused helper
  in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-021 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `2ccd8344` |

### ADP-022 - Extract Cloud Bulk Progress Helpers

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Reduce the inline retained cloud bulk-upload progress lifetime surface by
extracting progress dialog creation/teardown from
`LegacyCloudServices::begin_bulk_upload()` and `end_bulk_upload()` into
focused helpers while preserving current behavior.

Done Checks:

- The retained cloud bulk-upload progress creation/teardown no longer lives
  inline in `LegacyCloudServices::begin_bulk_upload()` and
  `LegacyCloudServices::end_bulk_upload()`.
- The retained bulk-progress lifetime path now routes through focused helpers
  in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-022 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `df2c6783` |

### ADP-023 - Extract Cloud Bulk Upload Loop Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Reduce the inline retained cloud bulk-upload execution surface by extracting
the per-file upload loop from `LegacyCloudServices::upload_all_bulk_files()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained cloud bulk-upload per-file loop no longer lives inline in
  `LegacyCloudServices::upload_all_bulk_files()`.
- The retained bulk-upload execution path now routes through a focused helper
  in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-023 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `3d5340a3` |

### ADP-024 - Extract Cloud Save-Required Warning Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Goal:

Reduce the inline retained cloud save-required warning surface by extracting
the prompt path from `LegacyCloudServices::show_save_required_warning()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained cloud save-required warning path no longer lives inline in
  `LegacyCloudServices::show_save_required_warning()`.
- The retained save-required warning path now routes through a focused helper
  in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-024 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `4c6b39c2` |

### ADP-025 - Extract Cloud Publish Prompt Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `777579ff`

Goal:

Reduce the inline retained cloud publish prompt surface by extracting
the prompt path from `LegacyCloudServices::prompt_publish()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained cloud publish prompt path no longer lives inline in
  `LegacyCloudServices::prompt_publish()`.
- The retained cloud publish prompt path now routes through a focused helper
  in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-025 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `777579ff` |

### ADP-026 - Extract Cloud Browser Dialog Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `2e520709`

Goal:

Reduce the inline retained cloud browser dialog surface by extracting
the dialog creation path from `LegacyCloudServices::show_browser()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained cloud browser dialog creation path no longer lives inline in
  `LegacyCloudServices::show_browser()`.
- The retained cloud browser dialog creation path now routes through a
  focused helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-026 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `2e520709` |

### ADP-027 - Extract Cloud Download Thread Launcher

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `88018f6c`

Goal:

Reduce the inline retained cloud download thread-launch surface by extracting
the launch path from `LegacyCloudServices::start_download()` into a focused
helper while preserving current behavior.

Done Checks:

- The retained cloud download thread launch path no longer lives inline in
  `LegacyCloudServices::start_download()`.
- The retained cloud download thread launch path now routes through a
  focused helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-027 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `88018f6c` |

### ADP-028 - Extract Cloud Download Progress Dialog Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `e993fa48`

Goal:

Reduce the inline retained cloud download progress-dialog surface by extracting
the dialog creation path from `execute_cloud_download_thread()` into a
focused helper while preserving current behavior.

Done Checks:

- The retained cloud download progress-dialog creation path no longer lives
  inline in `execute_cloud_download_thread()`.
- The retained cloud download progress-dialog creation path now routes
  through a focused helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-028 | no score movement | `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_cloud" --output-on-failure` | `e993fa48` |

### ADP-029 - Extract Cloud Download Transfer Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `65d762c6`

Goal:

Reduce the inline retained cloud download URL/progress wiring surface by
extracting the transfer call path from `execute_cloud_download_thread()` into a
focused helper while preserving current behavior.

Done Checks:

- The retained cloud download URL/progress wiring no longer lives inline in
  `execute_cloud_download_thread()`.
- The retained cloud download URL/progress wiring now routes through a focused
  helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-029 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `65d762c6` |

### ADP-030 - Extract Cloud Download Post-Transfer Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `2b8c11bf`

Goal:

Reduce the inline retained cloud download post-transfer open/close surface by
extracting the final worker-step path from `execute_cloud_download_thread()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained cloud download post-transfer open/close path no longer lives
  inline in `execute_cloud_download_thread()`.
- The retained cloud download post-transfer open/close path now routes through
  a focused helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-030 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `2b8c11bf` |

### ADP-031 - Extract Cloud Download Flow Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `7850d90e`

Goal:

Reduce the inline retained cloud download worker body by extracting the
remaining flow path from `execute_cloud_download_thread()` into a focused
helper while preserving current behavior.

Done Checks:

- The retained cloud download worker body no longer lives inline in
  `execute_cloud_download_thread()`.
- The retained cloud download worker body now routes through a focused helper
  in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-031 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `7850d90e` |

### ADP-032 - Extract Cloud Publish Transfer Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `07f3ca81`

Goal:

Reduce the inline retained cloud publish transfer-and-success surface by
extracting the worker body from `execute_cloud_publish_worker()` into a
focused helper while preserving current behavior.

Done Checks:

- The retained cloud publish transfer-and-success body no longer lives inline
  in `execute_cloud_publish_worker()`.
- The retained cloud publish transfer-and-success body now routes through a
  focused helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-032 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `07f3ca81` |

### ADP-033 - Extract Cloud Publish Thread Launcher

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `9e731b4a`

Goal:

Reduce the inline retained cloud publish detached-thread launch surface by
extracting the launcher from `show_cloud_publish_prompt()` into a focused
helper while preserving current behavior.

Done Checks:

- The retained cloud publish detached-thread launch no longer lives inline in
  `show_cloud_publish_prompt()`.
- The retained cloud publish detached-thread launch now routes through a
  focused helper in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-033 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `9e731b4a` |

### ADP-034 - Extract Cloud Publish Prompt Setup Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_cloud_services.cpp` only

Closeout: `b5317832`

Goal:

Reduce the inline retained cloud publish prompt setup surface by extracting
the prompt plan/dialog/button wiring from `show_cloud_publish_prompt()` into a
focused helper while preserving current behavior.

Done Checks:

- The retained cloud publish prompt setup no longer lives inline in
  `show_cloud_publish_prompt()`.
- The retained cloud publish prompt setup now routes through a focused helper
  in `src/legacy_cloud_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-034 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `b5317832` |
| 2026-06-15 | ADP-007 | +1 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_app_dialog\|pp_ui_core_node_lifetime\|pp_ui_core_overlay_lifetime" --output-on-failure`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_main_toolbar" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_app_core_main_toolbar_tests.vcxproj /p:Configuration=Debug /p:Platform=x64`; `MSBuild.exe out\build\windows-msvc-default\panopainter_app.vcxproj /p:Configuration=Debug /p:Platform=x64` | `8db859cb` |

### ADP-035 - Extract Downloaded Project Reconcile Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_document_open_services.*` only

Closeout: `98c48c33`

Goal:

Reduce the inline retained downloaded-project open bridge surface by extracting
the layer refresh and history-clear path from `execute_legacy_downloaded_project_open()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained downloaded-project layer refresh and action-history reset no
  longer live inline in `execute_legacy_downloaded_project_open()`.
- The retained downloaded-project open reconciliation now routes through a
  focused helper in `src/legacy_document_open_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-035 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `69603ed6` |

### ADP-036 - Extract Downloaded Project Open Prep Helper

Status: Done
Score: no score movement
Debt: `DEBT-0038`
Scope: `src/legacy_document_open_services.*` only

Closeout: `69603ed6`

Goal:

Reduce the inline retained downloaded-project open setup surface by
extracting the camera reset and layer-clear path from
`execute_legacy_downloaded_project_open()` into a focused helper while
preserving current behavior.

Done Checks:

- The retained downloaded-project camera reset and layer-clear setup no
  longer lives inline in `execute_legacy_downloaded_project_open()`.
- The retained downloaded-project open setup now routes through a focused
  helper in `src/legacy_document_open_services.cpp`.
- `DEBT-0038` and the roadmap note the reduced remaining cloud bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-036 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_cloud"` | `359e6b94` |

### ADP-037 - Extract Document-Open Brush Import Prompt Helper

Status: Done
Score: no score movement
Debt: `DEBT-0039`
Scope: `src/legacy_document_open_services.cpp` only

Closeout: `359e6b94`

Goal:

Reduce the inline retained document-open brush import prompt surface by
extracting the shared ABR/PPBR confirmation wiring from
`prompt_import_abr()` and `prompt_import_ppbr()` into a focused helper while
preserving current behavior.

Done Checks:

- The retained ABR/PPBR import prompt wiring no longer lives inline in the
  individual document-open prompt methods.
- The retained ABR/PPBR import prompt path now routes through a focused helper
  in `src/legacy_document_open_services.cpp`.
- `DEBT-0039` and the roadmap note the reduced remaining document-open bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-037 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"` | `16111e09` |

### ADP-038 - Extract Document-Session Overwrite Prompt Helper

Status: Done
Score: no score movement
Debt: `DEBT-0040`, `DEBT-0041`, `DEBT-0042`
Scope: `src/legacy_document_session_services.cpp` only

Goal:

Reduce the inline retained document-session overwrite-prompt surface by
extracting the shared OK wiring from the new-document and save-file overwrite
prompts into a focused helper while preserving current behavior.

Done Checks:

- The retained document-session overwrite-prompt OK wiring no longer lives
  inline in the new-document and save-file prompt methods.
- The retained document-session overwrite-prompt path now routes through a
  focused helper in `src/legacy_document_session_services.cpp`.
- `DEBT-0040`, `DEBT-0041`, `DEBT-0042`, and the roadmap note the reduced
  remaining document-session bridge surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-038 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"` | `93846763` |

### ADP-039 - Extract Document-Session Workflow Prompt Helper

Status: Done
Score: no score movement
Debt: `DEBT-0040`
Scope: `src/legacy_document_session_services.cpp` only

Closeout: `93846763`

Goal:

Reduce the inline retained save-before-continue workflow prompt surface by
extracting the button wiring from `prompt_save_before_continue()` into a
focused helper while preserving current behavior.

Done Checks:

- The retained save-before-continue workflow prompt wiring no longer lives
  inline in `prompt_save_before_continue()`.
- The retained workflow prompt path now routes through a focused helper in
  `src/legacy_document_session_services.cpp`.
- `DEBT-0040` and the roadmap note the reduced remaining document-session
  bridge surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-039 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"` | `c37451e9` |

### ADP-040 - Extract Document-Session Close Prompt Helper

Status: Done
Score: no score movement
Debt: `DEBT-0040`
Scope: `src/legacy_document_session_services.cpp` only

Closeout: `c37451e9`

Goal:

Reduce the inline retained close-unsaved prompt surface by extracting the
button wiring from `show_unsaved_close_prompt()` into a focused helper while
preserving current behavior.

Done Checks:

- The retained close-unsaved prompt wiring no longer lives inline in
  `show_unsaved_close_prompt()`.
- The retained close prompt path now routes through a focused helper in
  `src/legacy_document_session_services.cpp`.
- `DEBT-0040` and the roadmap note the reduced remaining document-session
  bridge surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-040 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"` | `be8dee8d` |

### ADP-041 - Extract Document-Session Save-Version Helper

Status: Done
Score: no score movement
Debt: `DEBT-0042`
Scope: `src/legacy_document_session_services.cpp` only

Closeout: `be8dee8d`

Goal:

Reduce the inline retained Save Version execution surface by extracting the
version-save body from `LegacyDocumentVersionSaveServices::save_document_version()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained Save Version execution no longer lives inline in
  `LegacyDocumentVersionSaveServices::save_document_version()`.
- The retained version-save path now routes through a focused helper in
  `src/legacy_document_session_services.cpp`.
- `DEBT-0042` and the roadmap note the reduced remaining document-session
  bridge surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-041 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"` | `aaf55dd7` |

### ADP-042 - Extract Document-Session Save Dialog Helper

Status: Done
Score: no score movement
Debt: `DEBT-0042`
Scope: `src/app_dialogs.cpp` only

Closeout: `aaf55dd7`

Goal:

Reduce the inline retained Save dialog button wiring by extracting the
button handling from `App::dialog_save()` into a focused helper while
preserving current behavior.

Done Checks:

- The retained Save dialog wiring no longer lives inline in `App::dialog_save()`.
- The retained Save dialog path now routes through a focused helper in
  `src/app_dialogs.cpp`.
- `DEBT-0042` and the roadmap note the reduced remaining document-session
  bridge surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-042 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"` | `98c48c33` |

### ADP-043 - Extract Document-Session Save-Version Dialog Helper

Status: Done
Score: no score movement
Debt: `DEBT-0042`
Scope: `src/app_dialogs.cpp` only

Closeout: `ffda49ad`

Goal:

Reduce the inline retained Save Version dialog surface by extracting the
version-save button handling from `App::dialog_save_ver()` into a focused
helper while preserving current behavior.

Done Checks:

- The retained Save Version dialog wiring no longer lives inline in
  `App::dialog_save_ver()`.
- The retained Save Version dialog path now routes through a focused helper
  in `src/app_dialogs.cpp`.
- `DEBT-0042` and the roadmap note the reduced remaining document-session
  bridge surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-043 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli,pp_app_core_document_session_tests -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"` | `ffda49ad` |

### ADP-044 - Extract Document-Browse Dialog Helper

Status: Done
Score: no score movement
Debt: `DEBT-0039`
Scope: `src/app_dialogs.cpp` only

Closeout: `294d9ce7`

Goal:

Reduce the inline retained Browse dialog surface by extracting the
button-handling logic from `App::dialog_browse()` into a focused helper while
preserving current behavior.

Done Checks:

- The retained Browse dialog wiring no longer lives inline in
  `App::dialog_browse()`.
- The retained Browse dialog path now routes through a focused helper in
  `src/app_dialogs.cpp`.
- `DEBT-0039` and the roadmap note the reduced remaining document-open bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-044 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_session|pano_cli_plan_document_session_prompt"` | `294d9ce7` |

### ADP-045 - Extract Document-Open Unsaved Prompt Helper

Status: Done
Score: no score movement
Debt: `DEBT-0039`
Scope: `src/legacy_document_open_services.cpp` only

Closeout: `39728e46`

Goal:

Reduce the inline retained unsaved-project discard prompt surface by
extracting the button wiring from `LegacyDocumentOpenServices::prompt_discard_unsaved_project()`
into a focused helper while preserving current behavior.

Done Checks:

- The retained unsaved-project discard prompt wiring no longer lives inline in
  `LegacyDocumentOpenServices::prompt_discard_unsaved_project()`.
- The retained unsaved-project discard prompt path now routes through a focused
  helper in `src/legacy_document_open_services.cpp`.
- `DEBT-0039` and the roadmap note the reduced remaining document-open bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-045 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"` | `39728e46` |

### ADP-046 - Extract Document-Open Project Result Helper

Status: Done
Score: no score movement
Debt: `DEBT-0039`
Scope: `src/legacy_document_open_services.cpp` only

Closeout: `7b8da2f0`

Goal:

Reduce the inline retained project-open success/failure handling surface by
extracting the callback body from `open_legacy_project()` into a focused helper
while preserving current behavior.

Done Checks:

- The retained project-open success/failure handling no longer lives inline in
  `open_legacy_project()`.
- The retained project-open result path now routes through a focused helper in
  `src/legacy_document_open_services.cpp`.
- `DEBT-0039` and the roadmap note the reduced remaining document-open bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-046 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"` | `7b8da2f0` |

### ADP-047 - Extract Document-Open Project Result Branch Helpers

Status: Done
Score: no score movement
Debt: `DEBT-0039`
Scope: `src/legacy_document_open_services.cpp` only

Closeout: `2f2b9621`

Goal:

Reduce the inline retained project-open result branch surface by extracting the
success-path layer refresh and failure-path error dialog from
`handle_open_legacy_project_result()` into focused helpers while preserving
current behavior.

Done Checks:

- The retained project-open success-path layer/title work no longer lives
  inline in `handle_open_legacy_project_result()`.
- The retained project-open failure-path error dialog no longer lives inline in
  `handle_open_legacy_project_result()`.
- The retained project-open result path now routes through focused helpers in
  `src/legacy_document_open_services.cpp`.
- `DEBT-0039` and the roadmap note the reduced remaining document-open bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-047 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"` | `2f2b9621` |

### ADP-048 - Extract Document-Open History Helper

Status: Done
Score: no score movement
Debt: `DEBT-0039`
Scope: `src/legacy_document_open_services.cpp` only

Closeout: `c2083565`

Goal:

Reduce the inline retained project-open history-planning surface by
extracting the `plan_document_open_history(route)` call and failure logging
from `open_legacy_project()` into a focused helper while preserving current
behavior.

Done Checks:

- The retained project-open history-planning call no longer lives inline in
  `open_legacy_project()`.
- The retained project-open history path now routes through a focused helper in
  `src/legacy_document_open_services.cpp`.
- `DEBT-0039` and the roadmap note the reduced remaining document-open bridge
  surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-048 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"` | `c2083565` |

### ADP-049 - Reuse Shared History Service In Document Bridges

Status: Done
Score: no score movement
Debt: `DEBT-0039`, `DEBT-0040`
Scope: `src/legacy_document_open_services.cpp`, `src/legacy_document_session_services.cpp`

Closeout: `530e572e`

Goal:

Reduce the duplicated retained history adapter surface by reusing the shared
`src/legacy_history_services.*` helper from both document-open and
document-session bridges while preserving current behavior.

Done Checks:

- The retained history adapter no longer lives inline in the document-open and
  document-session bridges.
- The retained document-open and document-session history paths now reuse the
  shared `src/legacy_history_services.*` helper.
- `DEBT-0039`, `DEBT-0040`, and the roadmap note the reduced retained history
  adapter surface.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | ADP-049 | no score movement | `powershell -ExecutionPolicy Bypass -File scripts\\automation\\quiet-validate.ps1 -BuildTargets pano_cli -TestRegex "pp_app_core_document_route|pp_app_core_document_session"` | `530e572e` |

### RND-001 - Make Pure Equirectangular Export The Primary Success Path

Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0010`, `DEBT-0036`, `DEBT-0043`
Scope: `src/legacy_document_export_services.*`,
`src/app_core/document_export.*`, `src/paint_renderer/compositor.*`,
`tests/app_core/document_export_tests.cpp`,
`tests/paint_renderer/compositor_tests.cpp`

Goal:

For payload-complete snapshots, live PNG/JPEG equirectangular export should
complete through the pure document/paint-renderer writer. Retained
`Canvas::export_equirectangular*` should run only for unsupported Web,
incomplete-readback, or writer-failure fallbacks.

Done Checks:

- Export route reports whether the pure writer was used or why fallback was
  required.
- Tests cover pure-writer success and each fallback reason.
- Live bridge does not call retained equirectangular export after pure-writer
  success.
- The debt log narrows retained equirectangular export execution.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export|pp_paint_renderer_compositor|pano_cli_plan_export_snapshot_route|pano_cli_simulate_document_export" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### RND-002 - Make Pure Layer And Animation Collection Export Primary

Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0010`, `DEBT-0036`, `DEBT-0043`
Scope: `src/legacy_document_export_services.*`,
`src/app_core/document_export.*`, `src/paint_renderer/compositor.*`,
collection export tests only

Goal:

For payload-complete snapshots, live layer and animation-frame collection export
should complete through the pure collection writer. Retained
`Canvas::export_layers*` and `Canvas::export_anim_frames*` should run only for
unsupported Web, incomplete-readback, or writer-failure fallbacks.

Done Checks:

- Collection export route reports pure-writer success versus fallback reason.
- Tests cover layer collection, animation-frame collection, and fallback.
- The debt log narrows retained collection export execution.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export|pp_paint_renderer_compositor|pano_cli_plan_export_snapshot_route" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### RND-003 - Replace Depth Export Readiness With Pure Depth Export Execution

Status: Done
Score: +3 renderer boundary and OpenGL parity
Debt: `DEBT-0010`, `DEBT-0036`, `DEBT-0043`
Scope: `src/paint_renderer/compositor.*`,
`src/app_core/document_export.*`, `src/legacy_document_export_services.*`,
depth tests only

Goal:

Turn the current pure depth export render plan into actual payload generation
for payload-complete snapshots, then write image/depth payloads through the
existing app-core two-payload writer before falling back to retained
`Canvas::export_depth*`.

Done Checks:

- Pure depth export produces deterministic image and depth payloads for a
  payload-complete snapshot.
- Retained depth export runs only for unsupported targets, incomplete readback,
  or writer failure.
- Tests cover malformed depth target inputs and byte-size validation.
- The debt log narrows depth export readback/execution.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export|pp_paint_renderer_compositor|pano_cli_plan_export_snapshot_route" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### RND-004 - Add First Desktop GPU Golden Gate

Status: Done
Score: +2 hardening and future backend readiness
Debt: `DEBT-0036`
Scope: `tests/`, `CMakeLists.txt`, renderer test helpers only

Goal:

Create the first non-default `desktop-gpu` golden/readback test that validates
one OpenGL output against a deterministic fixture. Keep it opt-in so headless
agents are not blocked.

Done Checks:

- `ctest --preset desktop-gpu --build-config Debug` has at least one real test.
- The test is skipped with a clear message when no GPU/context is available.
- The roadmap and debt log describe what the golden covers and what remains.

Validation:

```powershell
ctest --preset desktop-gpu --build-config Debug --output-on-failure
ctest --preset desktop-fast --build-config Debug -R "pp_renderer_gl|pp_paint_renderer" --output-on-failure
```

### RND-005 - Add Desktop GPU Preview Golden Gate

Status: Done
Score: +2 hardening and future backend readiness
Debt: `DEBT-0036`
Scope: `tests/`, `CMakeLists.txt`, renderer test helpers only

Goal:

Create the next non-default `desktop-gpu` golden/readback test for the preview
parity lane so the renderer parity coverage is not limited to the existing
clear-readback fixture.

Done Checks:

- `ctest --preset desktop-gpu --build-config Debug` has more than one real GPU
  readback gate.
- The test is skipped with a clear message when no GPU/context is available.
- The roadmap and debt log describe the GPU readback coverage and what remains.

Validation:

```powershell
ctest --preset desktop-gpu --build-config Debug --output-on-failure
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | RND-005 | +2 hardening and future backend readiness | `ctest --preset desktop-gpu --build-config Debug --output-on-failure`; `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `4a44e6cd` |
### RND-006 - Add Desktop GPU Compositor Golden Gate

Status: Done
Score: +2 hardening and future backend readiness
Debt: `DEBT-0036`
Scope: `tests/`, `CMakeLists.txt`, renderer test helpers only

Goal:

Create the next `desktop-gpu` readback gate for the compositor path so the
OpenGL parity lane covers a second live render target, not just the existing
clear/preview fixtures.

Done Checks:

- `ctest --preset desktop-gpu --build-config Debug` has a compositor readback
  gate in addition to the existing GPU fixtures.
- The test is skipped with a clear message when no GPU/context is available.
- The roadmap and debt log describe the compositor output the gate covers and
  what remains.

Validation:

```powershell
ctest --preset desktop-gpu --build-config Debug --output-on-failure
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | RND-006 | +2 hardening and future backend readiness | `ctest --preset desktop-gpu --build-config Debug --output-on-failure`; `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `96b7b6f8` |

### PLT-001 - Split Apple Picker/Browse Service From Legacy Platform Adapter

Status: Done
Score: +2 platform and package parity
Debt: `DEBT-0017`, `DEBT-0051`, `DEBT-0055`
Scope: `src/platform_legacy/legacy_platform_services.*`, new
`src/platform_apple/*` files if needed, `CMakeLists.txt`, platform API tests

Goal:

Move macOS/iOS document browse roots, file picking, directory picking, and
display-path formatting out of the catch-all legacy platform adapter and into a
named Apple platform service boundary.

Done Checks:

- `src/platform_legacy/legacy_platform_services.*` no longer owns Apple
  browse/picker policy.
- Apple compile validation still passes through the remote build script.
- The debt log narrows Apple platform shell extraction.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos,ios-simulator,ios-device
```

### PLT-002 - Split Web Export/Storage Policy From Legacy Platform Adapter

Status: Done
Score: +2 platform and package parity
Debt: `DEBT-0050`, `DEBT-0053`, `DEBT-0057`
Scope: `src/platform_legacy/legacy_platform_services.*`, new
`src/platform_web/*` files if needed, `src/platform_api/*`, platform tests

Goal:

Move WebGL exported-image publishing, persistent-storage flushing,
prepared-file handoff, and default canvas resolution out of the catch-all
legacy platform adapter into a named Web platform service boundary.

Done Checks:

- Web policy is injectable through `pp_platform_api`.
- The legacy adapter no longer owns Web default canvas resolution or storage
  flush policy.
- Package-smoke readiness still reports Web blockers explicitly.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly
```

### DEP-001 - Remove Generated fmt Overlay

Status: Done
Score: +1 build and CMake ownership
Debt: `DEBT-0062`
Scope: `CMakeLists.txt`, `cmake/`, `vcpkg.json`, `libs/fmt` or package wiring

Goal:

Use a supported fmt package or update the vendored fmt release so VS 2026 no
longer needs a generated `format.h` overlay.

Done Checks:

- No build-tree fmt header overlay is generated.
- `DEBT-0062` is closed.
- Windows app and at least one focused component test build pass.

Validation:

```powershell
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pp_platform_api_tests
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
```

### DEP-002 - Remove Generated nanort Overlay

Status: Done
Score: +1 build and CMake ownership
Debt: `DEBT-0060`
Scope: retained Android package CMake, `libs/nanort`, grid/lightmap dependency
wiring
Goal:

Update, replace, or isolate `nanort` so Android package builds do not generate
a patched vendor overlay.

Done Checks:

- Android retained package CMake no longer generates a patched `nanort.h`.
- `DEBT-0060` is closed.
- Standard Android retained package validation passes.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

## Blocked Or Later Queue

These are real goals, but they should not be picked until prerequisite tasks
above have reduced risk.

### LATER-001 - Replace OpenVR With OpenXR

Status: Blocked
Score: +3 platform and package parity
Debt: `DEBT-0061`
Blocked By: OpenXR package decision and runtime availability

Done Checks:

- OpenXR SDK/package target exists.
- Windows platform service can start/stop desktop XR without OpenVR.
- `libs/openvr` and `openvr_api.dll` deployment are removed.
- `DEBT-0061` is closed.

### LATER-002 - Remove Catch2 Harness Debt

Status: Blocked
Score: +2 test and automation coverage
Debt: `DEBT-0005`
Blocked By: vcpkg/toolchain reliability across Windows and headless presets

Done Checks:

- Existing local test harness is replaced or permanently justified.
- Catch2 tests run through `desktop-fast` and vcpkg headless presets.
- `DEBT-0005` is closed.

### LATER-003 - Live Stroke Rasterization Through Renderer Services

Status: Done
Score: +5 renderer boundary and OpenGL parity
Debt: `DEBT-0036`

Done Checks:

- Live stroke rasterization, dual-brush compositing, and pattern feedback choose
  paths through renderer services.
- OpenGL output parity is covered by golden/readback tests.
- Retained stroke OpenGL execution is deleted or isolated as an OpenGL backend
  implementation.

### STR-020 - Extract Stroke Draw Main Pass Texture Dispatch

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline `Canvas::stroke_draw()` main-pass texture dispatch bundle into
a retained helper so the callsite keeps only branch selection plus concrete
texture/sampler wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer builds the main-pass texture dispatch
  bundle inline.
- Regression coverage proves the extracted helper preserves main-pass texture
  callback order.
- `docs/modernization/debt.md` records the reduced stroke-draw texture
  dispatch surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-020 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `87e6c111` |

### STR-021 - Extract Stroke Draw Live Pass Sampler Dispatch

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline `Canvas::stroke_draw()` live-pass sampler dispatch bundle into
a retained helper so the callsite keeps only branch selection plus concrete
sampler wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer builds the live-pass sampler dispatch
  bundle inline.
- Regression coverage proves the extracted helper preserves sampler callback
  order.
- `docs/modernization/debt.md` records the reduced live-pass sampler surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-021 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `d12b5797` |

### STR-022 - Extract Stroke Draw Dual Brush Tip Dispatch

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline dual-pass brush-tip dispatch bundle in `Canvas::stroke_draw()`
into a retained helper so the dual-brush branch keeps only concrete texture
object wiring and branch selection.

Done Checks:

- `Canvas::stroke_draw()` no longer builds the dual-pass brush-tip dispatch
  bundle inline.
- Regression coverage proves the extracted helper preserves dual-brush tip
  callback order.
- `docs/modernization/debt.md` records the reduced dual-pass texture-dispatch
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-022 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `065ddf8e` |
| 2026-06-13 | STR-023 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `07b188de` |
| 2026-06-13 | STR-024 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `b1d6e5e2` |
| 2026-06-13 | STR-025 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `77ac50b9` |
| 2026-06-13 | STR-028 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `3478219a` |
| 2026-06-13 | STR-030 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `e507fe27` |

### STR-023 - Extract Stroke Draw Dual Pass Frame Orchestration

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline `execute_legacy_canvas_stroke_dual_pass_frame_callbacks(...)`
lambda body in `Canvas::stroke_draw()` into a retained helper so the dual-pass
branch owns only concrete shader, sampler, and framebuffer wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline dual-pass frame
  callback body.
- Regression coverage proves the extracted helper preserves dual-pass frame
  callback order and face execution.
- `docs/modernization/debt.md` records the reduced dual-pass frame surface.

Closeout: `07b188de`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-024 - Extract Stroke Draw Dual Pass Frame Callback Body

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline body passed to `execute_legacy_canvas_stroke_dual_pass_frame_callbacks(...)`
into a retained helper so the dual-pass branch owns only concrete callback
selection and framebuffer wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline dual-pass frame
  callback body.
- Regression coverage proves the extracted helper preserves callback order and
  face-state updates.
- `docs/modernization/debt.md` records the reduced dual-pass frame-callback
  surface.

Closeout: `b1d6e5e2`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-025 - Extract Stroke Draw Dual Pass Frame Request Wrapper

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining dual-pass frame request assembly in `Canvas::stroke_draw()`
into a retained helper so the branch keeps only concrete callbacks and frame
execution wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer spells the dual-pass frame request inline.
- Regression coverage proves the extracted helper preserves dual-pass request
  wiring.
- `docs/modernization/debt.md` records the reduced dual-pass request surface.

Closeout: `77ac50b9`

Closeout: `77ac50b9`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-026 - Extract Stroke Draw Dual Pass Shader Setup Wrapper

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline dual-pass shader setup lambda in `Canvas::stroke_draw()` into
a retained helper so the dual-pass branch owns only concrete shader selection
and framebuffer wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline dual-pass shader
  setup lambda.
- Regression coverage proves the extracted helper preserves shader setup
  behavior.
- `docs/modernization/debt.md` records the reduced dual-pass shader-setup
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-026 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `2118693c` |

### STR-027 - Extract Stroke Draw Pad Destination Dispatch

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline pad-stroke destination texture dispatch lambda in
`Canvas::stroke_draw()` into a retained helper so the pad branch keeps only
concrete texture wiring and copy timing.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline pad-stroke destination
  texture dispatch lambda.
- Regression coverage proves the extracted helper preserves pad destination
  bind/unbind order.
- `docs/modernization/debt.md` records the reduced pad-stroke dispatch
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-027 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `4bca8398` |

### STR-028 - Extract Stroke Draw Pad Face Orchestration

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline pad-stroke face execution block in `Canvas::stroke_draw()` into
a retained helper so the pad branch keeps only concrete brush-shape, texture,
and framebuffer wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline pad-face block around
  `execute_legacy_canvas_stroke_pad_face_callbacks(...)`.
- Regression coverage proves the extracted helper preserves pad-face order and
  copy behavior.
- `docs/modernization/debt.md` records the reduced pad callback surface.

Closeout: `3478219a`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-029 - Extract Stroke Draw Pad Copy Callback Body

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline pad-stroke copy callback body in `Canvas::stroke_draw()` into
a retained helper so the pad branch owns only concrete copy-region wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline pad-stroke copy
  callback body.
- Regression coverage proves the extracted helper preserves pad copy-region
  behavior.
- `docs/modernization/debt.md` records the reduced pad copy-callback surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-030 - Extract Stroke Draw Pad Face Callback Body

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline pad-face callback body in `Canvas::stroke_draw()` into a
retained helper so the pad branch owns only concrete face selection and
texture/copy wiring.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline pad-face callback
  body.
- Regression coverage proves the extracted helper preserves pad-face callback
  order and copy behavior.
- `docs/modernization/debt.md` records the reduced pad-face callback surface.

Closeout: `e507fe27`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-029 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `d03f0c63` |
| 2026-06-13 | STR-030 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `e6f3be1c` |

Progress Notes:

- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` now routes final
  composite execution and preview copy-back through a retained local wrapper,
  leaving the call site with only sequence wiring.
- 2026-06-13: `Canvas::stroke_draw()` now routes dual-brush tip dispatch
  through retained helper overloads, so the face-index callback wiring is no
  longer built inline in the dual-pass branch.
- 2026-06-13: `Canvas::stroke_draw()` now routes pad destination texture
  dispatch through retained helper overloads, so the face-index callback
  wiring is no longer built inline in the pad branch.
- 2026-06-13: `Canvas::stroke_draw()` now routes its main live-pass bind,
  execute, and unbind sequence through
  `execute_legacy_canvas_stroke_main_pass(...)`; the retained adapter still
  owns the concrete sampler and texture callbacks.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes retained mix-pass request
  wiring through `make_legacy_canvas_stroke_mix_pass_request(...)`; the legacy
  path still owns the concrete framebuffer setup and per-plane GL callbacks.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes its retained mix-pass
  execution shell through `execute_legacy_canvas_stroke_mix_pass_with_setup(...)`
  with a concrete request builder; the live path still owns the OpenGL setup
  and per-plane texture wiring.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the setup/end shell through
  `make_legacy_canvas_stroke_mix_pass_setup(...)`; the live path still owns the
  concrete framebuffer bind and GL capability toggles.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the full retained mix-pass
  shell through `execute_legacy_canvas_stroke_mix_pass_with_setup(...)`; the
  live path still owns the concrete framebuffer bind and GL capability toggles.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the retained mix-pass
  shell through `execute_legacy_canvas_stroke_mix_pass_shell(...)`; the live
  path still owns the concrete framebuffer bind and GL capability toggles.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the combined setup and
  request bundle through `make_legacy_canvas_stroke_mix_pass_shell(...)`; the
  live path still owns the concrete framebuffer bind and GL capability
  toggles.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the combined setup,
  request bundle, and shell execution through
  `make_legacy_canvas_stroke_mix_pass_shell(...)` plus
  `execute_legacy_canvas_stroke_mix_pass_shell(...)`; the live path still
  owns the concrete framebuffer bind and GL capability toggles.
- 2026-06-13: `Canvas::stroke_draw()` live-pass sampler wiring now reuses a
  retained helper builder, and the stroke execution tests cover it. `STR-004`
  is now done after the final pad-destination helper extraction and tracker
  closeout.
- 2026-06-13: `STR-004` closed the last inline stroke dispatch glue. `LATER-003`
  remains at the binding-only tail.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the retained shell
  execution through a local wrapper around
  `execute_legacy_canvas_stroke_mix_pass_shell(...)`; the live path still owns
  the concrete mixer framebuffer setup and GL capability toggles.
- 2026-06-13: `Canvas::stroke_draw_mix()` now calls the retained mix-pass
  executor directly instead of a local wrapper shell helper; the live path
  still owns the concrete mixer framebuffer setup and GL capability toggles.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the remaining mixer
  framebuffer/capability shell through a local helper, leaving the call site
  with only mix-pass planning and helper invocation.
- 2026-06-13: `retained_stroke_mix_pass_shell_builder_preserves_combined_wiring`
  now also exercises the direct shell executor path, locking the combined mix
  shell boundary with call-order regression coverage.
- 2026-06-13: `retained_stroke_mix_pass_shell_executor_preserves_combined_wiring`
  now covers the direct shell executor path separately, keeping the shell
  boundary regression isolated from the builder coverage.
- 2026-06-13: `Canvas::stroke_draw_mix()` no longer computes an unused mix-plane
  plan in the live shell path; the remaining code is the retained shell setup
  and executor call.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes the remaining framebuffer
  setup callbacks through a local helper, leaving the method with only shell
  assembly and executor dispatch.
- 2026-06-13: `Canvas::stroke_draw_mix()` now keeps just the retained shell
  assembly and executor dispatch at the callsite; the framebuffer setup
  callbacks are isolated in the helper.
- 2026-06-13: `Canvas::stroke_commit()` now routes the large retained callback
  bundle through a local helper, leaving the callsite with sequence planning
  and helper invocation.
- 2026-06-13: `Canvas::stroke_commit()` now keeps the commit callback bundle
  in a local helper, leaving the callsite with sequence planning and retained
  callback invocation only.
- 2026-06-13: `Canvas::stroke_commit()` now routes the commit-input texture
  role switch through a local helper, so the callsite no longer owns that
  inline texture binding bundle.
- 2026-06-13: `Canvas::stroke_commit()` now routes the commit-input texture
  role switch through a retained service helper, so the callsite only supplies
  concrete face bindings.
- 2026-06-13: `Canvas::stroke_draw_samples()` now reuses a retained destination
  texture dispatch helper for the live sample path; `Canvas` still owns the
  concrete face textures and callback execution.
- 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers the
  new retained main-pass texture dispatch helper builder and its pattern/mixer
  wiring. Next slice should target another narrow `stroke_draw()` seam or stop
  once the remaining inline code is just trivial binding glue.
- 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now covers the new
  retained sampler dispatch helper builder and its pattern bind/unbind wiring.
  Next slice should target another narrow `stroke_draw()` seam or another
  helper-builder regression without reopening landed texture or dual-pass
  helpers.
- 2026-06-13: `Canvas::stroke_draw()` live-pass sampler dispatch now reuses a
  retained helper builder for brush-tip, destination, pattern, and mixer
  sampler callbacks; the live adapter still owns the concrete sampler objects.
  Next slice should target another narrow `stroke_draw()` seam without
  reopening landed texture or dual-pass helpers.
- 2026-06-13: `Canvas::stroke_draw()` main-pass texture dispatch now reuses
  retained helper builders for texture-unit, brush-tip, pattern, and mixer
  callback wiring; the live adapter still owns the concrete textures and
  sampler state. Next slice should target the remaining inline `stroke_draw()`
  dispatch construction or another narrow seam without reopening landed pad or
  dual-pass helpers.
- 2026-06-13: `Canvas::stroke_draw()` dual-brush replay now reuses the new
  retained brush-tip texture dispatch helper for binding and unbinding; the
  live adapter still owns the concrete brush texture object and live-pass
  execution. Next slice should target another narrow `stroke_draw()` seam
  without reopening landed pad or sample helpers.
- 2026-06-13: `Canvas::stroke_draw()` dual-brush replay now routes shader
  setup, brush-tip texture binding, live-pass execution, and brush-tip
  unbinding through `execute_legacy_canvas_stroke_dual_pass(...)`; the live
  adapter still owns the concrete texture callbacks and face-frame replay.
  Next slice should target another narrow `stroke_draw()` seam without
  reopening landed pad or sample helpers.
- 2026-06-13: `Canvas::stroke_draw()` now routes the pad-stroke destination
  texture dispatch through a local helper lambda, so the repeated bind/unbind
  callback construction is centralized while the pad executor still owns the
  face loop and copy timing. Next slice should target another narrow canvas
  stroke execution seam without reopening landed pad or sample helpers.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes mix-pass plane planning
  through `plan_legacy_canvas_stroke_mix_pass_planes(...)` and wraps retained
  framebuffer setup/teardown with
  `execute_legacy_canvas_stroke_mix_pass_with_setup(...)`; the live adapter
  still owns concrete sampler, texture, and draw callbacks. `pp_paint_renderer_stroke_execution_tests`
  now covers the default dirty-union path for live-pass face-framebuffer
  execution. Next slice should target the remaining Canvas stroke execution
  seam without reopening the landed mix-pass setup helper.
- 2026-06-13: `Canvas::draw_merge()` now routes per-plane merge-target clear,
  blend-state gating, and optional checkerboard prepass through
  `execute_legacy_canvas_draw_merge_plane_setup(...)`; per-layer iteration,
  temporary-stroke branches, and concrete framebuffer ownership remain local to
  `Canvas`. Next slice should target another narrow draw-merge execution seam
  without reopening landed plane-setup, temporary-composite, layer-blend,
  final-plane, or texture-alpha helpers.
- 2026-06-13: `Canvas::draw_merge()` now routes the standard per-layer
  texture-alpha pass through
  `execute_legacy_canvas_draw_merge_layer_texture(...)`; temporary-stroke
  branches, per-plane iteration, and concrete RTT ownership remain local to
  `Canvas`. Next slice should target another narrow draw-merge execution seam
  without reopening landed temporary-composite, layer-blend, final-plane, or
  texture-alpha helpers.
- 2026-06-13: `Canvas::draw_merge()` temporary composite setup now routes
  through `execute_canvas_draw_merge_temporary_composite(...)`; the retained
  path still owns the concrete setup, sampler, texture, draw, and unbind
  callbacks. Next slice should target another narrow draw-merge seam without
  reopening the landed temporary-composite helper.
- 2026-06-13: `Canvas::draw_merge()` temporary erase and paint branch wiring
  now routes through `execute_legacy_canvas_draw_merge_temporary_composite(...)`;
  the retained path still owns the concrete setup, sampler, texture, draw, and
  unbind callbacks. Next slice should target another narrow draw-merge seam
  without reopening the landed temporary-composite helper.
- 2026-06-13: `Canvas::draw_merge()` now routes the remaining temporary erase
  and paint callback bundle through
  `execute_legacy_canvas_draw_merge_temporary_composite(...)`; only the
  branch selection remains inline. Next slice should target another narrow
  draw-merge seam without reopening the landed temporary-composite helper.
- 2026-06-13: `Canvas::draw_merge()` now routes the layer-composite shell
  through a local wrapper around
  `execute_legacy_canvas_draw_merge_layer_composite(...)`; only the final
  branch selection remains inline. Next slice should target another narrow
  draw-merge seam without reopening the landed temporary-composite helper.
- 2026-06-13: `Canvas::draw_merge()` now routes the layer texture, layer blend,
  and final-plane composite callbacks through local wrappers around the
  retained helpers; the remaining work in this lane is now just branch
  selection glue.
- 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers
  retained frame-plan assembly for previous-sample projection mode and zoom
  scaling. Next slice should target the remaining preview/Canvas stroke
  execution seam or another narrow renderer boundary without reopening the
  landed stroke-frame planner coverage.
- 2026-06-13: `NodeStrokePreview` live-pass orchestration now routes through
  `execute_legacy_stroke_preview_live_pass(...)`, and
  `pp_paint_renderer_stroke_execution_tests` now covers live-pass clear,
  traversal, and final copy ordering. Next slice should target the remaining
  preview or Canvas stroke execution seam without reopening the landed live-pass
  helper.
- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` now routes retained
  preview feedback/material/composite planning plus stroke-shader uniform
  assembly through `plan_legacy_node_stroke_preview_pass_orchestration(...)`;
  compositor coverage now locks destination-feedback fallback, composite-slot
  intent, and the retained pattern/dual shader-uniform handoff. The preview
  node still owns brush object mutation, retained `Stroke` population, and the
  concrete GL pass callbacks. Next slice should target another narrow preview
  execution seam without reopening the landed preview setup, mix, pass-sequence,
  or final-composite helpers.
- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` now routes preview
  stroke max-size fallback, dual-preview max-size derivation, pattern-scale
  flips, and Bezier preview-point generation through
  `plan_legacy_node_stroke_preview_stroke_setup(...)`; compositor coverage now
  also locks the retained stroke-setup curve/pressure intent and
  pass-sequence request-validation short-circuit behavior. The preview node
  still owns brush object mutation, camera wiring, retained `Stroke`
  population, and live GL execution. Next slice should target the remaining
  higher-level preview pass orchestration seam without reopening landed
  sample, mix, pass-sequence, or final-composite helpers.
- 2026-06-13: `Canvas::draw_merge()` per-layer blend composite now routes
  merge-RTT unbind, shader setup, optional destination-copy feedback, draw,
  and cleanup ordering through
  `execute_legacy_canvas_draw_merge_layer_blend(...)`; temporary-stroke
  branch selection, layer iteration, and final merged-plane redraw ownership
  remain local to `Canvas`. Next slice should target the remaining end-of-plane
  merged-texture copy/grid/final-redraw seam or another similarly narrow final
  composite boundary without reopening landed temporary-composite or
  per-layer blend helpers.
- 2026-06-13: `Canvas::draw_merge()` end-of-plane merged-texture copy,
  optional checkerboard redraw, and final merged-texture composite ordering now
  route through `execute_legacy_canvas_draw_merge_final_plane_composite(...)`;
  per-plane iteration and concrete framebuffer, sampler, texture, and draw
  callbacks remain local to `Canvas`. Next slice should target another narrow
  retained draw-merge boundary without reopening landed temporary-composite,
  layer-blend, or final-plane helpers.
- 2026-06-13: `Canvas::draw_merge()` erase live temporary-stroke composite now
  routes retained setup, sampler bind, texture bind, draw, and texture unbind
  ordering through `execute_legacy_canvas_stroke_temporary_composite(...)`;
  both live temporary composite branches now share the same retained execution
  helper while `Canvas` keeps only concrete GL object callbacks and broader
  final composite ownership. Next slice should target the remaining final
  composite seam without reopening landed sample, mix, dirty, or framebuffer
  helpers.
- 2026-06-13: `NodeStrokePreview::stroke_draw_mix()` now routes retained
  mix-pass shader setup plus framebuffer/state/input/draw ordering through
  `execute_legacy_node_stroke_preview_mix_pass(...)`, with compositor coverage
  locking the retained callback order and shader-plan handoff. The preview node
  keeps only the concrete GL save/restore, texture-object bind, and plane-draw
  callbacks. Next slice should target another retained preview or canvas stroke
  seam without reopening the landed preview sample, material-planning,
  pass-sequence, or final-composite helpers.
- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` now routes
  dual-pass/background/main-pass/final-composite/copy-back ordering through
  `execute_legacy_node_stroke_preview_pass_sequence(...)`; the remaining local
  preview ownership is concentrated around `stroke_draw_mix()` setup/execution.
  Next slice should target that final mix-pass setup/execution seam without
  reopening landed sample, binding, material-planning, or final-composite
  helpers.
- 2026-06-13: `Canvas::draw_merge()` non-erase live temporary-stroke
  composite ordering now routes through
  `execute_legacy_canvas_stroke_temporary_composite(...)`; erase-path and
  broader final composite ownership still remain local to `Canvas`. Next slice
  should target the erase temporary composite branch or another similarly
  narrow final composite seam without reopening landed sample, mix, dirty, or
  framebuffer helpers.
- 2026-06-13: `Canvas::draw_merge()` temporary paint branch execution now
  routes through `make_legacy_canvas_draw_merge_temporary_paint_composite(...)`;
  the live path still owns the concrete layer RTT, mixer RTT, sampler, and
  plane callbacks.
- 2026-06-13: `Canvas::stroke_draw_samples()` now routes face-indexed
  destination bind/copy/unbind and brush upload/draw through
  `execute_legacy_canvas_stroke_face_sample_polygon(...)`; the retained sample
  executor now owns the face-aware dispatch contract while `Canvas` keeps only
  the concrete GL object callbacks. Next slice should target any remaining
  final temporary-texture composite setup without reopening landed sample,
  mix, sampler, dirty, face, or pad helpers.
- 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers
  direct retained frame-sample callback ordering plus
  `execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(...)`
  timing/state semantics, including pre-update dirty visibility and
  `previous_pass_dirty_box` override behavior. Next test slice should target
  the next header-level preview live sample ordering surface without reopening
  production files.
- 2026-06-13: `Canvas::stroke_draw_mix()` now routes visible-plane filtering,
  retained sampler/texture-slot binding, and final plane draw ordering through
  `execute_legacy_canvas_stroke_mix_pass(...)`; mixer framebuffer/state setup
  and per-plane shader material/MVP preparation remain local to `Canvas`. Next
  slice should target the remaining `stroke_draw_samples()` callback body or
  any final temporary-texture composite setup without reopening landed
  sample, sampler, dirty, face, or pad helpers.

### STR-019 - Extract Stroke Draw Mix Mixer State And Copy Ordering

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `stroke_draw_mix()` mixer-framebuffer state and copy-order
callbacks into a retained helper so `Canvas::stroke_draw_mix()` keeps only
branch selection plus concrete GL object wiring.

Done Checks:

- `Canvas::stroke_draw_mix()` no longer owns the remaining mixer framebuffer
  state and copy ordering inline.
- Regression coverage proves the extracted helper preserves the mixer-state
  callback order.
- `docs/modernization/debt.md` records the reduced stroke-mix shell surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-019 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `e7d96bfd` |

- 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers
  retained preview background capture ordering, final composite ordering, and
  preview texture-copy bind-before-copy behavior via
  `legacy_canvas_stroke_preview_services.h`. Next test slice should target the
  next header-level preview live sample or mix-pass ordering surface without
  reopening production files.
- 2026-06-13: `Canvas::stroke_draw_samples()` now routes polygon
  triangulation, sample-point assembly, and the retained destination-copy /
  upload / draw helper handoff through
  `execute_legacy_canvas_stroke_sample_polygon(...)`; direct GL callback
  wiring and the remaining live draw ownership stay local to `Canvas`. Next
  slice should target the remaining callback body or final temporary-texture
  composite setup without reopening the landed sampler, dirty, face, or pad
  helpers.
- 2026-06-13: `NodeStrokePreview::stroke_draw_mix()` now routes mixer
  framebuffer bind/unbind, viewport/scissor/blend state, texture-slot binding,
  and final plane draw through one local helper; material planning and shader
  uniform setup remain local to the preview node. Next slice should target the
  remaining mix-pass material/setup orchestration without reopening the landed
  preview live-pass, binding, sample, or final composite helpers.
- 2026-06-13: `NodeStrokePreview::stroke_draw_mix()` now routes retained
  mix-pass material planning plus composite/pattern/dual uniform payload
  assembly through `plan_legacy_node_stroke_preview_mix_pass(...)`, with
  compositor coverage locking the retained preview mix adapter semantics for
  composite-pass patterning and dual state. Mixer framebuffer ownership,
  retained shader execution, texture binding, and final draw/copy ordering
  remain local to the preview node. Next slice should target the remaining
  mix-pass execution ownership without reopening the landed preview live-pass,
  binding, sample, or final composite helpers.
- 2026-06-13: `pp_paint_renderer_stroke_execution_tests` now also covers
  retained texture-dispatch activation order and sampler-dispatch routing
  across brush tip, destination, pattern, and mixer helper inputs. Next test
  slice should extend the dedicated lane into frame-sample loop ordering or
  preview-side retained helper coverage.
- 2026-06-13: Added `pp_paint_renderer_stroke_execution_tests` as a dedicated
  retained stroke execution-helper target covering texture-input binding order,
  sample destination-copy behavior, live-pass face-framebuffer dirty tracking,
  and pad-face destination-copy behavior. Next test slice should extend this
  target toward the newer sampler-dispatch helpers or preview-side retained
  execution helpers.
- 2026-06-13: `Canvas::stroke_draw` live-pass sampler bind/unbind plus
  semantic texture-input dispatch now routes through retained stroke execution
  helpers; concrete GL object mapping, framebuffer ownership, shader timing,
  and final draw execution remain local to `Canvas`. Next slice should target
  the remaining live draw execution boundary inside `stroke_draw_samples()` or
  the final temporary-texture composite setup without reopening the landed
  dirty, face-framebuffer, pad, or texture-intent helpers.
- 2026-06-13: `NodeStrokePreview::stroke_draw_samples()` now routes
  destination bind/unbind, framebuffer copy callback wrapping, sample-point
  assembly, and brush-vertex upload/draw through one local helper; mixer-pass
  state execution and higher-level pass orchestration remain local to the
  preview node. Next slice should target the remaining mixer-pass state/copy
  ordering without reopening the landed preview live-pass, binding, or final
  composite helpers.
- 2026-06-13: `Canvas::stroke_draw` main, pad, and dual live-pass
  texture-input binding/unbinding intent now routes through retained stroke
  execution helpers; sampler binding, concrete GL object mapping, framebuffer
  ownership, and final draw execution remain local to `Canvas`. Next slice
  should target the remaining sampler binding/teardown or the direct
  semantic-input-to-GL mapping callbacks without reopening the landed dirty,
  face-framebuffer, or pad helpers.
- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` live-pass sampler
  binding, dual/main pass texture binding, checkerboard/background capture
  wrapping, and final preview copy-back now route through named local helpers;
  mixer state execution and per-sample GL ordering remain local to the preview
  node. Next slice should target the remaining mixer-pass state/copy ordering
  or sample-pass destination callback wrapping without reopening the landed
  preview live-pass or final-composite helpers.
- 2026-06-13: `NodeStrokePreview::draw_stroke_immediate()` dual-pass and
  main-pass frame-loop execution plus full-frame copy-back now route through
  shared local helpers; checkerboard/background capture ordering, texture-unit
  binding, mixer ownership, and final composite semantics remain local to the
  preview node. Next slice should target background/final-copy helpers or
  pass-level texture binding without reopening the new preview composite
  helper.
- 2026-06-13: `Canvas::stroke_draw` main and dual live-pass per-face
  framebuffer begin/end execution plus pad-face array assembly now route
  through `legacy_canvas_stroke_execution_services.h`; shader activation
  timing, texture/sampler binding, framebuffer ownership, and final draw
  execution remain local to `Canvas`. Next slice should target the remaining
  pass-level texture binding or final composite setup without reopening the
  newer pad, dirty, or commit helpers.
- 2026-06-13: `Canvas::stroke_draw` current and dual stroke per-face
  framebuffer/sample callback ordering now routes through
  `legacy_canvas_stroke_execution_services.h`; framebuffer ownership, shader
  uniform timing, sampler/texture binding, and draw execution remain local to
  Canvas. Next slice should target the remaining per-face retained state around
  sample execution without reopening the new pad-pass, dirty-mutation, or
  commit executors.
- 2026-06-13: `Canvas::stroke_draw` current and dual stroke dirty-box mutation
  now routes through `legacy_canvas_stroke_execution_services.h`; framebuffer
  binding, shader uniform timing, sampler/texture binding, and draw execution
  remain local to Canvas. Next slice should wrap the retained per-face
  framebuffer/sample callback ordering without rewriting the new pad-pass or
  commit executors.
- 2026-06-13: `Canvas::stroke_commit` now routes retained commit input
  texture/sampler binding, erase/composite draw dispatch, committed-copy, and
  dilate draw through `legacy_canvas_stroke_commit_services.h`; history
  readback, `ActionStroke` population, and layer dirty-box mutation remain
  local to Canvas.
- 2026-06-13: `pp_paint_renderer` owns tested Canvas stroke commit sequencing
  and commit texture slot intent. Next slice should wire
  `legacy_canvas_stroke_commit_services.h` into `Canvas::stroke_commit` while
  keeping history/layer mutation local.

### LATER-004 - Remove Catch-All Platform Legacy Adapter

Status: Blocked
Score: +5 platform and package parity
Debt: `DEBT-0017`
Blocked By: Apple/Web split tasks and per-platform package validation

Done Checks:

- `src/platform_legacy/legacy_platform_services.*` is deleted or contains only
  compile-time unsupported stubs with debt entries.
- Windows, Apple, Android, Linux, and Web platform services are named targets.
- Platform-build and package-smoke report explicit pass/fail per platform.

## Completed Task Log

| Date | Task | Score Change | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-12 | RND-004 | +2 hardening and future backend readiness | `ctest --preset desktop-gpu --build-config Debug --output-on-failure`; `ctest --preset desktop-fast --build-config Debug -R "pp_renderer_gl\|pp_paint_renderer" --output-on-failure` | e37b2929 |
| 2026-06-12 | DEP-002 | +1 build and CMake ownership | `powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard` | 648404ee |
| 2026-06-12 | RND-002 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export\|pp_paint_renderer_compositor\|pano_cli_plan_export_snapshot_route\|pano_cli_simulate_document_export" --output-on-failure` | 46fb8ef |
| 2026-06-12 | RND-001 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export\|pp_paint_renderer_compositor\|pano_cli_plan_export_snapshot_route\|pano_cli_simulate_document_export" --output-on-failure` | 46fb8ef |
| 2026-06-12 | ADP-004 | +2 legacy adapter retirement | VS-bundled CMake build of `pp_app_core_app_dialog_tests` and `pano_cli`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_app_dialog\|pano_cli_plan_app_dialog" --output-on-failure` | 46fb8ef |
| 2026-06-12 | PLT-001 | +2 platform and package parity | VS-bundled CMake build of `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; Apple remote build blocked by unpublished fmt submodule pointer before DEP-001 correction | 46fb8ef |
| 2026-06-12 | RND-003 | +3 renderer boundary and OpenGL parity | VS-bundled CMake build of `pp_paint_renderer_compositor_tests`, `pp_app_core_document_export_tests`, and `pano_cli`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_export\|pp_paint_renderer_compositor\|pano_cli_plan_export_snapshot_route" --output-on-failure` | 46fb8ef |
| 2026-06-12 | DEP-001 | +1 build and CMake ownership | VS-bundled CMake build of `PanoPainter` and `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure` | 46fb8ef |
| 2026-06-12 | ADP-003 | +1 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_route\|pp_app_core_document_session\|pano_cli_plan_open_route\|pano_cli_simulate_app_session\|pano_cli_plan_document_file\|pano_cli_plan_document_version" --output-on-failure` | 34a9e910 |
| 2026-06-12 | PLT-002 | +2 platform and package parity | `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_layer\|pano_cli_plan_layer\|pp_platform_api_tests" --output-on-failure` | 8cd38401 |
| 2026-06-12 | ADP-002 | +1 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_layer\|pano_cli_plan_layer" --output-on-failure`; `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_layer\|pano_cli_plan_layer\|pp_platform_api_tests" --output-on-failure` | ae242852 |
| 2026-06-12 | ADP-001 | +1 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_app_core_document_resize\|pp_app_core_document_canvas\|pano_cli_plan_document_resize\|pano_cli_plan_canvas_clear" --output-on-failure`; `powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core\|pano_cli_plan"` | e489b1e2 |
| 2026-06-12 | MT-001 | 0 | `git diff -- docs\modernization\roadmap.md docs\modernization\tasks.md` | same docs slice |
| 2026-06-13 | STR-004 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | 5c03b130 |
## Task Template

Use this shape when adding a new task:

````markdown
### STR-004 - Collapse Remaining Stroke Dispatch Glue

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`,
`tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Remove the last inline `Canvas::stroke_draw()` dispatch construction that is
still just wiring. Keep the live adapter owning concrete GL objects, but move
the remaining helper-builder glue into retained stroke-execution helpers or
delete it if the callsite can consume a smaller already-tested helper.

Done Checks:

- `Canvas::stroke_draw()` has no ad hoc `LegacyCanvasStroke*Dispatch` literal
  blocks for the remaining live-pass wiring.
- The helper-builder coverage tests prove the remaining dispatch helpers route
  the expected callbacks.
- `docs/modernization/debt.md` records the reduced stroke-dispatch surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### STR-005 - Extract Live Stroke Face Callback Orchestration

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`,
`tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Move the remaining inline live-pass face callback orchestration in
`Canvas::stroke_draw()` into a retained helper so the live stroke path owns only
concrete GL object wiring and per-face execution callbacks. Keep the face loop
and callback order unchanged.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the remaining inline face-pass
  callback block around `execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(...)`.
- Regression coverage proves the extracted helper preserves callback order and
  face-state updates.
- `docs/modernization/debt.md` records the reduced live-stroke callback surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-005 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | `4c9809f7` |

### STR-006 - Extract Dual Stroke Face Execution Orchestration

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`,
`tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Move the inline dual-stroke face execution lambda in `Canvas::stroke_draw()`
into a retained helper so the dual-pass branch keeps only concrete shader,
texture, and framebuffer wiring. Preserve dual-pass face order and callback
behavior.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline dual-pass face execution
  lambda around `execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(...)`.
- Regression coverage proves the extracted helper preserves dual-pass callback
  order and face execution.
- `docs/modernization/debt.md` records the reduced dual-pass callback surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-006 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | `ae46be9f` |

### STR-007 - Extract Main Stroke Face Loop Orchestration

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`,
`tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Move the inline main-stroke face loop in `Canvas::stroke_draw()` into a
retained helper so the main-pass branch keeps only concrete shader, sampler,
and framebuffer wiring. Preserve main-pass face order and callback behavior.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline main-pass face loop
  around `execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(...)`.
- Regression coverage proves the extracted helper preserves main-pass callback
  order and face execution.
- `docs/modernization/debt.md` records the reduced main-pass callback surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-007 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | `fee09e53` |

### STR-008 - Extract Pad Stroke Face Orchestration

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`,
`tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Move the inline pad-stroke face execution block in `Canvas::stroke_draw()` into
a retained helper so the pad branch keeps only concrete brush-shape, texture,
and framebuffer wiring. Preserve pad-face order and copy timing.

Done Checks:

- `Canvas::stroke_draw()` no longer contains the inline pad-face block around
  `execute_legacy_canvas_stroke_pad_faces(...)`.
- Regression coverage proves the extracted helper preserves pad-face order and
  copy behavior.
- `docs/modernization/debt.md` records the reduced pad callback surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-008 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure`; `MSBuild.exe out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | `11a62e9b` |

### STR-009 - Extract Stroke Commit Callback Orchestration

Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`,
`tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Move the large inline callback block in `Canvas::stroke_commit()` into a
retained helper so commit sequencing keeps only concrete layer/texture/canvas
wiring. Preserve commit ordering, history capture, and dilate sequencing.

Done Checks:

- `Canvas::stroke_commit()` no longer contains the large inline commit callback
  block around `execute_legacy_canvas_stroke_commit_sequence(...)`.
- Regression coverage proves the extracted helper preserves commit ordering and
  history capture.
- `docs/modernization/debt.md` records the reduced stroke-commit callback
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-009 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "retained_stroke_commit_callback_builder_preserves_order|retained_stroke_commit_runner_preserves_per_face_step_order" --output-on-failure`; `& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_compositor_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | `e7813c2f` |

### STR-013 - Extract Stroke Commit Face Input Binding

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/legacy_canvas_stroke_commit_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the face-indexed commit-input binding wrapper behind the retained stroke
commit service boundary so `Canvas::stroke_commit()` only supplies concrete
face bindings.

Done Checks:

- `Canvas::stroke_commit()` uses the retained face-input binding helper.
- Regression coverage proves the face-input helper forwards the sequence slots.
- `docs/modernization/debt.md` records the reduced stroke-commit face-binding
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-013 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure` | `6220f333` |

### STR-014 - Extract Stroke Commit Request Assembly

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the face array assembly and commit request construction out of
`Canvas::stroke_commit()` so the callsite only triggers the retained commit
sequence with concrete state.

Done Checks:

- `Canvas::stroke_commit()` no longer builds the face array inline.
- Regression coverage proves the request assembly helper preserves dirty-face
  ordering.
- `docs/modernization/debt.md` records the reduced stroke-commit request
  assembly surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-014 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure` | `536f2683` |

- 2026-06-13: `Canvas::stroke_commit()` now routes the retained commit request
  assembly through `make_legacy_canvas_stroke_commit_request(...)`, so the
  callsite only supplies concrete faces, sequence, and callbacks.

### STR-015 - Extract Stroke Commit Sequence Invocation

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `execute_legacy_canvas_stroke_commit_sequence(...)`
invocation shape out of `Canvas::stroke_commit()` so the callsite only passes
already-built retained commit state.

Done Checks:

- `Canvas::stroke_commit()` no longer spells the retained commit request
  invocation inline.
- Regression coverage proves the helper preserves the retained request shape.
- `docs/modernization/debt.md` records the reduced stroke-commit invocation
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | --- | --- | --- |
| 2026-06-13 | STR-015 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure` | `161900c5` |

### STR-011 - Extract Preview Main Pass Orchestration

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Extract the remaining `NodeStrokePreview::draw_stroke_immediate()` main-pass
orchestration into a retained helper boundary without changing preview output.

Done Checks:

- Main-pass live execution uses a retained helper for setup, frame execution,
  and final texture copy.
- Preview regression coverage proves callback order and binding still match the
  current behavior.
- `DEBT-0036` records the narrowed preview orchestration surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-011 | no score movement | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | `fa6ac4dc` |

### STR-036 - Extract Preview Main Live Pass Orchestration

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `NodeStrokePreview::draw_stroke_immediate()` main live-pass
orchestration into a retained helper so the callsite keeps only branch
selection and direct live-pass dispatch.

Closeout: `b9647847`

Done Checks:

- `NodeStrokePreview::draw_stroke_immediate()` no longer owns the main live-pass
  orchestration inline.
- Regression coverage proves the helper preserves live-pass branch order and
  preview copy behavior.
- `docs/modernization/debt.md` records the reduced preview live-pass surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-037 - Extract Preview Dual Pass Live Body

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `NodeStrokePreview::draw_stroke_immediate()` dual-pass live
body into a retained helper so the callsite keeps only sequence orchestration
and preview copy handling.

Closeout: `a2e805f9`

Done Checks:

- `NodeStrokePreview::draw_stroke_immediate()` no longer owns the dual-pass live
  body inline.
- Regression coverage proves the helper preserves the dual-pass callback order
  and preview copy behavior.
- `docs/modernization/debt.md` records the reduced dual-pass live surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-038 - Extract Preview Pass Sequence Request Assembly

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `NodeStrokePreview::draw_stroke_immediate()` pass-sequence
request assembly into a retained helper so the callsite keeps only the
sequence wrapper and final state restoration.

Closeout: `3672f9a5`

Done Checks:

- `NodeStrokePreview::draw_stroke_immediate()` no longer owns the pass-sequence
  request object inline.
- Regression coverage proves the helper preserves dual/main sequence ordering
  and copy behavior.
- `docs/modernization/debt.md` records the reduced preview pass-sequence
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-039 - Wire Stroke Commit Service Boundary

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_commit_services.h`, `tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Move the remaining `Canvas::stroke_commit()` service wiring behind the retained
stroke-commit helper boundary so the callsite keeps only local history and
layer mutation logic.

Closeout: `7cafaaa1`

Done Checks:

- `Canvas::stroke_commit()` no longer owns the retained commit-service wiring
  inline.
- Regression coverage proves the helper preserves commit ordering and history
  capture.
- `docs/modernization/debt.md` records the reduced stroke-commit service
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### STR-040 - Extract Stroke Commit Local History And Dirty Mutation

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `tests/paint_renderer/stroke_execution_tests.cpp`

Goal:

Move the remaining local history and dirty-mutation work inside
`make_canvas_stroke_commit_callbacks()` into a retained helper so the callback
builder only forwards concrete canvas state.

Closeout: `aa53a5f9`

Done Checks:

- `make_canvas_stroke_commit_callbacks()` no longer owns the history/dirty
  mutation block inline.
- Regression coverage proves the helper preserves action bookkeeping and dirty
  box updates.
- `docs/modernization/debt.md` records the reduced stroke-commit history
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_stroke_execution_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### STR-041 - Extract Preview Mix Pass Execution Ownership

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `NodeStrokePreview::stroke_draw_mix()` execution ownership
into a retained helper so the callsite keeps only the structural mix-pass
orchestration.

Closeout: `cf859cd4`

Done Checks:

- `NodeStrokePreview::stroke_draw_mix()` no longer owns the mix-pass execution
  block inline.
- Regression coverage proves the helper preserves mixer framebuffer, viewport,
  and draw ordering.
- `docs/modernization/debt.md` records the reduced preview mix-pass execution
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-042 - Extract Preview Main Live Sample Callback Wrapping

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `NodeStrokePreview::make_stroke_draw_immediate_main_live_pass_request()`
sample/copy callback wrapping into retained helpers so the request builder only
forwards concrete preview state.

Closeout: `2053c55b`

Done Checks:

- `make_stroke_draw_immediate_main_live_pass_request()` no longer owns the
  live-pass sample/copy callback wrapping inline.
- Regression coverage proves the helper preserves live-pass sample ordering and
  preview copy behavior.
- `docs/modernization/debt.md` records the reduced preview main-live-pass
  callback surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-043 - Extract Preview Mixer Pass State And Copy Ordering

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `NodeStrokePreview::stroke_draw_samples()` mixer-pass state
and copy ordering into retained helpers so the callsite keeps only the final
sample dispatch.

Done Checks:

- `NodeStrokePreview::stroke_draw_mix()` no longer owns the mixer-pass
  state/copy ordering inline.
- Regression coverage proves the helper preserves mixer framebuffer binding,
  preview copy behavior, and mix-input ordering.
- `docs/modernization/debt.md` records the reduced preview mixer-pass surface.

Closeout: `c147c1d1`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-044 - Extract Preview Sample Pass Request Construction

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `execute_stroke_preview_sample_pass()` request construction
into a retained helper so the executor wrapper keeps only request dispatch and
dirty-bounds return handling.

Done Checks:

- `execute_stroke_preview_sample_pass()` no longer owns the sample-request
  construction inline.
- Regression coverage proves the helper preserves destination binding, copy,
  and brush draw ordering.
- `docs/modernization/debt.md` records the reduced preview sample-pass surface.

Closeout: `5f66d0e7`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-045 - Extract Preview Final Composite Request Construction

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `execute_stroke_preview_final_composite_pass()` request
construction into a retained helper so the final preview composite wrapper
keeps only dispatch and copy handling.

Done Checks:

- `execute_stroke_preview_final_composite_pass()` no longer owns the
  final-composite request construction inline.
- Existing preview final-composite executor coverage still proves the helper
  preserves shader setup, sampler binding, input binding, and draw ordering.
- `docs/modernization/debt.md` records the reduced preview final-composite
  surface.

Closeout: `538441a5`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

### STR-046 - Extract Draw Merge Plane Iteration Orchestration

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `Canvas::draw_merge()` plane iteration and branch
orchestration into a retained helper so the callsite keeps only framebuffer
setup, per-plane dispatch, and final composite gating.

Done Checks:

- `Canvas::draw_merge()` no longer owns the per-plane iteration and branch
  orchestration inline.
- Regression coverage proves the helper preserves plane dispatch order and
  final composite gating.
- `docs/modernization/debt.md` records the reduced draw-merge plane-loop
  surface.

Closeout: `d46399f4`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-047 - Extract Draw Merge Final Composite Gating

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `Canvas::draw_merge()` final composite gating into a
retained helper so the plane-loop helper only dispatches plane setup and layer
branches.

Done Checks:

- `execute_canvas_draw_merge_plane_iteration(...)` no longer owns the final
  composite gating inline.
- Regression coverage proves the helper preserves the `use_blend` final
  composite decision.
- `docs/modernization/debt.md` records the reduced draw-merge final-composite
  surface.

Closeout: `a16ac39d`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-048 - Extract Draw Merge Branch Orchestration Body

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `Canvas::draw_merge_branch_orchestration()` body into
retained helpers so the branch wrapper keeps only branch selection and helper
dispatch.

Done Checks:

- `Canvas::draw_merge_branch_orchestration()` no longer owns the branch
  execution body inline.
- Regression coverage proves the extracted helper preserves temporary erase,
  temporary paint, texture, and blend execution order.
- `docs/modernization/debt.md` records the reduced draw-merge branch surface.

Closeout: `85f8af42`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-049 - Extract Draw Merge Temporary Paint Request Construction

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `Canvas::draw_merge_temporary_paint_branch()` request
construction into a retained helper so the temporary-paint wrapper keeps only
dispatch and execution handling.

Done Checks:

- `Canvas::draw_merge_temporary_paint_branch()` no longer owns the
  temporary-paint request construction inline.
- Regression coverage proves the helper preserves temporary-paint sampler,
  texture, and draw ordering.
- `docs/modernization/debt.md` records the reduced draw-merge temporary-paint
  surface.

Closeout: `1a28716e`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-050 - Extract Draw Merge Temporary Composite Dispatch Helpers

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining temporary composite dispatch bodies used by
`Canvas::draw_merge_branch_orchestration()` into retained helpers so the
branch body keeps only branch selection and helper dispatch.

Done Checks:

- `execute_canvas_draw_merge_branch_body(...)` no longer owns the temporary
  erase, temporary paint, texture, and blend dispatch bodies inline.
- Regression coverage proves the extracted helpers preserve temporary erase,
  temporary paint, texture, and blend execution order.
- `docs/modernization/debt.md` records the reduced draw-merge temporary
  composite surface.

Closeout: `27d34f2f`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-051 - Extract Draw Merge Branch Dispatch Bodies

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining branch dispatch object assembly used by
`execute_canvas_draw_merge_branch_body()` into a retained helper so the branch
body keeps only helper dispatch.

Done Checks:

- `execute_canvas_draw_merge_branch_body()` no longer owns the branch dispatch
  object assembly inline.
- Regression coverage proves the extracted helper preserves temporary erase,
  temporary paint, texture, and blend execution order.
- `docs/modernization/debt.md` records the reduced draw-merge branch-dispatch
  surface.

Closeout: `8e1aea9a`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-052 - Extract Draw Merge Per-Plane Dispatch Wrapper

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining per-plane dispatch wrapper used by
`execute_canvas_draw_merge_plane_iteration()` into retained helpers so the
plane loop keeps only plane selection and helper dispatch.

Done Checks:

- `execute_canvas_draw_merge_plane_iteration()` no longer owns the per-plane
  dispatch wrapper inline.
- Regression coverage proves the extracted helper preserves per-plane
  framebuffer setup, branch dispatch, and final composite gating.
- `docs/modernization/debt.md` records the reduced draw-merge plane-dispatch
  surface.

Closeout: `e8fe66da`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-053 - Extract Draw Merge Per-Plane Setup And Unbind

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining per-plane framebuffer setup and unbind handling used by
`execute_canvas_draw_merge_plane_dispatch()` into retained helpers so the
per-plane wrapper keeps only branch dispatch and final composite gating.

Done Checks:

- `execute_canvas_draw_merge_plane_dispatch()` no longer owns the per-plane
  framebuffer setup and unbind handling inline.
- Regression coverage proves the extracted helper preserves plane framebuffer
  setup, branch dispatch, and final composite gating.
- `docs/modernization/debt.md` records the reduced draw-merge plane setup
  surface.

Closeout: `e8fe66da`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-054 - Extract Draw Merge Final Plane Composite Execution

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`
Closeout: `e98c8f48`

Goal:

Move the remaining `Canvas::draw_merge_final_plane_composite()` execution
bundle into retained helpers so the final composite wrapper keeps only request
assembly and dispatch.

Done Checks:

- `Canvas::draw_merge_final_plane_composite()` no longer owns the final-plane
  execution bundle inline.
- Regression coverage proves the extracted helper preserves final composite
  sampler, texture, draw, and unbind ordering.
- `docs/modernization/debt.md` records the reduced draw-merge final-plane
  surface.

### STR-055 - Extract Stroke Commit Sequence Wrapper

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `tests/paint_renderer/stroke_execution_tests.cpp`
Closeout: `d5af0b98`

Goal:

Move the remaining `Canvas::stroke_commit()` sequence wrapper into a retained
helper so the callsite keeps only request assembly and dispatch.

Done Checks:

- `Canvas::stroke_commit()` no longer owns the sequence wrapper inline.
- Regression coverage proves the extracted helper preserves the commit request
  build and execution order.
- `docs/modernization/debt.md` records the reduced stroke-commit wrapper
  surface.

### STR-056 - Extract Stroke Commit Plan Assembly

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `tests/paint_renderer/stroke_execution_tests.cpp`
Closeout: `7baf377c`

Goal:

Move the retained `Canvas::stroke_commit()` plan construction into a helper so
the callsite only supplies current state and dispatches the result.

Done Checks:

- `Canvas::stroke_commit()` no longer builds the stroke-commit plan inline.
- Regression coverage proves the helper preserves the plan fields used by the
  retained commit request.
- `docs/modernization/debt.md` records the reduced stroke-commit planning
  surface.

### STR-057 - Extract Stroke Commit Request Dispatch Invocation

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `tests/paint_renderer/stroke_execution_tests.cpp`
Closeout: `05386598`

Goal:

Move the remaining `Canvas::stroke_commit()` request-dispatch invocation into
a helper so the callsite keeps only state capture and helper dispatch.

Done Checks:

- `Canvas::stroke_commit()` no longer spells the request-dispatch invocation
  inline.
- Regression coverage proves the helper preserves the retained dispatch
  arguments.
- `docs/modernization/debt.md` records the reduced stroke-commit dispatch
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-058 - Extract Stroke Commit Pre-Dispatch State Capture

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `tests/paint_renderer/stroke_execution_tests.cpp`
Closeout: `be422245`

Goal:

Move the remaining pre-dispatch setup and state-capture seam in
`Canvas::stroke_commit()` into a retained helper so the callsite keeps only the
final request dispatch.

Done Checks:

- `Canvas::stroke_commit()` no longer owns the pre-dispatch setup/state capture
  inline.
- Regression coverage proves the helper preserves captured commit state and
  dispatch inputs.
- `docs/modernization/debt.md` records the reduced stroke-commit pre-dispatch
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### STR-059 - Extract Stroke Draw Main Pass Request Assembly

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/canvas.h`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/stroke_execution_tests.cpp`
Closeout: `be422245`

Goal:

Move the remaining `Canvas::stroke_draw()` main-pass request assembly into a
retained helper so the callsite keeps only branch selection and live-pass
dispatch.

Done Checks:

- `Canvas::stroke_draw()` no longer owns the main-pass request assembly inline.
- Regression coverage proves the helper preserves main-pass request fields and
  face execution.
- `docs/modernization/debt.md` records the reduced main-pass request-assembly
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### STR-060 - Extract Stroke Draw Main Pass Destination Binding

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/canvas.h`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/stroke_execution_tests.cpp`
Depends: `STR-059`

Goal:

Move the remaining `Canvas::stroke_draw()` destination-binding seam into a
retained helper so the callsite keeps only main-pass request assembly,
branch selection, and live-pass dispatch.

Done Checks:

- `Canvas::stroke_draw()` no longer owns the main-pass destination-binding
  seam inline.
- Regression coverage proves the helper preserves destination binding order
  and face execution.
- `docs/modernization/debt.md` records the reduced main-pass destination-
  binding surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution|pp_paint_renderer_compositor" --output-on-failure
```

### AUD-001 - Normalize Historical Task Tracker Duplicates

Status: Done
Score: no score movement
Debt: none
Scope: `docs/modernization/tasks.md`

Goal:

This historical tracker normalization was completed without changing code:
duplicate `STR-010`, duplicate/ambiguous `STR-016`, duplicate `STR-030`,
malformed `RND-006` completed-task table shape, and stale `pending` closeout
rows were normalized for auditability.

Done Checks:

- Each historical task id has one active section or one clearly marked
  historical alias note.
- No completed-task row has `pending` as a commit.
- Completed-task tables use a consistent Date/Task/Score/Validation/Commit
  shape.
- The scorecard total is not changed unless backed by already committed rows.

Validation:

```powershell
rg -n "pending|### STR-010|### STR-016|### STR-030|RND-006" docs\modernization\tasks.md
git diff -- docs\modernization\tasks.md
```

### ARCH-001 - Add Component Boundary Dependency Self-Test

Status: Done
Score: +2 pure component behavior ownership
Debt: `DEBT-0003`, `DEBT-0008`
Scope: `scripts/dev/`, `scripts/dev/check_component_boundaries.py`,
`tests/CMakeLists.txt`,
`docs/modernization/build-inventory.md`

Goal:

Add an automated boundary check that fails when pure component headers or
targets include platform SDK headers, OpenGL headers, retained `App::I`/
`Canvas::I` singletons, or backend-specific types outside allowed
`pp_renderer_gl` and `pp_platform_*` targets.

Done Checks:

- The check encodes allowed dependency direction for `pp_foundation`,
  `pp_assets`, `pp_paint`, `pp_document`, `pp_renderer_api`,
  `pp_paint_renderer`, `pp_ui_core`, `pp_app_core`, and platform/backend
  targets.
- The check reports exact offending file, include, or target edge.
- Existing known legacy exceptions are explicit allowlist entries with debt ids.
- CTest registers the check under `desktop-fast`.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "panopainter_component_boundary" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-14 | ARCH-001 | +2 pure component behavior ownership | `ctest --preset desktop-fast --build-config Debug -R "panopainter_component_boundary" --output-on-failure` | `f78fc307` |
| 2026-06-14 | RND-008 | +2 hardening and future backend readiness | `ctest --preset renderer-conformance --build-config Debug --output-on-failure`; `ctest --preset desktop-gpu --build-config Debug --output-on-failure`; `ctest --preset desktop-fast --build-config Debug -R "panopainter_renderer_conformance_matrix_self_test" --output-on-failure` | `f78fc307` |
| 2026-06-14 | RND-007 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_renderer_api|pp_renderer_gl|pp_paint_renderer|panopainter_renderer_api_contract_self_test" --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli` | `f78fc307` |

### RND-007 - Freeze Renderer API Backend Contract

Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0036`, `DEBT-0064`
Scope: `src/renderer_api/*`, `src/renderer_gl/*`,
`src/paint_renderer/*`, `tests/renderer_api/*`,
`tests/renderer_gl/*`, `tests/paint_renderer/*`,
`docs/modernization/renderer_api_contract.md`, `scripts/dev/check_renderer_api_contract.py`

Goal:

Define the minimum renderer contract that OpenGL, Vulkan, and Metal must share:
resource descriptors, lifetime rules, texture upload/readback, render-target
attachment, copy/blit semantics, viewport/scissor state, shader/program
binding, feature flags, frame capture, debug labels, and error/status returns.

Done Checks:

- `pp_renderer_api` documents and tests the backend-neutral contract without GL,
  Vulkan, Metal, platform SDK, or window headers.
- `pp_renderer_gl` passes the same contract expectations through focused
  backend tests or command-plan tests.
- `pp_paint_renderer` chooses only renderer-api feature/path data; it does not
  branch on OpenGL-specific symbols.
- `DEBT-0064` has a removal path tied to a callback-only or renderer-owned
  texture seam.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_renderer_api|pp_renderer_gl|pp_paint_renderer|panopainter_renderer_api_contract_self_test" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

### RND-008 - Add Backend Conformance Test Matrix

Status: Done
Score: +2 hardening and future backend readiness
Debt: `DEBT-0036`
Depends: `RND-007`
Scope: `tests/renderer_api/`, `tests/renderer_gl/`, `tests/CMakeLists.txt`,
`CMakePresets.json`, `docs/modernization/build-inventory.md`,
`scripts/dev/check_renderer_conformance_matrix.py`

Goal:

Create reusable backend conformance fixtures that every renderer backend must
pass before it can be considered for production: command recording order,
resource creation failures, texture state transitions, copy/blit bounds,
readback byte size, framebuffer feedback path selection, and deterministic
golden/readback samples.

Done Checks:

- Recording backend and OpenGL backend run equivalent conformance cases where
  possible.
- Backend-specific skips are explicit and tied to feature flags, not target
  names.
- `desktop-fast` covers headless contract cases; `desktop-gpu` covers real GL
  readback/golden cases.
- Future Vulkan/Metal lab targets can register the same fixture family without
  changing paint-renderer tests.

Validation:

```powershell
ctest --preset renderer-conformance --build-config Debug --output-on-failure
ctest --preset desktop-gpu --build-config Debug --output-on-failure
ctest --preset desktop-fast --build-config Debug -R "panopainter_renderer_conformance_matrix_self_test" --output-on-failure
```

### RND-009 - Scaffold Opt-In Vulkan Lab Backend

Status: Blocked
Score: +2 hardening and future backend readiness
Debt: none
Depends: `RND-007`, `RND-008`
Blocked By: Vulkan SDK/package decision, validation-layer availability, and
OpenGL conformance baseline stability.
Scope: `src/renderer_vulkan_lab/*`, root `CMakeLists.txt`,
`tests/renderer_vulkan_lab/*`, `docs/modernization/build-inventory.md`

Goal:

Add a non-default `pp_renderer_vulkan_lab` target that proves the renderer-api
contract can map to Vulkan command buffers, resource lifetimes, layout
transitions, and ping-pong compositing without entering the production app path.

Done Checks:

- Target is opt-in and never linked by `PanoPainter`.
- Smoke tests create/destroy a device, allocate a tiny render target, execute a
  clear/copy/readback path where local Vulkan support exists, and skip cleanly
  when unavailable.
- Validation-layer failures fail the lab test.
- No production OpenGL behavior changes.

Validation:

```powershell
cmake --build --preset windows-msvc-default --config Debug --target pp_renderer_vulkan_lab_tests
ctest --preset desktop-gpu --build-config Debug -R "pp_renderer_vulkan_lab" --output-on-failure
```

### RND-010 - Scaffold Opt-In Metal Lab Backend

Status: Blocked
Score: +2 hardening and future backend readiness
Debt: `DEBT-0059`
Depends: `RND-007`, `RND-008`
Blocked By: Apple root package/app gate, signed bundle policy, and Mac mini
toolchain validation.
Scope: `src/renderer_metal_lab/*`, root `CMakeLists.txt`,
`tests/renderer_metal_lab/*`, `scripts/automation/apple-remote-build.ps1`,
`docs/modernization/build-inventory.md`

Goal:

Add a non-default `pp_renderer_metal_lab` target that proves the renderer-api
contract can map to Metal command encoders, textures, render passes, and
readback without entering the production app path.

Done Checks:

- Target is opt-in and excluded from production app/package builds.
- Apple remote validation can build the target on macOS and skip iOS device
  execution unless a signed runner exists.
- Smoke tests cover tiny texture/render-pass/copy-readback behavior where a
  headless Metal device is available.
- No OpenGL or Apple production package behavior changes.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos -Targets pp_renderer_metal_lab_tests
```

### UI-001 - Define UI Thread-Affinity And Mutation Contract

Status: Done
Score: +2 hardening and future backend readiness
Debt: `DEBT-0003`, `DEBT-0063`
Scope: `src/ui_core/*`, `src/app_core/app_thread.*`,
`tests/ui_core/*`, `tests/app_core/app_thread_tests.cpp`,
`tools/pano_cli/*`

Goal:

Make UI safety explicit before broad retained UI migration: define which
operations are UI-thread-only, which may be posted from render/worker threads,
how checked node handles invalidate, how callbacks disconnect, and how layout
reload/destroy-during-callback mutations are sequenced.

Done Checks:

- `pp_ui_core` exposes a documented thread-affinity/mutation contract.
- Tests cover checked-handle invalidation, scoped callback disconnect,
  destroy-during-callback, add/remove-during-dispatch, capture release, and
  layout reload clear.
- `pp_app_core` app-thread plans reject or route unsafe cross-thread UI
  mutations.
- `pano_cli plan-app-thread` exposes at least one unsafe-dispatch rejection
  smoke.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_ui_core_(node_lifetime|overlay_lifetime)|pp_app_core_app_thread|pano_cli_plan_app_thread" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-14 | UI-001 | +2 hardening and future backend readiness | `ctest --preset desktop-fast --build-config Debug -R "pp_ui_core_(node_lifetime|overlay_lifetime)|pp_app_core_app_thread|pano_cli_plan_app_thread" --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli` | `f78fc307` |

### UI-002 - Migrate Next Retained Panel Family To Checked UI Handles

Status: Done
Score: +2 legacy adapter retirement
Debt: `DEBT-0063`
Depends: `UI-001`
Scope: `src/node_panel_layer.h/.cpp`, `src/node_combobox.cpp`,
`src/node_dialog_open.cpp`, `src/node_dialog_browse.cpp`,
`src/legacy_ui_overlay_services.*`,
focused tests.

Goal:

Move one more retained UI family from raw `Node*`/manual destroy/callback
ownership to the checked-handle and scoped-callback model, preserving current
behavior while proving the migration pattern is repeatable.

Current Verified Slices:

- `src/node_panel_layer.h/.cpp`: shared ownership for current-layer/m_layers and stale
  callback/mutation guards; popup outside-click close preserved via
  `close_legacy_popup_panel`.
- `src/node_combobox.cpp`: popup lifecycle uses `open_legacy_overlay_node_with_handle(...)`
  and `close_legacy_overlay_node(...)`; old attach/close popup path removed.
- `src/node_dialog_open.cpp` and `src/node_dialog_browse.cpp`: delete-confirmation overlay
  flow stays on handle-based open/confirm/cancel close and removes
  `attach_legacy_overlay_node_to_root` fallback.
- `src/node_panel_stroke.h/.cpp`: preset/brush/dual/pattern popup entry points now capture
  checked popup/tick overlay handles, close stale handles before reopen, and
  route popup close through stored handles instead of the old bool-only helper.
- `src/legacy_quick_ui_services.cpp`: quick brush/color popup lifecycle opens
  popup/tick overlays through checked handles and routes close through
  handle-based teardown instead of raw attach+destroy.

Done Checks:

- Chosen family no longer uses open-coded root insertion, close callback
  lifetime, outside-click release, or stale raw child pointers where a checked
  handle/service exists.
- Mutation-during-callback and close/release tests cover the chosen family.
- `DEBT-0063` names the migrated family and the remaining families.

Validation:

```powershell
cmake --build --preset windows-msvc-default --config Debug --target panopainter_app pp_ui_core_node_lifetime_tests pp_ui_core_overlay_lifetime_tests
.\out\build\windows-msvc-default\tests\Debug\pp_ui_core_node_lifetime_tests.exe
.\out\build\windows-msvc-default\tests\Debug\pp_ui_core_overlay_lifetime_tests.exe
cmake --build --preset windows-msvc-default --config Debug --target panopainter_app
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-15 | UI-002 | +2 legacy adapter retirement | `ctest --preset desktop-fast --build-config Debug -R "pp_ui_core_(node_lifetime|overlay_lifetime)|pp_app_core_quick_ui" --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target panopainter_app pp_ui_core_node_lifetime_tests pp_ui_core_overlay_lifetime_tests`; `.\out\build\windows-msvc-default\tests\Debug\pp_ui_core_node_lifetime_tests.exe`; `.\out\build\windows-msvc-default\tests\Debug\pp_ui_core_overlay_lifetime_tests.exe` | `68617e8b` |

### APP-001 - Add Render/UI Queue Race Regression Gate

Status: Done
Score: +2 test and automation coverage
Debt: `DEBT-0003`, `DEBT-0017`
Depends: `UI-001`
Scope: `src/foundation/*`, `src/app_core/app_thread.*`,
`tests/foundation/task_queue_tests.cpp`, `tests/foundation/task_queue_stress_tests.cpp`,
`tests/app_core/app_thread_tests.cpp`, `tests/app_core/app_thread_stress_tests.cpp`,
`tools/pano_cli/*`

Goal:

Add deterministic stress/regression coverage for render/UI task queues before
threading behavior is relied on by backend labs or safer UI adapters.

Done Checks:

- Tests cover nested dispatch, unique task replacement, shutdown drain,
  cancellation/no-op after stop, exception-free error reporting, and worker to
  UI/render handoff ordering.
- `stress` preset includes the longer queue scenario while `desktop-fast`
  keeps a small deterministic subset.
- `pano_cli plan-app-thread` exposes queue scenarios as JSON smoke commands.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_foundation_task_queue|pp_app_core_app_thread|pano_cli_plan_app_thread" --output-on-failure
ctest --preset stress --build-config Debug -R "app_thread|task_queue" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-14 | APP-001 | +2 test and automation coverage | `ctest --preset desktop-fast --build-config Debug -R "pp_foundation_task_queue|pp_app_core_app_thread|pano_cli_plan_app_thread" --output-on-failure`; `ctest --preset stress --build-config Debug -R "app_thread|task_queue" --output-on-failure` | `f78fc307` |

### PLT-010 - Gate Backend And UI Work On Platform Package Readiness

Status: Done
Score: +2 platform and package parity
Debt: `DEBT-0004`, `DEBT-0011`, `DEBT-0059`
Scope: `scripts/automation/package-smoke.*`,
`scripts/automation/platform-build.*`, root `CMakeLists.txt`,
`docs/modernization/build-inventory.md`

Goal:

Make backend/UI modernization platform-sensitive by requiring named package or
package-readiness gates for Windows AppX, Apple bundles, Android standard,
Quest, Focus/Wave, Linux, and WebGL before backend lab or retained UI migration
work can claim broad readiness.

Done Checks:

- Package-readiness output identifies which backend/UI-sensitive platforms are
  validated, blocked, or compile-only.
- Root CMake exposes a named target or documented command for each required
  platform gate.
- Apple/iOS signed-bundle gaps and Windows AppX gaps remain debt-tracked until
  real package builds exist.
- `docs/modernization/build-inventory.md` lists the commands as the current
  gate for backend/UI roadmap work.

Validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly
ctest --preset desktop-fast --build-config Debug -R "panopainter_package_smoke_readiness_self_test|panopainter_platform_build_target_matrix_self_test" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-14 | PLT-010 | +2 platform and package parity | `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly`; `ctest --preset desktop-fast --build-config Debug -R "panopainter_package_smoke_readiness_self_test\|panopainter_platform_build_target_matrix_self_test" --output-on-failure` | `f78fc307` |

### STR-016 - Extract Draw Merge Layer Composite Execution

Status: Blocked
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`
Blocked By: Helper API shape mismatch in the current per-layer composite extraction attempt

Goal:

Move the large per-layer `draw_merge()` composite execution block into a
retained helper so the callsite only supplies branch selection and concrete GL
objects.

Done Checks:

- `Canvas::draw_merge()` no longer builds the per-layer composite execution
  inline.
- Regression coverage proves the extracted helper preserves per-branch order.
- `docs/modernization/debt.md` records the reduced draw-merge composite
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-016 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `b9ed78e1` |

### STR-017 - Extract Draw Merge Temporary Erase Composite Branch

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining temporary erase branch inside `Canvas::draw_merge()` into a
retained helper so the callsite keeps only branch selection and concrete GL
object wiring.

Done Checks:

- `Canvas::draw_merge()` no longer builds the temporary erase composite inline.
- Regression coverage proves the extracted helper preserves erase-branch order.
- `docs/modernization/debt.md` records the reduced draw-merge erase surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-017 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `e8fdd96d` |

### STR-018 - Extract Draw Merge Temporary Paint Composite Branch

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining temporary paint branch inside `Canvas::draw_merge()` into a
retained helper so the callsite keeps only branch selection and concrete GL
object wiring.

Done Checks:

- `Canvas::draw_merge()` no longer builds the temporary paint composite inline.
- Regression coverage proves the extracted helper preserves paint-branch order.
- `docs/modernization/debt.md` records the reduced draw-merge paint surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-018 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `037be1a7` |

### STR-031 - Extract Draw Merge Temporary Paint Branch

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the inline temporary paint branch in `Canvas::draw_merge()` into a
retained helper so the callsite keeps only branch selection and concrete GL
object wiring.

Done Checks:

- `Canvas::draw_merge()` no longer builds the temporary paint branch inline.
- Regression coverage proves the extracted helper preserves paint-branch order.
- `docs/modernization/debt.md` records the reduced draw-merge temporary-paint
  surface.

Closeout: `91d4da09`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-031 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure` | `91d4da09` |
| 2026-06-13 | STR-032 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor" --output-on-failure`; `& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_compositor_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | `83a46770` |

### STR-032 - Extract Remaining Draw Merge Branch Orchestration

Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.*`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining inline `Canvas::draw_merge()` branch orchestration into
retained helpers so the merge path keeps only concrete framebuffer, sampler,
and texture wiring. Preserve per-plane order, temporary-stroke behavior, and
final merge composition.

Done Checks:

- `Canvas::draw_merge()` no longer contains the remaining large inline branch
  orchestration for temporary-stroke or blend/final-plane composition.
- Regression coverage proves the extracted helper preserves ordering and
  branch behavior.
- `docs/modernization/debt.md` records the reduced draw-merge callback surface.

Closeout: `83a46770`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_compositor_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### STR-033 - Extract Draw Merge Final Plane Composite

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.*`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the final-plane composite call in `Canvas::draw_merge()` behind a
retained helper so the merge path keeps only final-branch selection and
concrete GL object wiring.

Done Checks:

- `Canvas::draw_merge()` no longer owns the final-plane composite call inline.
- Regression coverage proves the final-plane helper preserves ordering and
  checkerboard/blend behavior.
- `docs/modernization/debt.md` records the reduced final-plane composite
  surface.

Closeout: `666c4dd3`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_compositor_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

### Completed Task Log

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-033 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor" --output-on-failure`; `& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_compositor_tests.vcxproj /p:Configuration=Debug /p:Platform=x64` | `666c4dd3` |
| 2026-06-13 | STR-034 | +1 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure` | `3acb2da3` |

### STR-034 - Extract Stroke Draw Samples Request Assembly

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `Canvas::stroke_draw_samples()` request assembly into a
retained helper so the callsite keeps only destination dispatch and result
handling.

Done Checks:

- `Canvas::stroke_draw_samples()` no longer owns the face-sample request
  assembly inline.
- Regression coverage proves the request helper preserves destination-copy
  behavior and brush upload/draw ordering.
- `docs/modernization/debt.md` records the reduced stroke-sample request
  surface.

Closeout: `3acb2da3`

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-035 - Extract Stroke Draw Samples Callback Body

Status: Done
Score: +1 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_stroke_execution_services.h`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining `Canvas::stroke_draw_samples()` callback body into a
retained helper so the callsite keeps only request dispatch and dirty-bounds
return handling.

Closeout: `3c3405d7`

Done Checks:

- `Canvas::stroke_draw_samples()` no longer owns the face-sample callback body
  inline.
- Regression coverage proves the callback helper preserves destination-copy
  behavior and brush upload/draw ordering.
- `docs/modernization/debt.md` records the reduced stroke-sample callback
  surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_stroke_execution" --output-on-failure
```

### STR-012 - Extract Preview Final Composite Orchestration

Status: Done
Score: no score movement
Debt: `DEBT-0036`
Scope: `src/node_stroke_preview.cpp`, `tests/paint_renderer/compositor_tests.cpp`

Goal:

Extract the remaining preview final-composite and copy-back glue so the
preview path keeps only concrete texture binding and pass ordering.

Done Checks:

- `NodeStrokePreview::draw_stroke_immediate()` no longer owns the final
  composite and preview copy-back orchestration inline.
- Regression coverage proves final composite and preview copy order.
- `DEBT-0036` records the reduced preview final-pass surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-012 | no score movement | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | `718c9224` |

### PLT-003 - Align Platform Build Matrix With Phase 6 Targets

Status: Done
Score: +3 platform alignment and package parity
Debt: `DEBT-0009`, `DEBT-0011`, `DEBT-0059`
Scope: `scripts/automation/platform-build.ps1`, `scripts/automation/platform-build.sh`,
`scripts/dev/check_platform_build_targets.py`, `scripts/dev/check_package_smoke_readiness.py`,
`tests/CMakeLists.txt`, `docs/modernization/roadmap.md`

Goal:

Keep the phase-6 platform matrix explicit and self-checking by aligning the
platform-build and package-smoke wrapper defaults, their self-tests, and the
roadmap/debt notes with the current supported preset set. The wrappers should
stay the source of truth for named validation commands.

Done Checks:

- `panopainter_platform_build_target_matrix_self_test` and
  `panopainter_package_smoke_readiness_self_test` cover the current platform
  preset/package matrix.
- The roadmap and debt log mention the current platform-build/package-smoke
  coverage and remaining platform shell gaps.
- The wrapper defaults stay synchronized with the self-tests.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "panopainter_platform_build_target_matrix_self_test|panopainter_package_smoke_readiness_self_test" --output-on-failure
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | PLT-003 | +3 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R "panopainter_platform_build_target_matrix_self_test|panopainter_package_smoke_readiness_self_test" --output-on-failure` | `7e09298e` |

### PLT-004 - Reduce Remaining Platform Legacy Adapter Tail

Status: Done
Score: +3 platform alignment and package parity
Debt: `DEBT-0017`, `DEBT-0052`, `DEBT-0053`
Scope: `src/platform_legacy/legacy_platform_services.*`, `src/platform_api/*`,
`src/platform_apple/*`, `src/platform_web/*`, `tests/platform_api/platform_services_tests.cpp`

Goal:

Trim one more concrete platform policy seam out of the catch-all legacy
platform adapter without destabilizing platform package or build validation.
Keep the work small, measurable, and centered on a policy that already has
test coverage in `pp_platform_api`.

Done Checks:

- One remaining platform policy surface moves out of `legacy_platform_services`
  or is explicitly isolated behind a narrower adapter.
- The affected platform API tests cover the updated policy route.
- The debt log records the reduced legacy adapter surface.

Validation:

```powershell
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_platform_api_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
& .\out\build\windows-msvc-default\tests\Debug\pp_platform_api_tests.exe
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | PLT-004 | +3 platform alignment and package parity | `& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_platform_api_tests.vcxproj /p:Configuration=Debug /p:Platform=x64`<br>`& .\out\build\windows-msvc-default\tests\Debug\pp_platform_api_tests.exe` | `6ba98ee` |

### PLT-005 - Split Linux FPS Title Reporting From Legacy Platform Adapter

Status: Done
Score: +1 platform alignment and package parity
Debt: `DEBT-0017`, `DEBT-0052`
Scope: `src/platform_legacy/legacy_platform_services.cpp`,
`src/platform_linux/*`, `tests/platform_api_tests.cpp` if coverage is needed

Goal:

Move Linux rendered-frame FPS title updates out of the catch-all legacy
platform adapter into a named Linux platform service boundary. Preserve the
current Linux title update behavior and keep non-Linux behavior unchanged.

Done Checks:

- `src/platform_legacy/legacy_platform_services.cpp` no longer owns the Linux
  FPS-title update branch.
- Linux rendered-frame reporting still updates the title as before.
- The debt log records the reduced Linux platform tail.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | PLT-005 | +1 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests` | `1d50bcc7` |

### PLT-006 - Split Mac Cursor Visibility From Legacy Platform Adapter

Status: Done
Score: +1 platform alignment and package parity
Debt: `DEBT-0015`, `DEBT-0017`
Scope: `src/platform_legacy/legacy_platform_services.cpp`,
`src/platform_apple/apple_platform_services.*`

Goal:

Move macOS cursor visibility handling out of the catch-all legacy platform
adapter into the Apple platform service boundary. Preserve cursor visibility
behavior and keep non-macOS behavior unchanged.

Done Checks:

- `src/platform_legacy/legacy_platform_services.cpp` no longer owns the macOS
  cursor visibility branch.
- macOS cursor visibility still dispatches through the Apple service path.
- The debt log records the reduced macOS platform tail.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | PLT-006 | +1 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests` | `fc4f5e40` |

### PLT-007 - Split Mac UI State Saving From Legacy Platform Adapter

Status: Done
Score: +1 platform alignment and package parity
Debt: `DEBT-0017`, `DEBT-0052`
Scope: `src/platform_legacy/legacy_platform_services.cpp`,
`src/platform_apple/apple_platform_services.*`

Goal:

Move macOS UI-state saving out of the catch-all legacy platform adapter into
the Apple platform service boundary. Preserve current macOS UI-state saving
behavior and keep non-macOS behavior unchanged.

Done Checks:

- `src/platform_legacy/legacy_platform_services.cpp` no longer owns the macOS
  UI-state save branch.
- macOS UI-state saving still dispatches through the Apple service path.
- The debt log records the reduced macOS platform tail.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | PLT-007 | +1 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests` | `623fdc67` |

### PLT-008 - Split Apple Clipboard Dispatch From Legacy Platform Adapter

Status: Done
Score: +1 platform alignment and package parity
Debt: `DEBT-0016`, `DEBT-0017`, `DEBT-0051`
Scope: `src/platform_legacy/legacy_platform_services.cpp`,
`src/platform_apple/apple_platform_services.*`

Goal:

Move Apple clipboard get/set dispatch out of the catch-all legacy platform
adapter into the Apple platform service boundary. Preserve clipboard behavior
and keep non-Apple behavior unchanged.

Done Checks:

- `src/platform_legacy/legacy_platform_services.cpp` no longer owns the Apple
  clipboard get/set branches.
- Apple clipboard get/set still dispatches through the Apple service path.
- The debt log records the reduced Apple platform tail.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | PLT-009 | +1 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests` | `3d999225` |
| 2026-06-13 | PLT-008 | +1 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests` | `2ec48965` |

### PLT-009 - Split Apple Clipboard Helpers From Legacy Platform Adapter

Status: Done
Score: +1 platform alignment and package parity
Debt: `DEBT-0016`, `DEBT-0017`, `DEBT-0051`
Scope: `src/platform_legacy/legacy_platform_services.cpp`,
`src/platform_apple/apple_platform_services.*`

Goal:

Move Apple clipboard helper methods out of the catch-all legacy platform
adapter into the Apple platform service boundary. Preserve clipboard behavior
and keep non-Apple behavior unchanged.

Done Checks:

- `src/platform_legacy/legacy_platform_services.cpp` no longer owns the Apple
  clipboard helper branch.
- Apple clipboard get/set still dispatches through the Apple service path.
- The debt log records the reduced Apple platform tail.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests
```

### STR-010 - Extract Remaining Draw Merge Composite Orchestration

Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
Scope: `src/canvas.cpp`, `src/legacy_canvas_draw_merge_services.*`,
`tests/paint_renderer/compositor_tests.cpp`

Goal:

Move the remaining inline `Canvas::draw_merge()` branch orchestration into
retained helpers so the merge path keeps only concrete framebuffer, sampler,
and texture wiring. Preserve per-plane order, temporary-stroke behavior, and
final merge composition.

Done Checks:

- `Canvas::draw_merge()` no longer contains the remaining large inline branch
  orchestration for temporary-stroke or blend/final-plane composition.
- Regression coverage proves the extracted helper preserves ordering and
  branch behavior.
- `docs/modernization/debt.md` records the reduced draw-merge callback surface.

Validation:

```powershell
ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor" --output-on-failure
& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_paint_renderer_compositor_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
```

Completed Task Log:

| Date | Task | Score | Validation | Commit |
| --- | --- | ---: | --- | --- |
| 2026-06-13 | STR-010 | +2 renderer boundary and OpenGL parity | `ctest --preset desktop-fast --build-config Debug -R "pp_paint_renderer_compositor|pp_paint_renderer_stroke_execution" --output-on-failure`; `& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\PanoPainter.vcxproj /p:Configuration=Debug /p:Platform=x64` | `42bc1866` |
