From a76560e3df17ac5f1e112ac12b295e72579c9a12 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 16 Jun 2026 06:35:59 +0200 Subject: [PATCH] Advance app runtime ownership and modernization docs --- AGENTS.md | 16 +- cmake/PanoPainterSources.cmake | 1 + docs/modernization/capability-map.md | 4 +- docs/modernization/debt.md | 4 + docs/modernization/director-workflow.md | 86 +- docs/modernization/roadmap.md | 3806 ++------------- docs/modernization/tasks-done.md | 5007 ++++++++++++++++++++ docs/modernization/tasks.md | 5650 +++-------------------- src/app.cpp | 245 +- src/app.h | 169 +- src/app_cloud.cpp | 4 +- src/app_runtime.cpp | 258 ++ src/app_runtime.h | 216 + src/legacy_cloud_services.cpp | 102 +- src/legacy_cloud_services.h | 4 + src/legacy_ui_overlay_services.cpp | 19 + src/legacy_ui_overlay_services.h | 7 + src/main.cpp | 8 +- src/node.cpp | 2 +- src/node_dialog_cloud.cpp | 20 +- src/node_dialog_cloud.h | 7 +- src/node_panel_stroke.cpp | 12 +- src/node_popup_menu.cpp | 30 +- test.cpp | 7 + 24 files changed, 6675 insertions(+), 9009 deletions(-) create mode 100644 docs/modernization/tasks-done.md create mode 100644 src/app_runtime.cpp create mode 100644 src/app_runtime.h create mode 100644 test.cpp diff --git a/AGENTS.md b/AGENTS.md index 270aca1f..a30d4f73 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,11 +30,12 @@ Read these first: - Commit and push each verified successful progress slice. - Prefer larger coherent slices over tiny checkpoints, but keep docs/debt updated with each slice. -- After a verified slice is committed and pushed, reset conversation context - before starting the next slice when practical, especially if the thread is - approaching automatic compaction. Record all needed resume state in committed - code/docs first so the next thread can restart from `AGENTS.md`, roadmap/debt, - and git history instead of relying on chat transcript context. +- Treat automatic compaction as a failure mode to avoid. Keep active context + small, commit and push before the thread grows large, and reset conversation + context between verified slices when practical instead of carrying excess + history forward. Record all needed resume state in committed code/docs first + so the next thread can restart from `AGENTS.md`, roadmap/debt, and git + history instead of relying on chat transcript context. - Do not revert user changes. Unrelated untracked notes, such as `docs/human-review-notes.md`, should be left alone unless explicitly requested. - Use CMake as the source of truth. Legacy Visual Studio project files are not the @@ -42,8 +43,9 @@ Read these first: - Use `apply_patch` for manual source/doc edits. - For delegated work, follow `docs/modernization/director-workflow.md`: the coordinator keeps integration locally, assigns direct worker tasks, uses - `gpt-5.3-codex-spark` workers by default, and gives them the exact project - context needed so they do not spend tokens re-reading repo docs. + `gpt-5.4-mini` workers by default, and gives them a minimal task packet with + only the build, test, and code-exploration context needed so they do not + spend tokens re-reading repo docs. ## Build And Test diff --git a/cmake/PanoPainterSources.cmake b/cmake/PanoPainterSources.cmake index c201817a..63269e68 100644 --- a/cmake/PanoPainterSources.cmake +++ b/cmake/PanoPainterSources.cmake @@ -86,6 +86,7 @@ set(PP_PLATFORM_LINUX_SOURCES set(PP_PANOPAINTER_APP_SOURCES src/app.cpp + src/app_runtime.cpp src/app_cloud.cpp src/app_commands.cpp src/app_dialogs.cpp diff --git a/docs/modernization/capability-map.md b/docs/modernization/capability-map.md index 7cf93e90..39ca1ac5 100644 --- a/docs/modernization/capability-map.md +++ b/docs/modernization/capability-map.md @@ -1,7 +1,7 @@ # PanoPainter Capability Map Status: live -Last updated: 2026-06-05 +Last updated: 2026-06-16 This map is the preservation checklist for the modernization. When a component is extracted, update the relevant rows with the owning component, test label, @@ -59,6 +59,7 @@ and validation command. | Yoga layout | `Node` | `pp_ui_core` | Deterministic geometry fixtures | | Generic controls | `NodeButton`, sliders, text, images | `pp_ui_core` | Event dispatch, layout, ownership-handle, callback-disconnect, and destroy-during-callback tests | | PanoPainter panels/dialogs | `NodePanel*`, `NodeDialog*` | `pp_panopainter_ui`, `pp_ui_core` | UI automation scripts, command-dispatch view models, pure overlay lifetime tests, retained overlay-adapter build coverage, retained popup/dialog lifetime tests | +| UI ownership and thread affinity | `Node`, `LayoutManager`, `App` UI queue, retained callbacks | `pp_ui_core`, app runtime service, `pp_panopainter_ui` | Checked-handle dispatch, scoped callback disconnect, destroy-during-callback, close-during-dispatch, and UI-thread post/drain/shutdown coverage | | Canvas viewport UI | `NodeCanvas` | `pp_panopainter_ui`, `pp_paint_renderer` | Input-to-command automation | | Settings UI | `Settings`, `NodeSettings` | `pp_assets`, `pp_panopainter_ui` | Round-trip settings | @@ -67,6 +68,7 @@ and validation command. | Capability | Current Area | Target Owner | Required Tests | | --- | --- | --- | --- | | Mouse/keyboard/touch/gestures/cursor | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*`, app | Cursor visibility decision tests, platform service dispatch tests, synthetic event playback | +| Render/UI task dispatch and worker shutdown | `App`, `Canvas`, retained worker threads, platform entrypoints | app runtime service, `pp_foundation`, `pp_platform_*` | Render/UI queue order, same-thread dispatch, cancellation, shutdown drain, and no-detached-worker ownership coverage | | Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback | | Clipboard/file picker/share/display | `App` platform methods | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Clipboard read/write, share saved-path, picked-path, and display-file decision tests, platform service display/share/picker dispatch tests, platform smoke or mocked service | | Virtual keyboard | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Keyboard visibility decision tests, platform service dispatch tests, platform smoke | diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index fe039983..5933805c 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -335,6 +335,10 @@ agent or engineer to remove them without reconstructing context from chat. routes the stroke preset/brush/dual-brush/pattern popup open-close flow through checked overlay handles and explicit partial-open cleanup; migration is still pending in remaining panel families. +- 2026-06-16: `DEBT-0063` was narrowed again. `src/node_panel_stroke.cpp` now + routes stroke popup close/reset through a shared overlay helper, and + `src/node_popup_menu.cpp` now keeps the popup alive through selection dispatch + with scoped ownership instead of raw `this` callbacks. - 2026-06-14: `DEBT-0063` was narrowed again. `src/node_panel_brush.cpp` now routes brush popup-menu open-close flow through checked overlay handles and handle-based close on selection. diff --git a/docs/modernization/director-workflow.md b/docs/modernization/director-workflow.md index 4281f375..1d9e323f 100644 --- a/docs/modernization/director-workflow.md +++ b/docs/modernization/director-workflow.md @@ -1,7 +1,7 @@ # Modernization Coordinator Workflow Status: live -Last updated: 2026-06-14 +Last updated: 2026-06-16 Use this workflow when the user explicitly asks for subagents, delegation, a coordinator, or parallel agent work. Do not spawn subagents just because a task @@ -16,10 +16,16 @@ layer. - Save main-thread tokens by keeping implementation and focused lookup out of the coordinator context. +- Treat thread compaction as wasted budget. Prefer small active context, + committed resume state, and fresh follow-on threads over carrying long stale + history. - Keep each implementation slice measurable, validated, committed, and pushed. - Avoid merge conflicts by giving every worker a disjoint task and file scope. -- Keep workers stateless between tasks: when a worker finishes, integrate or - reject the result, then clear that worker context before the next assignment. +- Do not leave workers idle. Either give a worker another coherent follow-on + task while its context is still useful, or close it promptly. +- Reuse worker context only for closely related follow-on tasks in the same + local area; otherwise close the worker and start a fresh one with a new + minimal packet. - Keep prompts dense with the exact project/task context workers need so they do not spend tokens re-reading repo docs or re-parsing broad areas. - Keep communication terse: no fillers, no cheerleading, no narrative padding. @@ -40,7 +46,8 @@ The coordinator is the main agent in the user thread. The coordinator owns: - running final validation - updating docs/debt/tasks - committing and pushing the verified slice -- resetting worker context after each completed or rejected task +- either assigning coherent follow-on work to an active worker or closing that + worker once its useful context window is over The coordinator should keep local work minimal. It may do a quick blocking check before delegation, such as reading task rows, checking git status, or @@ -49,13 +56,16 @@ code or test work, delegate it directly to a worker whenever the scope can be made clear. The coordinator must front-load context. Workers should not be told to "read -the roadmap" or "inspect the repo" unless that is the task. The coordinator is -responsible for extracting and passing: +the roadmap", "read AGENTS.md", or "inspect the repo" unless that is the task. +The coordinator is responsible for extracting and passing: - task ids and done checks - debt ids and removal conditions that matter - exact write scope and allowed read scope - required validation commands +- the specific build/test preset or target names the worker may need +- the exact code-exploration tools to use for the slice, such as `rg` or the + compiler-aware `clangd_nav.py` helper - relevant file paths, code references, and current behavior notes - any repo rules or user constraints that materially affect the task @@ -68,6 +78,11 @@ Workers do not own repo discovery. They start from the coordinator-provided context packet and stay inside the assigned scope unless they hit a blocker that requires a narrow follow-up question. +Workers may be kept alive for more than one assignment only when the next task +is coherent with the current one: same subsystem, overlapping read scope, +similar validation path, and no avoidable context rebuild. Do not keep workers +alive just because a slot is available. + Every worker and explorer must be told: - this repository may have other agents working in parallel @@ -80,13 +95,13 @@ Every worker and explorer must be told: | Work Type | Model | Reasoning Effort | Use | | --- | --- | --- | --- | -| Coordinator orchestration and integration | inherited main-thread model or `gpt-5.4-mini` | `low` or `medium` | Scope selection, task routing, conflict checks, validation, docs/debt updates, commits, pushes. | -| Direct worker coding task | `gpt-5.3-codex-spark` | `medium` | Bounded implementation in known files with coordinator-supplied context. | -| Direct worker lookup or inventory | `gpt-5.3-codex-spark` | `low` or `medium` | `rg` inventory, file ownership map, simple grep-based answers. | -| Mechanical docs cleanup | `gpt-5.3-codex-spark` | `low` | Formatting, table updates, command normalization. | +| Coordinator orchestration and integration | `gpt-5.4` | `low` or `medium` | Scope selection, task routing, conflict checks, validation, docs/debt updates, commits, pushes, and worker packet preparation. | +| Direct worker coding task | `gpt-5.4-mini` | `medium` | Bounded implementation in known files with coordinator-supplied context. | +| Direct worker lookup or inventory | `gpt-5.4-mini` | `low` or `medium` | `rg` inventory, file ownership map, simple grep-based answers. | +| Mechanical docs cleanup | `gpt-5.4-mini` | `low` | Formatting, table updates, command normalization. | | Coordinator-only escalation | inherited higher model only when explicitly justified | inherited | Resolve architecture ambiguity, conflict integration, or task decomposition failures without adding a captain layer. | -Workers default to `gpt-5.3-codex-spark`. If a task looks too broad or risky +Workers default to `gpt-5.4-mini`. If a task looks too broad or risky for that model, the coordinator should decompose it further or keep the narrow integration step locally instead of inserting an extra management tier. @@ -96,33 +111,46 @@ integration step locally instead of inserting an extra management tier. ids, validation commands, and only the necessary excerpts. - Use `fork_context=true` only when prior conversation details are essential and not already captured in the worker prompt. +- Do not let the coordinator thread drift toward compaction. Once a verified + slice is committed and pushed, prefer a fresh thread for the next slice if + the remaining context is no longer tight. - Do not paste large logs into prompts. Point workers at log paths and ask for the smallest relevant excerpt. -- Do not ask workers to broadly read `AGENTS.md`, the roadmap, or the debt log. - Summarize the exact rules and rows they need. +- Do not ask workers to broadly read `AGENTS.md`, the roadmap, the debt log, or + other repo-wide docs. Summarize the exact rules and rows they need. +- Default worker context to a minimal operating packet: task id, assigned file + scope, build command, test command, and code-exploration command hints. - Keep worker prompts compact but complete. Shorter is good only if it still removes the need for worker-side repo rediscovery. - Ask for compact final reports: changed files, result, validation, blockers, next recommendation. -- Close completed workers after their results are integrated or rejected. +- Keep active workers busy with another coherent task when that is cheaper than + restarting context; otherwise close them immediately after integration. +- Close workers that are done, blocked, or no longer have a strong context + advantage so they do not accumulate and saturate worker slots. - Prefer the smallest number of concurrent workers that keeps disjoint work moving. - Use rolling integration: wait for whichever worker finishes first, process the - result, then launch the next disjoint worker with a fresh context window. + result, then either reuse that worker for the next coherent slice or close it + before launching a fresh worker for unrelated work. +- Prefer committed repo state over chat history as the handoff mechanism between + slices so worker and coordinator prompts stay short. ## Delegation Flow 1. Coordinator picks one or more `Ready` tasks from `docs/modernization/tasks.md` with disjoint write scopes. -2. Coordinator splits each task into direct worker-sized units. +2. Coordinator splits each task into direct worker-sized units, grouping + coherent follow-on work when one worker can finish it efficiently without + broadening scope. 3. Coordinator prepares a context packet for each worker with the exact task requirements, file scope, validation commands, and relevant project details. -4. Coordinator assigns the task directly to a `gpt-5.3-codex-spark` worker or +4. Coordinator assigns the task directly to a `gpt-5.4-mini` worker or explorer. 5. Worker returns changed files, validation, blockers, and any narrow integration notes. -6. Coordinator reviews for scope conflicts, integrates the result, and clears - that worker context before assigning the next task. +6. Coordinator reviews for scope conflicts, integrates the result, and decides + whether to give that same worker another coherent task or close it. 7. Coordinator runs the listed validation command or the quiet checkpoint wrapper for each integrated slice. 8. Coordinator updates `tasks.md`, `debt.md`, and `roadmap.md` if task state or @@ -132,7 +160,7 @@ integration step locally instead of inserting an extra management tier. ## Coordinator Prompt Template For A Worker ```text -You are a `gpt-5.3-codex-spark` worker on PanoPainter. Other agents may be +You are a `gpt-5.4-mini` worker on PanoPainter. Other agents may be editing nearby files; do not revert unrelated changes. Task source: docs/modernization/tasks.md task(s) . @@ -142,15 +170,24 @@ Debt ids: . Write scope: . Read scope: . Validation: . +Code exploration: . Repo constraints you must follow: - -Current context you should rely on instead of broad repo/doc review: +Minimal context you should rely on instead of broad repo/doc review: - - - +Do not read repo-wide docs unless this task explicitly requires it. Use only +the supplied context, the listed file scope, and the build/test/code-exploration +commands above. + +If the coordinator gives you a second task, accept it only when it is coherent +with the current scope and does not require broad repo rediscovery. Otherwise +say that a fresh worker should be used. + Make the smallest behavior-preserving change that satisfies the done checks. Do not spend tokens on broad document review or inventory outside the assigned scope unless the task explicitly requires it. If the task is larger than @@ -187,6 +224,11 @@ Return only: - Focused validation for the task passed or the failure is documented. - `docs/modernization/debt.md` changed when debt was narrowed or closed. - `docs/modernization/tasks.md` score changed only for `Done` tasks. -- Each worker result was integrated before that worker context was reused. +- Each worker result was integrated before that worker was reused. +- Reused workers only handled coherent follow-on tasks with a real context + advantage. +- Done or blocked workers were closed instead of being left idle. +- The coordinator did not carry unnecessary stale history when a fresh thread + would have been cheaper than compaction. - The commit contains one coherent slice. - The branch was pushed. diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 1837d22b..0e0a6741 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1,3588 +1,306 @@ # PanoPainter Modernization Roadmap Status: live -Last updated: 2026-06-15 +Last updated: 2026-06-16 -This is the living roadmap for modernizing PanoPainter into independently -testable C++23 components while retaining all existing functionality. Keep this -file current as phases are implemented. Do not let shortcuts, skipped platforms, -or temporary adapters live only in chat history. +This roadmap is now architecture-first. +The active execution queue lives in `docs/modernization/tasks.md`. +Completed and superseded task history moved to +`docs/modernization/tasks-done.md`. +The debt log remains `docs/modernization/debt.md`. -## How To Keep This Roadmap Live +## Objective -- Update the phase status before and after each implementation pass. -- Use `docs/modernization/tasks.md` for measurable execution tasks and - percentage/progress claims. The phase prose explains direction; the task - tracker is the source of truth for score changes. -- When a shortcut is introduced, add it to the debt log section in this file - until `docs/modernization/debt.md` exists, then move debt entries there. -- When a major architectural decision is made, add an ADR under `docs/adr/` - once that directory exists. -- Every phase must preserve old behavior unless the roadmap explicitly says - otherwise. -- Each phase must leave the repo in a buildable and testable state. -- Do not add stubs without a debt entry, validation command, and removal - condition. +Turn PanoPainter into a thin composition-root application over separable C++23 +components while preserving current behavior. -## Locked Decisions +The target end state is not "more planners around the same legacy shell". +The next phase is measured by ownership transfer in the live app, not by +planner count, CLI breadth, or test count. +The target end state is: -- Graphics path: keep OpenGL working first; add Vulkan and Metal after the - renderer boundary exists. -- Required platforms at phase gates: Windows desktop/AppX, macOS, iOS, - Android standard, Quest, Focus/Wave, Linux, and WebGL. -- Dependency policy: use vcpkg where reliable; keep SDK, patched, or - vendor-only dependencies with documented reasons. -- Test stack: Catch2, golden/approval tests, and fuzz/property tests where - useful. -- Automation: local reproducible matrix first; hosted CI can be added later. -- Documentation: ADRs, debt log, and this living roadmap. -- "vkpkg" in older notes means `vcpkg`. -- Target C++ standard: C++23. -- Initial Windows CMake generator target: Visual Studio 2026 when available. +- real component ownership +- real platform boundaries +- real renderer boundaries +- a thin `panopainter_app` +- legacy containment targets either deleted or reduced to trivial, + debt-tracked adapters -## Phase Status +## What This Roadmap Covers -| Phase | Name | Status | Gate | -| --- | --- | --- | --- | -| 0 | Inventory, Safety Rails, And Memory | Complete | No behavior changes; old builds still work | -| 1 | Unified CMake Skeleton | In progress | Root CMake builds the Windows app and owns the source list | -| 2 | Toolchain, Diagnostics, And Dependencies | In progress | Strict desktop library builds compile cleanly | -| 3 | Test Harness And Agent-Ready Automation | In progress | `ctest --preset desktop-fast` runs headlessly | -| 4 | Component Split Without Behavior Change | Started | Each extracted target builds and tests | -| 5 | Renderer Boundary And OpenGL Parity | Started | OpenGL output matches golden readbacks | -| 6 | Platform Alignment | Started | Every supported platform has named validation | -| 7 | Hardening, Coverage, And Breaking-Point Tests | Started | Each component has edge/failure tests | -| 8 | Future Backend Readiness | Not started | Vulkan/Metal lab targets remain non-default | +- app architecture +- component boundaries +- platform boundaries +- renderer/app ownership boundaries +- the order of work needed to finish the split -## Measurable Task Tracking +It does not try to be the full build, test, or automation reference. +Those details live in the other modernization docs when needed. -Use `docs/modernization/tasks.md` before starting implementation work. It -contains the current 100-point modernization scorecard, task states, ready -queue, blocked queue, validation commands, and completion rules. Do not move the -percentage for a narrowed adapter or added planner unless a task in that file is -marked `Done` with validation and a debt/roadmap update. +## What Does Not Count As Top-Priority Progress -The 2026-06-14 planning audit added measurable blockers for roadmap -finalization: component boundary self-tests (`ARCH-001`), renderer backend -contract freeze and conformance (`RND-007`, `RND-008`), opt-in Vulkan/Metal lab -targets (`RND-009`, `RND-010`), UI thread-affinity and checked-handle migration -(`UI-001`, `UI-002`), render/UI queue race coverage (`APP-001`), package gates -for backend/UI work (`PLT-010`), `AUD-001`. -2026-06-15 app-dialog lifetime cleanup routed app-owned -progress/message/input overlays through checked handle registration in -`src/legacy_ui_overlay_services.*` before they fell back to retained dialog -node close helpers, narrowing another app-owned `DEBT-0058`/`DEBT-0063` -surface without changing dialog plans or captions. -The same checked-overlay seam now also owns main-toolbar settings dialog -opening from `src/legacy_app_shell_services.cpp`, removing another raw -app-owned dialog insertion path while leaving the remaining retained dialog -families debt-tracked. -The stale `make_canvas_draw_merge_branch_dispatch(...)` forward declaration in -`src/canvas.cpp` now matches the existing 7-argument implementation, shrinking -draw-merge helper API mismatch as part of the `STR-016` cleanup in -`b9ed78e1`. -The remaining `Canvas::draw_merge()` inline-default comment in `src/canvas.cpp` -now matches the header's `SIXPLETTE(true)` default, so the draw-merge mismatch -is comment-only and archival. -The cloud browser dialog now also opens through that seam from -`src/legacy_cloud_services.cpp`, so the remaining cloud modernization debt is -now concentrated in retained background worker threads, transfer helpers, -project-open refresh, and dialog-internal legacy behavior instead of raw root -insertion. -Retained upload/download CURL setup, TLS-bypass policy consumption, and -progress callback dispatch now also live in `src/legacy_cloud_services.*` -instead of `App::upload` and `App::download`, leaving the remaining cloud debt -focused on save-before-upload execution, upload form/response handling, -prompt/progress lifetime, OpenGL context guarding, downloaded-project open, -layer refresh, and action-history reset. -The retained upload-form setup is now also isolated behind a dedicated helper -in `src/legacy_cloud_services.cpp`, so the remaining cloud debt is further -concentrated on save-before-upload execution, response/error handling, -prompt/progress lifetime, OpenGL context guarding, downloaded-project open, -layer refresh, and action-history reset. -The retained upload post-transfer response/error handling is now also isolated -behind a dedicated helper in `src/legacy_cloud_services.cpp`, so the remaining -cloud debt is further concentrated on save-before-upload execution, -prompt/progress lifetime, OpenGL context guarding, downloaded-project open, -layer refresh, and action-history reset. -Retained save-before-upload execution now also routes through -`src/legacy_document_session_services.*` instead of -`src/legacy_cloud_services.cpp` calling `Canvas::I->project_save_thread(...)` -directly, so the remaining cloud debt is further concentrated on -prompt/progress lifetime, OpenGL context guarding, downloaded-project open, -layer refresh, and action-history reset. -Retained cloud downloaded-project open camera reset and layer-clear setup in -`execute_legacy_downloaded_project_open()` now also routes through a focused -helper in `src/legacy_document_open_services.*`, so the remaining cloud bridge -debt is further concentrated on prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -Retained cloud downloaded-project open, layer refresh, and action-history -reset in `execute_legacy_downloaded_project_open()` now also route through a -focused helper in `src/legacy_document_open_services.*`, so the remaining -cloud bridge debt is further concentrated on prompt/progress lifetime, OpenGL -context guarding, and the still-retained transfer-thread execution model. -Retained cloud downloaded-project post-open reconciliation now also routes -through `src/legacy_document_open_services.*` instead of living inline in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is further -concentrated on prompt/progress lifetime, OpenGL context guarding, -`NodeDialogCloud`, and transfer-thread execution. -The initial cloud-browser loading placeholder setup in -`NodeDialogCloud::load_thumbs_thread()` now also routes through a focused -helper in `src/node_dialog_cloud.*`, so the remaining cloud bridge debt is -further concentrated on prompt/progress lifetime, OpenGL context guarding, -`NodeDialogCloud` network/thumbnail execution, and transfer-thread execution. -The cloud-browser file-list request/response handling in -`NodeDialogCloud::load_thumbs_thread()` now also routes through a focused -helper in `src/node_dialog_cloud.*`, so the remaining cloud bridge debt is -further concentrated on prompt/progress lifetime, OpenGL context guarding, -`NodeDialogCloud` thumbnail execution, and transfer-thread execution. -The cloud-browser slot creation and selection wiring in -`NodeDialogCloud::load_thumbs_thread()` now also route through a focused -helper in `src/node_dialog_cloud.*`, so the remaining cloud bridge debt is -further concentrated on prompt/progress lifetime, OpenGL context guarding, -`NodeDialogCloud` thumbnail execution, and transfer-thread execution. -The retained cloud-browser thumbnail fetch, decode, and texture-apply path in -`NodeDialogCloud::load_thumbs_thread()` now also routes through a focused -helper in `src/node_dialog_cloud.cpp`, so the remaining cloud bridge debt is -further concentrated on prompt/progress lifetime, OpenGL context guarding, -and transfer-thread execution. -The retained cloud-download detached worker body in -`LegacyCloudServices::start_download()` now also routes through a focused -helper in `src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt -is further concentrated on prompt/progress lifetime, OpenGL context guarding, -and the still-retained transfer-thread execution model. -The retained cloud-publish worker body in -`LegacyCloudServices::prompt_publish()` now also routes through a focused -helper in `src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt -is further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud-publish prompt button wiring in -`LegacyCloudServices::prompt_publish()` now also routes through a focused -helper in `src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt -is further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud-browser OK-button wiring in -`LegacyCloudServices::show_browser()` now also routes through a focused helper -in `src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud bulk-upload progress lifetime wiring in -`LegacyCloudServices::begin_bulk_upload()` and `end_bulk_upload()` now also -routes through focused helpers in `src/legacy_cloud_services.cpp`, so the -remaining cloud bridge debt is further concentrated on retained -prompt/progress lifetime, OpenGL context guarding, and the still-retained -transfer-thread execution model. -The retained cloud bulk-upload per-file execution loop in -`LegacyCloudServices::upload_all_bulk_files()` now also routes through a -focused helper in `src/legacy_cloud_services.cpp`, so the remaining cloud -bridge debt is further concentrated on retained prompt/progress lifetime, -OpenGL context guarding, and the still-retained transfer-thread execution -model. -The retained cloud save-required warning path in -`LegacyCloudServices::show_save_required_warning()` now also routes through a -focused helper in `src/legacy_cloud_services.cpp`, so the remaining cloud -bridge debt is further concentrated on retained prompt/progress lifetime, -OpenGL context guarding, and the still-retained transfer-thread execution -model. -The retained cloud publish prompt path in -`LegacyCloudServices::prompt_publish()` now also routes through a focused -helper in `src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt -is further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud browser dialog creation path in -`LegacyCloudServices::show_browser()` now also routes through a focused helper -in `src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud download thread launch in -`LegacyCloudServices::start_download()` now also routes through a focused -helper in `src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt -is further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud download progress-dialog creation path in -`execute_cloud_download_thread()` now also routes through a focused helper in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud download URL construction and progress-callback wiring in -`execute_cloud_download_thread()` now also routes through a focused helper in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud download post-transfer open/close sequence in -`execute_cloud_download_thread()` now also routes through a focused helper in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud download flow in -`execute_cloud_download_thread()` now also routes through a focused helper in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud publish transfer-and-success body in -`execute_cloud_publish_worker()` now also routes through a focused helper in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud publish detached-thread launch in -`show_cloud_publish_prompt()` now also routes through a focused helper in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. -The retained cloud publish prompt setup in -`show_cloud_publish_prompt()` now also routes through a focused helper in -`src/legacy_cloud_services.cpp`, so the remaining cloud bridge debt is -further concentrated on retained prompt/progress lifetime, OpenGL context -guarding, and the still-retained transfer-thread execution model. +These can still be useful, but they are not first-order modernization work +while the app shell still mostly looks like the old codebase: -On 2026-06-13, retained preview reductions narrowed DEBT-0036: -`NodeStrokePreview::draw_stroke_immediate()` now also routes -feedback/material/composite planning and stroke-shader uniform assembly through -`plan_legacy_node_stroke_preview_pass_orchestration(...)`, leaving the preview -node with a smaller live-GL callback surface around pass execution. -`Canvas::draw_merge()` now also routes its per-plane merge-target clear, -blend-state gating, and optional checkerboard prepass through -`execute_legacy_canvas_draw_merge_plane_setup(...)`, reducing another retained -plane-setup branch. -`Canvas::draw_merge()` also now routes its standard per-layer texture-alpha pass -through `execute_legacy_canvas_draw_merge_layer_texture(...)`, reducing another -piece of retained layer-composite sequencing. +- planner-only extraction that leaves the same live owner in place +- new CLI surface without corresponding live app ownership reduction +- test-only or automation-only expansion that does not unblock code movement +- backend lab scaffolds +- debt-log churn without a target or ownership change -## Target Component Architecture +## Reality Check -The refactor should move toward one-way dependencies: +The codebase is meaningfully farther along than the old flat app, but it is not +close to the final architecture yet. +Historical percentage claims such as the earlier 67% score should not be used +as a proxy for architectural completion. The live app still mostly runs through +the same large shell and hotspot files. + +What is already real: + +- `pp_foundation` +- `pp_assets` +- `pp_paint` +- `pp_document` +- `pp_renderer_api` +- `pp_renderer_gl` +- `pp_paint_renderer` +- `pp_ui_core` +- `pp_platform_api` +- `pp_app_core` + +What is still carrying too much live ownership: + +- `pp_panopainter_ui`: 34 files, about 9102 lines +- `panopainter_app`: 29 files, about 8817 lines +- `pp_legacy_paint_document`: 7 files, about 5709 lines +- `pp_legacy_app`: 20 files, about 4368 lines +- `pp_legacy_ui_core`: 20 files, about 3770 lines + +Current hotspot files: + +- `src/canvas.cpp`: 4128 lines +- `src/app_layout.cpp`: 2026 lines +- `src/canvas_modes.cpp`: 1798 lines +- `src/node.cpp`: 1551 lines +- `src/main.cpp`: 1374 lines +- `src/node_panel_brush.cpp`: 1197 lines +- `src/node_stroke_preview.cpp`: 1129 lines +- `src/node_canvas.cpp`: 962 lines +- `src/app.cpp`: 950 lines +- `src/app_dialogs.cpp`: 908 lines + +Current architecture mismatches that must be treated as real blockers: + +- `pp_platform_api` still compiles Apple implementation files instead of only + platform-neutral policy and interface code. +- `src/platform_apple/apple_platform_services.cpp` and + `src/platform_linux/linux_platform_services.cpp` still reach `App::I`. +- `src/platform_legacy/legacy_platform_services.*` is still part of the live + app shell. +- `pp_panopainter_ui` still depends on `pp_legacy_app`. +- `Canvas`, `NodeCanvas`, and `NodeStrokePreview` still own too much live + OpenGL execution around the renderer boundary. +- `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files + rather than thin composition/binding surfaces. +- `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use + global singleton reach, raw observer pointers, detached `std::thread` + launches, and ad hoc mutex/condition-variable ownership. +- Modern C++23 usage exists in extracted components, especially `std::span`, + explicit result/status objects, and a few concepts, but the live app still + does not consistently express ownership, thread affinity, or renderer + resources through safe component contracts. + +Conclusion: + +- the base component extraction is real +- the app shell thinning is not done +- the platform split is not done +- the UI split is not done +- the renderer/app ownership split is not done +- future backend lab work is still premature until those four statements change + +## Final Target Architecture + +The old roadmap showed a straight chain. +That was too simple. +The real target is a layered DAG with a thin composition root. ```text pp_foundation -> pp_assets -> pp_paint -> pp_document + +pp_foundation -> pp_renderer_api -> pp_renderer_gl + +pp_document + pp_paint + pp_renderer_api -> pp_paint_renderer + +pp_foundation + pp_document + -> pp_app_core + +pp_foundation -> pp_ui_core + +pp_platform_api + -> pp_platform_windows + -> pp_platform_apple + -> pp_platform_linux + -> pp_platform_android + -> pp_platform_web + -> pp_platform_vr + +pp_app_core + pp_ui_core + pp_paint_renderer + pp_platform_api -> pp_panopainter_ui - -> pp_platform_* + +pp_app_core + pp_panopainter_ui + pp_platform_* -> panopainter_app ``` -Intended responsibilities: +Key ownership rules: -- `pp_foundation`: logging facade, math/util helpers, events, task queues, - binary streams. -- `pp_assets`: `Asset`, `Image`, `Settings`, serialization, ABR, PPBR, and PPI - helpers. -- `pp_paint`: pure `Brush`, `Stroke`, stroke sampling, and CPU reference blend - math. -- `pp_document`: canvas document model, layers, animation frames, and undo/redo - model. -- `pp_renderer_api`: renderer-neutral interfaces for textures, render targets, - shaders, meshes, readback, frame capture, and tracing. -- `pp_renderer_gl`: current OpenGL implementation behind renderer interfaces. -- `pp_paint_renderer`: stroke rasterization, layer compositing, cube/equirect - export using `pp_renderer_api`. -- `pp_ui_core`: `Node`, layout, generic controls, text/image primitives. -- `pp_panopainter_ui`: panels, dialogs, `NodeCanvas`, and app-specific - workflows. -- `pp_platform_api`: SDK-free service interfaces for clipboard, cursor, - virtual keyboard, file pickers, sharing, and future platform automation. -- `pp_platform_*`: Windows, macOS/iOS, Android, Linux, and WebGL shells. -- `panopainter_app`: composition root only. +- `pp_platform_api` is interface and policy only. No concrete platform service + implementation files belong there. +- `pp_platform_*` owns platform SDK, OS handles, platform event loops, and + concrete service bridges. +- `pp_ui_core` owns generic node/layout/overlay behavior and generic controls. +- `pp_panopainter_ui` owns app-specific panels, dialogs, canvas views, and + UI-to-app bindings. +- `pp_app_core` owns planner logic, workflow policy, and service contracts. It + does not own nodes, GL objects, or platform handles. +- `pp_paint_renderer` owns renderer-facing paint/export/preview contracts. +- `panopainter_app` owns composition only. It should stop being a second home + for document workflow, dialog orchestration, platform state, or renderer + execution. +- Threading and task dispatch are app runtime services, not incidental static + queues on `App` or detached workers launched from panels, dialogs, canvas, or + cloud helpers. +- UI ownership is handle/registry based at component boundaries. Raw `Node*` + can remain as non-owning implementation detail only when lifetime is proven + by checked handles or scoped connections. +- Renderer-facing app code uses `pp_renderer_api` resources and command/context + contracts. `Texture2D`, `RTT`, direct GL dispatch, and render-thread helpers + must not leak into future-backend-facing UI or document code. -Rules: +## Workstreams -- Component headers must not include platform SDK or graphics API headers unless - the component name includes that backend or platform. -- Pure libraries must build and test without a window, GL context, network, - tablet, VR headset, or filesystem outside test temp directories. -- Public APIs should return explicit status/result objects. PanoPainter app - code keeps exceptions disabled unless isolated SDK wrappers require them. -- Singleton access should be replaced at component boundaries with context or - service objects. Temporary facade shims require debt entries. +### 1. Break The Canvas And Preview Hotspots First -## Phase 0: Inventory, Safety Rails, And Memory +This is the highest-value work because it moves the largest concentration of +real app behavior out of the old shell. -Status: complete on 2026-05-31. Created this roadmap, -`docs/modernization/debt.md`, `docs/modernization/capability-map.md`, -`docs/modernization/build-inventory.md`, and ADR 0001. +Required outcomes: -Goal: create durable project memory and prevent silent shortcuts before large -refactors begin. +- `canvas.cpp` loses major document-plus-render ownership +- `node_canvas.cpp` and `node_stroke_preview.cpp` lose major render-pass + orchestration +- concrete GL execution moves behind renderer-facing services instead of living + in app/node files + +### 2. Thin The App Shell -Implementation tasks: +`app.cpp`, `app_layout.cpp`, and `app_dialogs.cpp` must stop acting as mixed +workflow, UI, and composition files. + +Required outcomes: + +- `app_layout.cpp` becomes menu/toolbar binding composition +- `app_dialogs.cpp` becomes workflow dispatch plus retained dialog opening glue +- `app.cpp` becomes startup/frame/queue composition over named helpers + +### 3. Finish The UI Core Split + +`pp_ui_core` exists, but generic widget ownership is still incomplete. + +Required outcomes: + +- generic `Node` and control code moves out of `pp_legacy_ui_core` +- `pp_panopainter_ui` keeps only app-specific nodes +- shared overlay/lifetime behavior stays centered in `pp_ui_core` +- the scene graph has explicit ownership, non-owning references, scoped + callback connections, and documented UI-thread affinity + +### 4. Make Runtime And Thread Ownership Explicit + +This is crucial for a modern app architecture and must move with the app-shell +split, not after it. + +Required outcomes: + +- render/UI/worker queues are owned by explicit runtime services +- detached worker threads are replaced by joinable/cancellable ownership or a + task service with shutdown semantics +- render-thread and UI-thread access are expressed through small contracts that + can be implemented by future platform shells +- `App::I` and `Canvas::I` stop being the way cross-thread code reaches state + +### 5. Finish The Platform Split + +This is still a real blocker, but it should follow the bulk code-moving work +above instead of taking priority over the main app hotspots. + +Required outcomes: + +- remove concrete platform code from `pp_platform_api` +- remove `App::I` reach from platform service implementations +- remove app-owned cross-platform handle storage +- reduce `platform_legacy` to thin composition or delete it + +### 6. Retire Thick Workflow Bridges + +Open/save/session/export/cloud/brush package flows are still too distributed +across retained app, panel, and dialog files. + +Required outcomes: + +- document workflow bridges become thin adapters over `pp_app_core` +- cloud transfer and cloud browser ownership move out of retained UI nodes +- brush package import/export ownership moves out of retained panel code + +### 7. Only Then Resume Future Backend Work -- Add `docs/modernization/roadmap.md`, `docs/modernization/debt.md`, and - `docs/adr/`. -- Add a shortcut rule: every temporary adapter, fallback, skipped platform, or - retained vendored dependency must have owner, reason, validation command, and - removal condition. -- Generate a current capability map covering: - - project open/save and PPI compatibility - - image import/export and thumbnails - - brush presets, ABR import, PPBR export/import - - layers, blend modes, alpha lock, selection mask - - animation frames and MP4/timelapse recording - - VR, tablet, touch, mouse, keyboard, gestures - - cloud upload/download/browse - - UI dialogs, panels, layout XML, settings - - Windows/AppX, macOS, iOS, Android standard, Quest, Focus/Wave, Linux, WebGL -- Record current build commands and known platform prerequisites. +Vulkan, Metal, and package-only cleanup are explicitly downstream of the app +architecture work above. + +Do not treat future backend scaffolds as proof that modernization is near done +while the current shell still depends on: -Gate: +- `platform_legacy` +- `pp_legacy_app` +- `pp_legacy_ui_core` +- `pp_legacy_paint_document` +- large GL-heavy node and canvas files -- No behavior changes. -- Existing Visual Studio, platform CMake, Gradle, Apple, Linux, and WebGL paths - are not removed. +## Exit Criteria -## Phase 1: Unified CMake Skeleton +The modernization is not done until these are all true: -Goal: make CMake the canonical source list without breaking existing projects. +- `pp_platform_api` contains only platform-neutral code +- `src/platform_apple/*`, `src/platform_linux/*`, and other concrete platform + implementations do not reach `App::I` +- `platform_legacy` is gone or reduced to a trivial temporary shim +- `App` no longer stores cross-platform handle state that belongs to platform + shells +- `pp_panopainter_ui` no longer depends on `pp_legacy_app` +- `panopainter_app` is a composition root, not a second workflow layer +- `canvas.cpp`, `node_canvas.cpp`, and `node_stroke_preview.cpp` no longer own + large renderer-orchestration bodies +- live app, UI, canvas, cloud, and platform code no longer launch detached + worker threads without owned shutdown/cancellation +- render/UI task queues are owned behind explicit app runtime services +- raw app/UI pointers are non-owning implementation details only, backed by + checked handles, scoped connections, or documented owner objects +- future-backend-facing app and UI code depends on renderer API contracts, not + retained OpenGL resource classes or direct GL execution +- `pp_legacy_ui_core`, `pp_legacy_app`, and `pp_legacy_paint_document` are + removed or reduced to narrow, explicit adapter seams with debt ids and clear + removal conditions -Status: in progress. Root `CMakeLists.txt`, `CMakePresets.json`, and project -option targets exist. The Windows desktop app builds through CMake as -`PanoPainter`; the raw Visual Studio solution/project files were removed on -2026-05-31 by user decision. The root CMake Windows app graph now includes a -`panopainter_app` composition target and `pp_platform_windows` shell target so -`PanoPainter` is only the executable/resource wrapper; Windows and vendor link -dependencies now belong to the platform shell target, and Windows runtime -payload deployment lives behind `cmake/PanoPainterRuntime.cmake`. -`pp_legacy_vendor` now owns the retained third-party source bundle as an -interim containment boundary until vcpkg, SDK imports, or documented permanent -vendoring decisions replace each dependency. `pp_legacy_engine` now contains -the retained legacy tablet, video, and low-level runtime sources as an interim -containment boundary while pure replacement components take over. -`pp_legacy_assets_io` now owns retained ABR, asset/file, binary stream, image, -serializer, and settings implementations until `pp_assets` fully replaces those -paths in the app. `pp_legacy_paint_document` now owns retained action, bezier, -brush, canvas, canvas-layer, and event implementations until `pp_paint` and -`pp_document` fully replace those paths in the app. -`pp_legacy_renderer_gl` now owns the retained OpenGL runtime implementations -for `Font`, `RTT`, `Shader`, `Shape`, `Texture2D`, `TextureCube`, `Sampler`, -and `TextureManager` as an object-library boundary folded into the retained -engine until the renderer API inversion is complete. `pp_legacy_ui_core` now -owns retained base `Node`, layout, text, image, input, popup, slider, scroll, -and settings UI controls as an object-library boundary folded into the legacy -app adapter until those paths are replaced by `pp_ui_core` and app-specific UI -targets. -`pp_app_core` now owns tested app-level document-open routing for project -files, ABR imports, and PPBR imports without UI, filesystem, platform, or -renderer dependencies; `App::open_document` and `pano_cli classify-open` -consume this route contract. It also owns tested session decisions for -project-open, app-close, save, save-as, and save-version flows; -`App::open_document`, `App::request_close`, file-menu save actions, -`NodeCanvas` save hotkeys, and `pano_cli simulate-app-session` consume those -contracts while legacy canvas/project loading remains in place. -`pp_app_core` also owns tested app preference plans for UI scale/font scale, -scale option selection, viewport scale, RTL layout direction, timelapse -recording toggles, VR mode start/stop, VR controller enablement, and canvas cursor mode; -the live tools/options menu and `pano_cli plan-app-preferences` consume those -contracts. Options-menu preference execution now dispatches through -`AppPreferenceServices` and `src/legacy_app_preference_services.*` before -legacy widgets, settings persistence, recording toggles, and canvas cursor -updates continue. -It also owns tested startup plans for run-counter increments, preference-save -intent, auto-timelapse startup, stored VR-controller state, license-warning -visibility, and main startup resource sequencing for shader, asset, layout, -title, and UI render-target setup. `App::init` now plans those decisions before -heavy initialization, executes run-counter persistence through -`src/legacy_app_startup_services.*` before resource setup, dispatches the -resource sequence through the same bridge, and executes runtime startup side -effects after the UI layout and main render target exist. -It also owns tested app status/display plans for document title text, -resolution mapping/labels, DPI text, history-memory text, and recording-frame -status text, plus renderer diagnostic indicator labels for framebuffer fetch -and floating-point render targets; `App::title_update`, -`App::update_memory_usage`, `App::update_rec_frames`, resolution helpers, -`App::initLayout`, and `pano_cli plan-app-status` consume those contracts while -legacy UI nodes still render the strings and status lights. -App-level progress, message, and input dialog metadata now also lives in -`pp_app_core` through `plan_app_progress_dialog`, -`plan_app_message_dialog`, and `plan_app_input_dialog`; `App::show_progress`, -`App::message_box`, `App::input_box`, and `pano_cli plan-app-dialog` consume -those plans before `src/legacy_app_dialog_services.*` creates retained -`NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` instances. Legacy -dialog node lifetime/layout ownership remains tracked under `DEBT-0058`. -Frame-level app decisions for the initial surface size, redraw/animation update -gating, layout ticking, resize render-target recreation, canvas-stroke drawing, -VR UI drawing, main UI drawing, UI observer clipping/on-screen transition/scissor -projection, and redraw reset now live in `pp_app_core`; -`App::create`, `App::tick`, `App::resize`, `App::update`, `App::draw`, and -`pano_cli plan-app-frame` consume those plans while retained layout traversal, -render-target recreation, `Node` parent walking, on-screen callbacks, and -OpenGL/UI drawing stay in the legacy app. -App input dispatch decisions for pointer coordinate normalization, mouse -designer-first routing, gesture midpoint/delta math, touch/key main-layout -routing, VR spacebar camera-sync intent, UI visibility toggling, and stylus -touch-lock attachment now live in `pp_app_core`; `App::mouse_*`, -`App::gesture_*`, `App::touch_tap`, `App::key_*`, `App::toggle_ui`, -`App::set_stylus`, and `pano_cli plan-app-input` consume those plans while -retained event objects, child-node mutation, and legacy `Node` dispatch stay -in the app shell. -App thread orchestration decisions for render/UI task dispatch, unique queued -task replacement, queue draining, render-context wrapping, async redraw -notification, UI tick redraw scheduling, UI-loop timer/report/reload cadence, -and thread start/stop intents now live in `pp_app_core`; `App::render_task*`, -`App::ui_task*`, `App::async_redraw`, `App::render_thread_*`, -`App::ui_thread_*`, and `pano_cli plan-app-thread` consume those plans while -retained `std::thread`, condition-variable, OpenGL context, live reload, and -task execution remain in the app shell. -Shutdown lifecycle staging for UI-state save, stroke-preview renderer shutdown, -recording stop, texture/shader invalidation, layout unload, render-target -destruction, panel-node release, and quick-mode cleanup now lives in -`pp_app_core`; `App::terminate` and `pano_cli plan-app-shutdown` consume that -plan while retained cleanup execution stays in the legacy app. -Command-line panorama conversion planning for renderer-state setup, temporary -canvas allocation, project open, and equirectangular export now lives in -`pp_app_core`; `App::cmd_convert` and `pano_cli plan-command-convert` consume -that sequence while retained OpenGL state dispatch and legacy `Canvas` -open/export execution stay in the legacy app. -`panopainter_app` is now a real static target that owns app orchestration -sources, app version metadata, and version-header generation. -`pp_panopainter_ui` now owns app-specific modal, dialog, panel, canvas, -viewport, color-picker, stroke-preview, and tool UI workflow nodes outside -`pp_legacy_app`; base `Node` controls and layout plumbing remain in the legacy -target until the UI core/app UI boundary is tightened. Android arm64 now -configures and builds headless -foundation/tool targets through the root CMake/NDK path. Non-Windows platform -app/package files remain during Phase 6 alignment. +## Active Queue -Implementation tasks: - -- Add root `CMakeLists.txt` and shared CMake modules under `cmake/`. -- Add `CMakePresets.json` with at least: - - `windows-vs2026-x64` - - `windows-clangcl-asan` - - `linux-clang` - - `android-arm64` - - `android-x64` - - `emscripten` - - `macos` - - `ios-device` - - `ios-simulator` -- Keep Android CMake, Linux CMake, WebGL CMake, Apple project files, and AppX - packaging during the transition until each consumes shared component targets. -- Move version generation into a CMake custom command using - `scripts/pre-build.py`. -- Fix `scripts/pre-build.py` only if required to avoid unnecessary rewrites or - missing-tag failures. -- Add CMake options: - - `PP_BUILD_APP` - - `PP_BUILD_TESTS` - - `PP_BUILD_TOOLS` - - `PP_ENABLE_OPENGL` - - `PP_ENABLE_VULKAN_EXPERIMENTAL=OFF` - - `PP_ENABLE_VR` - - `PP_ENABLE_CLOUD` - - `PP_ENABLE_VIDEO` -- Define source-list helper targets so per-platform source duplication can be - reduced incrementally. - -Gate: - -- Windows desktop app builds through CMake. -- New CMake can configure on Windows. -- Source list differences are understood and documented. -- Non-Windows platform migration is debt-tracked until Phase 6. - -## Phase 2: Toolchain, Diagnostics, And Dependencies - -Goal: turn the build into an error-finding system before deep refactors. - -Status: in progress. Initial warning/sanitizer option targets, `vcpkg.json`, -a validated Windows headless vcpkg preset, `pp_ui_core` support for vcpkg -tinyxml2 on that preset, and a headless `panopainter_validate_shaders` target -exist. `windows-clangcl-asan` now configures as a headless Ninja/clang-cl ASan -preset and uses the release MSVC runtime required by clang-cl ASan, but local -ASan builds are blocked by DEBT-0014 until Clang and the selected MSVC STL are -compatible. Dependency migration is not complete until remaining component -dependencies and mobile/Apple triplets are validated. Root CMake now also -exposes `panopainter_platform_build_vcpkg_ui_core`, a focused automation target -that resolves `VCPKG_ROOT` through the platform-build wrapper and validates the -vcpkg-backed `pp_ui_core`/tinyxml2 XML test boundary from the CMake target -graph. - -Implementation tasks: - -- Set C++23 through target features, not raw compiler flags. -- Add warning profiles: - - MSVC: `/W4 /permissive- /Zc:__cplusplus /Zc:preprocessor`, with - `C4100` muted temporarily under `DEBT-0019`. - - Optional MSVC analysis preset: `/analyze`. - - Clang/GCC: `-Wall -Wextra -Wpedantic -Wconversion -Wshadow - -Wnull-dereference`, with `-Wunused-parameter` muted temporarily under - `DEBT-0019`. -- Keep exceptions disabled for PanoPainter targets, except isolated SDK wrapper - targets when unavoidable. -- Add sanitizer presets: - - Clang/GCC ASan and UBSan for headless libraries. - - MSVC ASan where supported. - - TSan only for pure/headless targets. -- Add tooling hooks: - - `clang-tidy` - - `cppcheck` - - shader validation or compile checks - - CTest dashboard output -- Add `vcpkg.json`. -- Move reliable dependencies to vcpkg first: - - `fmt` - - `glm` - - `tinyxml2` - - `stb` - - `curl` - - `sqlite3` - - `glad` - - `Catch2` -- Keep vendored until proven: - - OpenVR only as the temporary desktop compatibility fallback while OpenXR is - introduced behind `pp_platform_vr` - - OVR/Wave SDKs - - Wacom WinTab - - AppCenter - - openh264 - - mp4v2 - - libyuv - - patched or SDK-specific libraries - -Gate: - -- Desktop library targets compile with strict diagnostics. -- New warnings caused by refactor are fixed or locally justified. -- Any global warning suppression must have an open debt entry, validation - command, and removal condition. - -## Phase 3: Test Harness And Agent-Ready Automation - -Goal: make each component reachable by automated tools and future agents. - -Status: in progress. `tests/` exists, `desktop-fast`, `fuzz`, and `stress` -CTest presets run headlessly, and -PowerShell/bash wrappers exist for -configure/build/test/analyze/platform-build/package-smoke. `pano_cli` exists -with JSON automation commands for app document-open routing, app session -dirty-state and save decisions, creating a `pp_document` model, metadata-only -PPI project loading, and inspecting image signatures, PPI headers, and layout -XML; full document/app integration is debt-tracked as DEBT-0010 and full PPI -body parsing is debt-tracked as DEBT-0013. Agent code navigation now includes -`scripts/dev/clangd_nav.py` with symbol/detail/path regex filters and a -`panopainter_clangd_nav_regex_self_test` CTest so broad symbol-family searches -can be validated before they guide refactors. Agents must use the -`panopainter-code-navigation` skill before broad text search whenever C++ -symbol identity, generated-style symbol families, declarations/definitions, -override groups, or platform/backend path slices are the real question. - -Implementation tasks: - -- Add `tests/` with one executable per component. -- Register CTest labels: - - `foundation` - - `assets` - - `paint` - - `document` - - `renderer` - - `ui` - - `platform` - - `integration` - - `fuzz` - - `slow` - - `gpu` -- Add `tools/pano_cli` for headless automation. -- `pano_cli` should support: - - create document - - load project - - save project - - apply scripted strokes - - import/export images - - inspect layers - - run layout parse - - emit JSON results -- Add local automation wrappers under `scripts/automation/`: - - configure - - build - - test - - analyze - - package smoke -- All wrappers must return machine-readable logs or summaries. -- Establish `tests/data/` fixtures: - - tiny PPI files - - corrupt/truncated PPI cases - - PNG/JPEG fixtures - - ABR/PPBR samples - - layout XML - - shader snippets - - brush stroke scripts - -Gate: - -- `ctest --preset desktop-fast --build-config Debug` runs without a GL - context. -- Non-render components can be tested on a headless machine. - -## Phase 4: Component Split Without Behavior Change - -Goal: split libraries while keeping current app behavior. - -Status: started. `pp_foundation` exists with binary stream utilities and -boundary/overread/overlapping-write tests. It also owns strict decimal `uint32` parsing used by -`pano_cli`, with rejection tests for empty, signed, mixed, and overflowing -input. A synchronous event dispatcher, structured logging facade, bounded FIFO -task queue, and deterministic `TraceRecorder` now record -component/name/thread/frame/stroke metadata with filtering, capacity, and -invalid-end tests. `pp_assets` has started with PNG/JPEG signature detection, -PNG IHDR metadata parsing, PPI header/project byte-layout/body-summary -recognition, layer/frame indexing, dirty-face PNG payload metadata validation, -asset-level RGBA PNG payload decoding, and a pure typed settings document -model, with -corrupt/truncated/unsupported, non-finite opacity, unsupported blend-mode, -extreme-dimension, and key/value limit tests. -`pp_paint` has started with pure brush parameter validation/stamp evaluation, -CPU reference math for the five current final RGBA shader blend modes plus the -shader-style stroke-alpha blend modes used by pattern/dual-brush mixing, and deterministic -stroke spacing/interpolation plus duplicate-segment, non-finite, sample-limit, -and 1001-sample stress coverage, plus a pure text stroke-script parser. -`pp_document` has -started with a pure canvas/layer/frame model, alpha-lock metadata, snapshot -construction, per-layer frame metadata, layer metadata operations, frame -move/duration queries, renderer-free RGBA8 cube-face payload storage, -renderer-free alpha8 selection-mask storage, PPI image import/export, and -layer/frame/undo-redo history invariant tests. Snapshot construction validates -embedded face-pixel payload bounds, byte counts, duplicate face payloads, and -duplicate selection masks. -`pp_renderer_api` has started with renderer-neutral -texture/readback descriptors and validation tests. `pp_paint_renderer` has -started with deterministic CPU layer compositing over renderer extents using -the paint blend reference, and now exposes pure `pp_document` face and -six-face frame compositors that expand per-layer dirty face payload rectangles -into full renderer-sized RGBA buffers for a requested document frame. It can -also upload those six composited faces through the renderer-neutral -`IRenderDevice` texture API, with the recording backend validating upload and -explicit-transition command streams. -`pp_ui_core` has started with XML-layout-facing -length parsing, color parsing, tinyxml-backed layout XML parsing, and invalid -input tests. Retained `Node` tree lifetime safety is now an explicit -modernization track under `DEBT-0063`: the current UI still mixes raw parent -and lookup pointers, public mutable child ownership, raw callback targets, and -manual destroy flags. `pp_ui_core` should introduce owned tree/handle APIs, -scoped callback connections, mutation-safe event dispatch, and focused -destroy-during-callback tests before broad `NodePanel*`/`NodeDialog*` -migration accelerates. The first pure `pp_ui_core::NodeLifetimeTree` slice now -models checked handles, parent/child invariants, scoped callback connections, -and mutation-safe dispatch for destroy-during-callback and connection-addition -cases. It now also models pointer/keyboard capture release and whole-tree -`clear()` invalidation for layout reload. `pp_ui_core::UiOverlayLifetime` now -layers popup/dialog stack ownership on top of that model, covering root and -nested popups, modal versus modeless dialogs, capture restoration, parent-popup -branch close, and layout-reload invalidation. Wiring those semantics into -retained `Node`/`NodePopupMenu`/`NodeDialog*` remains tracked by the same debt. -The first retained adoption point is `src/legacy_ui_overlay_services.*`, which -now centralizes app-dialog overlay initialization and root attachment for -progress/message/input dialogs, about/manual/changelog, document open/save/new/ -browse/resize/layer-rename dialogs, PPBR export, shortcuts, and the what's-new -remote page before those paths reach raw `Node` insertion. It also now owns -retained app-menu popup template cloning and root attachment for File, Export, -Edit, Tools, Panels, Options, About, and Layers menu popups, replacing the -previous direct template-child indexing in `App::init_menu_*`. The helper now -also exposes retained root-popup attachment for `pp_panopainter_ui`, and the -quick-panel brush/color popups, stroke-panel preset/tip/dual/pattern popups, -brush-panel preset menu root insertion, retained combo-box popups, -Open/Browse delete-confirmation dialogs, popup tick decoration nodes, and -top-toolbar panel popups route through it. The helper also centralizes retained -popup mouse-ignore/flood/capture-child activation for those popup families. -Repeated menu, submenu, combo-box, and brush-preset popup close/release -execution now uses the same retained helper, and popup tick-decoration close -callbacks are bound through the overlay service instead of per-panel lambdas. -Brush, brush-preset, color, layer, grid, stroke, and color-picker popup-panel -outside-click close handling now shares the same retained release/remove/ -callback helper. -About, changelog, settings, and user-manual close buttons now share retained -destroy-on-click binding through the same overlay service. -Message-box submit/cancel and input-box cancel destroy callbacks now use the -same retained close binding helpers. -Document open/save/new/browse/resize, layer-rename, cloud-browse, and PPBR -export dialog cancel buttons now use those same retained close helpers. -App-level new-document, save, and layer-rename cancel callbacks now share the -retained dialog close plus virtual-keyboard hide helper. -Browse accept, resize accept/failure, and what's-new read-later/close callbacks -now use a named retained dialog close helper instead of direct `destroy()` calls. -Open/Browse delete-confirmation message boxes and shared destroy-on-click -bindings now use that same retained dialog close helper. -Document-session overwrite, unsaved-close, save-before-workflow, and accepted -new/save document cleanup now route retained dialog closing through the same -helpers. -Cloud publish prompts, upload/download progress dialogs, and cloud browser -download-close now use the retained overlay close helpers. -App message-dialog cancel-button removal, layer-rename finish cleanup, and -recording export progress cleanup also route through those helpers. -Floating-panel close and drag-outline cleanup now use the same retained close -helper while drag reparenting remains legacy-owned. -`NodePopupMenu` mouse-up close/release now uses the retained popup close helper. -Layer-row, animation-timeline, and heightmap-overlay drag release paths now use -the same retained mouse-capture release helper, and brush/grid progress or -recovery message dialogs now route through the retained dialog close helper. -Floating-panel placeholder detach/destroy, cloud-loading alignment cleanup, -brush-preset item removal, and retained popup-panel parent detach now use named -retained node helpers; dock/drop reparenting remains legacy-owned. -Canvas export/project progress-bar cleanup and remote-page loading placeholder -cleanup now route through those same retained close/destroy helpers. -Toolbar popup restoration from docked floating stroke/layer/grid panels and -restored floating color-panel title cleanup now use the retained detach/close/ -destroy helpers. -Canvas mode mouse-capture release for camera, paint, line, grid, mask, and fill -interactions now routes through the retained capture-release helper. -Button, slider, scroll, color-wheel, color-quad, and canvas gesture-end capture -release now route through that same helper. -Legacy document-open import/discard prompts and brush-package export completion -now route retained dialog closing through the same helper. -Flood-fill tool teardown and checkbox icon removal now route retained node -destruction through that helper as well. -Layout unload root destruction and popup tick-overlay close callbacks now route -retained node destruction through that helper. -Raw popup callback captures and full close/capture ownership remain part of -`DEBT-0063`. -`pano_cli inspect-image` exposes PNG IHDR metadata as JSON, -`pano_cli import-image` accepts a PNG path and imports decoded RGBA8 pixels -into a new pure `pp_document` face payload, -with checked-in decodable PNG and truncated PNG automation coverage, -`pano_cli export-image` writes a deterministic RGBA8 PNG through `pp_assets` -and round-trips it back through file import automation, -`pano_cli inspect-project` reports validated PPI thumbnail/body byte layout, -body summary, layer/frame descriptors, dirty-face PNG payload metadata, and -asset-level decode coverage, and -`pano_cli load-project` creates a `pp_document` projection with per-layer frame -counts, durations, and decoded face-pixel payload attachment when PPI image -payloads are present. -`pp_assets` can write generated PPI projects with explicit per-layer names, -visibility, opacity, blend mode, alpha lock, per-layer frame durations, and -dirty-face payloads targeted to layer/frame/face slots. `pano_cli save-project` -exposes the generated writer for metadata-only and test dirty-face-payload -round-trips through `load-project` and rejects non-finite automation float -inputs before writing files. -`pp_document::export_ppi_project_document` converts pure documents into PPI -bytes using that writer, including PNG-encoded layer/frame face payloads. -`pano_cli simulate-document-export` exercises that pure document export path, -decodes the generated PPI bytes, reimports them, and emits JSON round-trip -metadata. -`pano_cli simulate-document-render` exercises the pure document-to-renderer -frame compositor and renderer texture-upload bridge, emitting six-face render -summaries, renderer upload-command summaries, and OpenGL command-planner -support counts for headless automation. -`pano_cli save-document-project` writes the same pure document export to a PPI -file for inspect/load round-trip automation. -`pano_cli create-document` can create simple animation documents with explicit -frame count/duration. `pano_cli simulate-document-edits` exercises pure -layer metadata, frame reordering, active-index preservation, tiny face-payload -attachment, and selection-mask attachment. `pano_cli simulate-document-history` exercises the -pure `pp_document::DocumentHistory` apply/undo/redo path and emits JSON state -summaries. `pano_cli simulate-image-import` decodes an embedded tiny PNG -through `pp_assets` and attaches the resulting RGBA8 payload to `pp_document`. -`pano_cli simulate-document-export` exercises pure document-to-PPI export, -asset-level PPI image decode, and document reimport in one automation command. -`pano_cli save-document-project` writes a deterministic pure document export -PPI and verifies it through inspect/load smoke coverage. -`pano_cli simulate-blend` exposes deterministic final RGBA and stroke-alpha -blend reference vectors through JSON automation. -`pano_cli simulate-stroke` exercises the pure stroke sampler for -scripted-stroke automation. `pano_cli simulate-stroke-script` -loads stroke script fixtures, parses them through `pp_paint`, and samples every -stroke. `pano_cli apply-stroke-script` maps sampled script points into a -bounded `pp_document` RGBA8 face payload, writes a PPI file, and verifies that -the applied stroke payload survives inspect/load round-trip automation, with a -rejection smoke test for unsafe tiny canvas dimensions. -`pano_cli classify-open` exposes the pure `pp_app_core` document-open route -contract for project files, ABR imports, PPBR imports, and malformed path -rejection. `pano_cli plan-open-route` exposes the pure `pp_app_core` -document-open action plan for clean project open, dirty project prompt, and -brush-import prompt flows. `pano_cli simulate-app-session` exposes the pure -`pp_app_core` session decisions used by project-open, app-close, save, save-as, -and save-version flows, plus the save-before-continue workflow gate used by -new-document/open/browse dialogs. `pano_cli plan-new-document` exposes the -same app-core new-document target, legacy resolution-index mapping, and -overwrite decision used by the live new-document dialog, including invalid -resolution rejection. `pano_cli plan-document-file` exposes the same app-core -document-name validation, legacy `.ppi` path construction, and overwrite -prompt decision used by save-as dialogs through one combined save-file plan. -`pano_cli plan-document-version` exposes the save-version suffix parsing, -candidate generation, collision skipping, and no-slot failure behavior used by -the live save-version dialog. -`pano_cli plan-export-target` exposes app-core export target planning for -equirectangular image files, layer/frame collection stems, picked-directory -stems, cube-face work-directory file sets, and MP4 suggested names used by the -live export dialogs and cube-face writer bridge. -`pano_cli plan-export-start` exposes the app-core export availability decision -used by live image, layer, animation-frame, depth, and cube-face export dialogs -plus MP4 animation and timelapse export dialogs before they call legacy -canvas/recording export execution. -`pano_cli plan-export-message` exposes the app-core export completion dialog -metadata now consumed by the live legacy export bridge for equirectangular, -layer/frame, depth/cube, animation MP4, and timelapse success reporting, -including platform-style destinations and no-message/suppressed branches. -`pano_cli plan-export-report` exposes app-core failure and license-disabled -dialog metadata now consumed by live export dialogs before retained legacy -export execution/logging continues. -`pano_cli plan-recording-session` exposes the app-core recording start, stop, -clear, platform recorded-file cleanup, frame reset, export progress-total, and -export progress-dialog decisions used by the live recording controls. Recording -lifecycle and MP4 export execution now dispatch through `RecordingServices` in -`src/legacy_recording_services.*` before legacy recording threads, PBO readback, -and MP4 encoder execution continue. The retained MP4 export progress bar now -uses `src/legacy_app_dialog_services.*` for creation while progress lifetime -and MP4 writing remain legacy-owned. -`pano_cli plan-share-file` exposes the app-core saved-path decision used by the -live platform share command before iOS/macOS sharing bridges or retained no-op -platform branches execute. -`pano_cli plan-picked-path` exposes the app-core selected-path decision used by -live image, file, save-file, and directory picker branches before retained -platform callbacks or legacy picker bridges continue. -`pano_cli plan-display-file` exposes the app-core external file presentation -decision used by live display-file requests before retained platform open-file -bridges continue. -`pano_cli plan-keyboard-visibility` exposes the app-core virtual keyboard -visibility decision used by live show/hide keyboard requests before retained -mobile platform keyboard bridges continue. -`pano_cli plan-cursor-visibility` exposes the app-core cursor visibility -decision used by live canvas cursor requests before retained desktop platform -cursor bridges continue. -`pp_platform_api::probe_asset_file_timestamp` now owns the retained asset mtime -probe used by layout reload planning, with missing/existing file coverage. -`LayoutManager::load` now consumes that explicit probe plus the pure -`plan_asset_file_load_with_probe` decision instead of calling the probing -wrapper directly. -`pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write` expose the -app-core clipboard text decisions used by live clipboard get/set requests -before retained platform clipboard bridges continue. -`pano_cli plan-document-resize` exposes the app-core resize dialog state and -selected-resolution commit plan used by the live document resize dialog, and -resize execution now dispatches through `DocumentResizeServices` before the -shared app-shell document-canvas bridge runs the legacy `Canvas` resize adapter -and history clearing. -`pano_cli plan-layer-rename` exposes the app-core layer rename decision used by -the live layer rename dialog, and rename execution now dispatches through -`DocumentLayerRenameServices` in the shared app-shell layer bridge -`src/legacy_document_layer_services.*` before legacy `Canvas`, `NodeLayer`, and -`ActionManager` undo adapters continue. -`pano_cli plan-layer-operation` exposes app-core planning for layer add, -duplicate, select, reorder, remove, opacity, visibility, alpha-lock, blend-mode, -and highlight actions used by the live layer panel. Direct layer-panel -operations now dispatch through `DocumentLayerOperationServices` before the -shared app-shell layer bridge continues legacy `Canvas` and UI layer execution. -`pano_cli plan-layer-panel-view` exposes the app-core layer panel view model for -current opacity, alpha-lock, blend mode, and per-layer visibility state, and -live `NodePanelLayer::update_attributes()` now consumes that tested projection -before writing the retained legacy UI controls. -`pano_cli plan-layer-menu` exposes app-core planning for Layer menu clear, -rename, and merge-down labels/actions, and direct Layer menu commands now -dispatch through `DocumentLayerMenuServices` before the legacy canvas/layer UI -adapter in `src/legacy_document_layer_services.*` continues execution. Layer -menu clear now routes through the shared `DocumentCanvasClearServices` executor -from that bridge before the legacy canvas-clear adapter continues, and Layer -menu merge now validates and dispatches through `DocumentLayerMergeServices` -before the legacy layer-panel merge adapter continues. -`pano_cli plan-animation-operation` exposes app-core planning for animation -frame add, duplicate, remove, duration adjustment, timeline moves, timeline -goto/next/previous, onion-size updates, frame selection, no-reload playback -stepping, and play-mode toggles used by the live animation panel. -`pp_app_core` also owns onion-skin frame range and alpha falloff planning now -consumed by live `NodeCanvas` panorama drawing. -`pano_cli plan-animation-panel-action` exposes the higher-level animation panel -state/action planner for goto, next, previous, playback-step, and play-toggle -automation without requiring the legacy UI or canvas. -Panel-control, timeline, selected-frame click, playback tick, and play-button -toggle execution now dispatch through `DocumentAnimationServices` before the -shared `src/legacy_document_animation_services.*` bridge continues legacy -`Canvas`/`Layer`/canvas-mode and animation-panel state execution. -`pano_cli plan-brush-operation` exposes app-core planning for brush color -changes, tip/pattern/dual texture changes, preset brush replacement, and stroke -settings refreshes used by the live brush, quick, color, and floating panel -callbacks. Brush UI execution now dispatches through `BrushUiServices` before -the shared `src/legacy_brush_ui_services.*` bridge mutates legacy `Brush` and -panel state or loads brush resources. -`pano_cli plan-brush-texture-list` exposes app-core planning for brush/pattern -texture add, remove, and reorder actions, and `NodePanelBrush` now dispatches -those actions through `BrushTextureListServices` in the shared brush bridge -before the legacy image load/save and UI-list adapter continues. -`pano_cli plan-brush-stroke-control` exposes app-core planning for the live -stroke panel's slider, checkbox, blend-mode, tip-aspect reset, and default -brush reset commands. `NodePanelStroke` now dispatches those controls through -`BrushStrokeControlServices` in the shared brush bridge before the legacy -`Canvas::I`/`Brush`/stroke-panel adapter continues. -`pano_cli plan-brush-stroke-panel-view` exposes the app-core stroke-panel view -projection for brush float settings, toggles, blend modes, and thumbnail paths, -and live `NodePanelStroke::update_controls()` now consumes that tested -projection before applying retained slider-curve, preview, and thumbnail UI -updates. -`pano_cli plan-brush-refresh` exposes app-core planning for app-level brush -refresh fan-out, and live `App::brush_update()` now consumes that view before -applying retained stroke, quick, and floating color widget updates. -`pano_cli plan-canvas-tool` exposes app-core planning for draw/erase/line, -camera, grid, copy, cut, fill, mask, flood-fill, pick, and touch-lock toolbar -commands. Canvas tool execution now dispatches through `CanvasToolServices` -in `src/legacy_canvas_tool_services.*` before legacy toolbar selection, `Canvas` -mode, pen picking, touch-lock, and transform state adapters continue. -`pano_cli plan-canvas-tool-toolbar` now exposes the full draw-toolbar binding -set, including button ids, select/toggle actions, button-class expectation, and -the default draw-mode initialization. Live `App::init_toolbar_draw` consumes the -same app-core toolbar plan to install handlers and apply the initial draw tool -while retained `NodeButton`/`NodeButtonCustom` lookup and legacy canvas-tool -execution remain under `DEBT-0027`. -`pano_cli plan-canvas-tool-state` exposes the matching toolbar active-state -refresh used by `App::update` before legacy `Canvas` mode state remains the -source of truth. `NodeCanvas` stylus eraser mode switching consumes the same -shared bridge through its input-only path before legacy canvas mode execution -continues. Canvas mode pointer-tip visibility and Windows pressure remapping -now dispatch through `PlatformServices`, preserving iOS tip behavior and the -Windows pressure curve outside `canvas_modes.cpp`. `NodeCanvas` keyboard and -touch command handling now consumes -`pp_app_core` canvas-hotkey planning for E draw/erase, Ctrl+Z, Ctrl+Shift+Z, -Ctrl+S, Ctrl+Shift+S, Tab UI toggle, brush-size brackets, Android back, Alt -cursor reveal, and two-finger undo before the shared bridge delegates to legacy -UI/canvas/history adapters. `pano_cli plan-canvas-cursor` exposes the -canvas-specific cursor visibility policy for draw/erase versus non-paint -modes, small-brush thresholds, active-stroke hiding, and modifier/tool forced -visibility; live `NodeCanvas::update_cursor()` consumes that planner before -retained `App::show_cursor`/`App::hide_cursor` platform dispatch. -`pano_cli plan-canvas-clear` exposes app-core planning for the main toolbar -clear-current-layer command, including clear color validation, no-canvas -handling, undo recording intent, and dirty-state intent; live toolbar execution -and Layer menu clear now dispatch through the shared app-shell document-canvas -bridge before the legacy `Canvas::clear` adapter continues. -`pano_cli plan-canvas-document-snapshot` exposes the app-core projection from -live canvas metadata into a pure `pp_document::CanvasDocument`, including -dimensions, active layer/frame, layer visibility/opacity/alpha/blend metadata, -frame durations, captured RGBA8 face payloads, and remaining renderer -payload-readback counts; the retained `legacy_document_canvas_services` bridge -now builds the same metadata snapshot from live `Canvas` state and has an -opt-in dirty-face payload snapshot path backed by retained `Layer::snapshot()` -readback. Live Save, Save As, Save Version, and save-before-workflow paths now -prepare and log a payload-completeness report from that snapshot before -delegating to retained `Canvas::project_save`; payload-complete live save -snapshots also run the tested pure PPI exporter and log the app-core -save-writer route/byte count before retained save continues. The app-core -snapshot boundary also has a tested pure PPI export helper, and -`pano_cli plan-canvas-document-snapshot` runs that helper for payload-complete -snapshots and reports generated byte/dirty-face summaries plus the same -save-writer route JSON. `pp_app_core` also owns the retained project-save target -path planner for target, temporary PPI, and timelapse sidecar paths; live -`Canvas::project_save_thread` consumes that planner and -`pano_cli plan-canvas-project-save-target` exposes it for automation. The same -app-core boundary now also plans the retained save write mode, distinguishing -direct first saves from existing-target temporary writes that swap into place -and preserving the legacy direct-write fallback when the temporary file cannot -be opened. The same app-core boundary now also classifies the post-write -commit result for direct writes, successful temporary swaps, remove failures, -and rename-after-remove failures before retained save metadata mutation -continues. Post-save side effects are now planned there too: mark-clean, -new-document commitment, timelapse sidecar gating, platform flush, progress -cleanup, and title refresh are reported before retained execution performs the -actual mutations and sidecar serialization. The same automation now feeds -payload-complete snapshots through the shared -`pp_paint_renderer::prepare_document_frame_export_readiness` report, which -records renderer-neutral six-face texture upload commands and encodes the -active document frame's six composited faces to PNG bytes. This gives CLI -automation and live export adapters the same document/canvas-to-renderer -readiness boundary before broader writer replacement. Live save writer -replacement, export adoption, and renderer-owned readback remain under -`DEBT-0010`/`DEBT-0013`/`DEBT-0036`. -The same CLI snapshot report now emits `depthExport` readiness from -`pp_paint_renderer::plan_document_depth_export_render`: metadata-only snapshots -report pending renderer payload readback, while payload-complete snapshots -report the legacy 1024x1024 depth-render draw plan and still flag the final -3D view/depth image readback as renderer-owned. -Live equirectangular, layer, animation-frame, and cube-face export adapters now -prepare the same payload-bearing document snapshot and shared renderer export -readiness report. Cube-face export writes those -document/renderer-owned PNG bytes through a tested app-core write/publish -executor using the app-core-planned legacy face filenames when available and -falls back to retained `Canvas::export_cube_faces` on snapshot/write failure. -PNG equirectangular export now uses the same document/composite payload to -generate an equirectangular PNG through `pp_paint_renderer`, then writes it -through the app-core file write/publish executor before the retained fallback. -Payload-complete layer and animation-frame PNG collections now use -pure `pp_paint_renderer` equirectangular PNG generation plus app-core -collection write/publish execution before retained fallback. Payload-complete -desktop JPEG equirectangular export now uses the same projection through -`pp_paint_renderer`, `pp_assets` JPEG encoding, GPano XMP injection, and the -same app-core file write/publish executor before retained fallback. -`pp_app_core` now owns the document-snapshot export route -decision used by those live adapters, covering platform support, target support, -PNG/JPEG equirectangular target support, collection/cube target support, and -incomplete renderer-payload fallback; `pano_cli plan-export-snapshot-route` -exposes the same decision for automation, including unsupported target paths -and unsupported platform fallback. The retained export bridge now consumes the -app-core current-platform policy for snapshot-writer attempts instead of owning -local Web writer gates. -Web handoff, video, depth writer replacement, and incomplete-readback cases -still delegate to retained `Canvas` writers after route/readiness reporting. -Depth export now also plans the retained image/depth file targets in -`pp_app_core`, logs the document-snapshot route fallback as an unsupported -snapshot-writer target, and logs a `pp_paint_renderer` document depth render -plan for the legacy 1024x1024 perspective render plus per-layer depth pass -before falling back to retained `Canvas::export_depth`; actual depth rendering, -readback, and format parity remain retained. -`pano_cli plan-image-import` exposes app-core planning for File > Import image -route decisions, including wide equirectangular images, legacy vertical cube -strips, regular transform-placement images, and invalid image dimensions; live -File > Import execution now dispatches through `DocumentImageImportServices` -before legacy image loading, `Canvas::import_equirectangular`, or import -transform-mode setup continues. -`pano_cli plan-file-menu` exposes app-core planning for the top-level File menu -commands, including new/open/import, save/save-as/save-version, share, resize, -cloud upload/browse, JPEG export, and export-submenu routing. Direct File menu -commands now dispatch through `FileMenuServices` in the shared app-shell bridge -`src/legacy_app_shell_services.*` before legacy dialogs, pickers, platform -services, cloud code, and canvas workflows continue. -`pano_cli plan-export-menu` exposes app-core planning for File menu export -choices, including image, layer, cube-face, depth, animation-frame, MP4, and -timelapse dialog routing plus license/canvas gating. Export menu commands now -dispatch through `DocumentExportMenuServices` in the shared app-shell bridge -before legacy export dialogs and renderer/video execution continue. -Export success-message metadata now also comes from `pp_app_core` through -`pano_cli plan-export-message` and the legacy document-export bridge, reducing -the bridge to export execution, platform handoff, and retained threading. -Export failure/license dialog metadata now comes from `pp_app_core` through -`pano_cli plan-export-report`, with legacy `App::dialog_export*` only showing -the planned dialog and dispatching retained export calls. -`pano_cli plan-grid-operation` exposes app-core planning for grid heightmap -pick/load/reload/clear, lightmap render capability/limit checks, and heightmap -commit used by the live grid panel. Grid execution now dispatches through -`GridUiServices` in `src/legacy_grid_ui_services.*` before legacy image loading, -OpenGL texture updates, nanort lightmap baking, and `Canvas::draw_objects` -execution continue. -The retained `NodePanelGrid` lightmap bake now uses the shared `parallel_for` -helper instead of platform-specific Win32 Concurrency Runtime and Apple -`dispatch_apply` branches, keeping the row-dispatch policy in common legacy -infrastructure while the bake itself remains debt-tracked. -`pano_cli plan-history-operation` exposes app-core planning for undo, redo, and -clear-history availability used by toolbar buttons and canvas shortcuts; live -toolbar and canvas-hotkey execution now dispatch through a shared app-shell -legacy history bridge before the legacy `ActionManager` stack adapter -continues. The bridge also centralizes saturated history metrics so app-core -plans never receive wrapped negative counts from oversized legacy stacks. -`pano_cli plan-main-toolbar` exposes app-core planning for the live main -toolbar/status-bar shell, including open/save dialogs, undo/redo availability, -clear-history availability, clear-canvas no-canvas blocking, message-box -creation, and settings dialog routing. The toolbar test-message dialog metadata -now lives in `pp_app_core` through `plan_main_toolbar_message_dialog`, and -`pano_cli plan-main-toolbar --command message-box` exposes it for automation. -`pp_app_core` now also owns a `MainToolbarServices` executor boundary, so -`App::init_toolbar_main` dispatches through `src/legacy_app_shell_services.*` -before legacy dialogs, history/canvas adapters, and settings UI execution -continue; retained toolbar message-box creation now uses -`src/legacy_app_dialog_services.*`. -`pano_cli plan-quick-operation` exposes app-core planning for quick brush/color -slot selection versus popup opening, plus quick mini-state restore/reset -validation used by the live quick panel. Quick-panel execution now dispatches -through `QuickUiServices` in `src/legacy_quick_ui_services.*` before the legacy -`Brush`, color picker, stroke preview, and preset popup adapter continues. -`pano_cli plan-quick-slider-preview` exposes app-core planning for quick -size/flow slider preview cursor placement, RTL offset handling, and pen/line -mode tip flags; live `NodePanelQuick` slider callbacks now consume that plan -before the retained `CanvasModePen`/`CanvasModeLine` and brush-preview updates. -`pano_cli plan-tools-menu` and `pano_cli plan-tools-panel` expose app-core -planning for top-level Tools commands and floating-panel requests, including -already-visible no-ops, panel chrome metadata, shortcuts, camera reset, -grid-clear, and platform-only SonarPen gating. Direct Tools commands now -dispatch through `ToolsMenuServices` in the shared app-shell bridge before the -legacy UI/panel/canvas/platform adapters continue execution. The live animation -panel route now also checks animation panel visibility and applies animation -panel layout state instead of using the grid panel by mistake. -`pano_cli plan-canvas-camera-reset`, `pano_cli plan-canvas-view-density`, and -`pano_cli plan-canvas-view-cursor-mode` expose shared app-core canvas-view -state used by live reset-camera, viewport-density, and cursor-mode paths. -Tools reset-camera, document open/new-document reset, cloud download reset, and -options viewport/cursor callbacks now dispatch through -`src/legacy_canvas_view_services.*` before retained legacy canvas mutation and -settings writes. -The live SonarPen menu action now asks the active `PlatformServices` instance -for availability and startup, removing the local iOS branch from the Tools menu -and shared Tools executor while preserving the retained iOS bridge in the -legacy platform adapter. -Options-menu preference callbacks now dispatch UI scale, viewport scale, RTL, -VR mode, VR-controller, auto-timelapse, and cursor-mode side effects through -`AppPreferenceServices` in `src/legacy_app_preference_services.*` before -retained settings writes, recording lifecycle calls, and legacy canvas/UI -adapters continue. -VR mode start/stop now enters `App` platform wrappers that dispatch through -`PlatformServices`; the desktop runtime-selection policy in `pp_platform_api` -prefers OpenXR and marks OpenVR as a legacy fallback. Windows still reaches the -retained OpenVR bridge in `WindowsPlatformServices` until the OpenXR backend is -wired, while the legacy fallback reports unsupported VR startup on non-Windows -platforms until their shells own the service. -`pano_cli plan-about-menu` exposes app-core planning for About menu help, -about, what's-new, crash-test, and performance-test commands, including -versioned what's-new labels, diagnostic gating, and no-canvas performance-test -blocking. `pp_app_core` now also owns an `AboutMenuServices` executor boundary, -so `App::init_menu_about` dispatches through `src/legacy_app_shell_services.*` -before legacy dialogs, platform crash hooks, and Canvas performance strokes -continue. -`pp_platform_api` now owns a headless `PlatformServices` interface for -startup storage path preparation, clipboard text, cursor visibility, -virtual-keyboard visibility, UI-thread lifecycle hooks, render-context -acquire/release/present hooks, render-target binding hooks, render-capture -frame hooks, render platform hint hooks, render debug callback hooks, external -file display, file sharing, recording file cleanup, live asset/layout reload -policy, diagnostic stacktrace/crash hooks, SonarPen availability/startup, -VR mode start/stop, -image/file/save-file pickers, and directory pickers. -It also owns the SDK-free layout/asset file load policy helper used by -`LayoutManager`, so XML layout hot-reload timestamp checks no longer live in -the shared UI parser. -Windows installs an injected `WindowsPlatformServices` implementation from -`src/platform_windows/windows_platform_services.*` in `pp_platform_windows`; -other platforms still route through the debt-tracked legacy fallback adapter -now isolated in `src/platform_legacy/legacy_platform_services.*`, so behavior -is preserved while their platform shell implementations are extracted. -The central `App` header now forward-declares retained platform handles instead -of including Objective-C, Android, or GLFW SDK headers; the full platform -headers live in the legacy platform adapter until those handles move out of -`App` during Phase 6. -The retained `Asset` header is now Android-SDK-free, hides the Android asset -handles behind opaque pointers and `Asset::set_android_asset_manager`, and keeps -concrete Android asset-manager headers in `asset.cpp`/the Android entrypoint. -This reduces legacy asset I/O header coupling while the static manager bridge -and actual Android asset-reader implementation remain inside retained legacy -asset I/O. -Default canvas allocation size now dispatches through `PlatformServices`, so -`NodeCanvas` and command-line conversion creation paths preserve the desktop -1536 and WebGL 512 defaults without carrying the old `CANVAS_RES` platform -macro in `canvas.h`; DEBT-0057 tracks moving the retained WebGL policy branch -out of the legacy fallback when the Web shell owns injected services. -OpenGL runtime build-target classification now lives in `pp_renderer_gl` -through CMake-owned compile definitions and -`opengl_runtime_for_current_build()`, so `app_shaders.cpp` no longer decides -desktop GL/GLES/WebGL capability policy with local platform branches. -OpenGL extension enumeration now also lives in `pp_renderer_gl` through a -dispatch-tested `query_opengl_extensions` helper; shader startup still logs and -applies the resulting feature flags, but the GL extension query loop is no -longer app-owned. -OpenGL shader startup feature negotiation now also flows through -`pp_renderer_gl::query_opengl_capability_detection` and -`detect_opengl_feature_state`, so extension enumeration, desktop/GLES/WebGL -capability policy, and renderer-neutral feature conversion are tested together -behind the backend boundary. `App::initShaders` remains a legacy adapter that -copies the backend-owned feature snapshot into retained `ShaderManager` static -flags until `ShaderManager` itself becomes an OpenGL backend service. -Prepared-file save/download handoff is now also part of the service contract, -so iOS/Web export completion routes through `PlatformServices` after the app -writes the temporary/exported payload. -Prepared-file writable target selection now also dispatches through -`PlatformServices`, preserving the existing iOS temporary background-write path -and Web data-path synchronous write path while removing those platform branches -from `App::pick_file_save`. -PPBR and MP4 export dialogs now ask `PlatformServices` whether prepared-file -writes are used, so those dialog flows no longer spell local `__IOS__ || -__WEB__` branches for mobile/Web export handoff. -Layer and animation-frame export dialogs now also ask `PlatformServices` -whether work-directory collection exports are used, then feed that into the -pure `pp_app_core` `plan_document_export_collection_target` policy. This -removes the local iOS branches from those dialogs while preserving iOS -`work_path/doc_layers` and `work_path/doc_frames` targets in the legacy -adapter until Apple platform services are injected. -App-owned curl helpers for download, upload, and license checks now ask -`PlatformServices` whether network TLS verification is disabled, removing the -local Android branches from those helpers while preserving Android's existing -TLS-verification bypass in the legacy adapter until a network/platform service -owns cloud transport. -The remaining legacy curl sites in `Asset::open_url`, `LogRemote::net_init`, -and `NodeDialogCloud::load_thumbs_thread` now consume the shared -`pp_platform_api` default TLS policy helper instead of spelling local Android -branches; this keeps the current Android behavior aligned with -`PlatformServices` while a dedicated network service is still pending. -The Tools menu SonarPen entry now asks `PlatformServices` whether SonarPen is -available and dispatches startup through the same service, preserving the -current iOS Objective-C bridge in the legacy adapter while removing iOS branches -from `App::init_menu_tools` and `LegacyToolsMenuServices`. -App VR lifecycle start/stop now asks `PlatformServices`, preserving the current -Windows OpenVR startup/shutdown bridge as the selected legacy fallback in -`WindowsPlatformServices` while non-Windows fallback adapters keep the existing -unsupported/no-op behavior. -Canvas image export publishing and explicit persistent-storage flushes now -dispatch through `PlatformServices` too, preserving iOS photo-library export -publication and WebGL filesystem sync behavior in the legacy adapter while -removing those direct platform calls from `Canvas` and brush preset storage. -Document-browser search root selection now dispatches through -`PlatformServices`, preserving the iOS `Inbox` root in the legacy adapter while -removing the iOS-specific branch from `App::dialog_browse`. -Save, New Document, and Browse dialog working-directory picker availability and -display-path formatting now also dispatch through `PlatformServices`, removing -desktop-only branches and Win32/macOS path formatting from those UI nodes while -preserving Windows and macOS picker behavior in platform adapters. -Native UI/window state saving now dispatches through `PlatformServices`, -preserving Windows window placement persistence in `WindowsPlatformServices` -and macOS UI state persistence in the legacy adapter while removing platform -guards from `App::ui_save`. -`App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, and -`App::hideKeyboard` now dispatch through the active service without local -platform guards; unsupported platforms rely on their service no-op behavior. -The unsaved-document close prompt now requests native app/window close through -`PlatformServices`, with Windows implemented by `WindowsPlatformServices` and -macOS/Linux still handled by the legacy adapter until those platform shells -are injected. -The UI loop's per-frame platform hooks now dispatch through -`PlatformServices`: Windows stylus timeout polling and FPS-title updates live -in `WindowsPlatformServices`, while Linux FPS-title updates remain in the -legacy adapter pending Phase 6 platform shell extraction. -Canvas input tip-visibility and pressure-remap policies now also dispatch -through `PlatformServices`, removing the local iOS and Windows branches from -pen, line, and flood-fill canvas modes. -The UI thread's platform attach/detach hooks now also dispatch through -`PlatformServices`, preserving Android JNI attach/detach behavior in the -legacy adapter while removing direct Android lifecycle calls from the main app -loop. -The app's render context acquire/release/present path now dispatches through -`PlatformServices` as well. Windows owns WGL acquisition, default framebuffer -rebinding, and swap in `WindowsPlatformServices`; Apple, Android, Linux, and -WebGL behavior is preserved behind the legacy adapter until their platform -shells are injected. -Render-task default-target binding and visible main-target binding now dispatch -through `PlatformServices`, preserving the existing iOS drawable bind in the -legacy adapter while removing the iOS drawable branch from `App::draw`. -Initial render platform hints now also dispatch through `PlatformServices`, -preserving the previous Windows/macOS program-point-size and line-smoothing -enablement while removing the Windows/macOS branch from `App::init`. The -Windows service now delegates the actual OpenGL program-point-size and -line-smooth enable sequence to a tested `pp_renderer_gl` dispatch helper, so -the platform shell no longer owns those backend state tokens. The legacy -non-Windows fallback now consumes the same helper for retained macOS render -platform hints, keeping the debt-tracked adapter as a thin GL call bridge -rather than a source of backend state policy. -Windows OpenGL debug callback setup now dispatches through `PlatformServices`, -moving Win32 console coloring and debug-break callback behavior into -`WindowsPlatformServices` while keeping other platform adapters as no-ops; the -debug-output/debug-output-synchronous state enable sequence is now a tested -`pp_renderer_gl` backend helper consumed by the Windows service. -Initial PanoPainter OpenGL depth/blend startup state is now represented and -applied by tested `pp_renderer_gl` startup-state contracts; `App::init` -delegates to the backend dispatch path instead of hard-coding the policy or -operation order, and its retained callback endpoints now reuse the shared UI -GL bridge instead of a local raw callback cluster. -OpenGL runtime version/vendor/renderer/GLSL string queries now also use a -tested `pp_renderer_gl` dispatch contract, leaving `App::init` to log the -result while the backend owns the query set and order. The Windows entrypoint -also uses that contract for early context logging and renderer-name window -title construction before replacing the temporary WGL context. Retained runtime -info and extension query callback endpoints now share -`legacy_gl_runtime_dispatch`. The global OpenGL error drain and Windows debug -message callback installation now also route through that retained runtime -dispatch, keeping those entrypoints beside the other runtime callbacks until -renderer/runtime services own context diagnostics. -The default app clear color and color-buffer clear operation now dispatch -through `pp_renderer_gl` as well, moving another direct OpenGL operation out -of `App::clear` while preserving the current gray clear behavior; the live -callback endpoints now share `legacy_ui_gl_dispatch`. -Main app UI viewport and scissor execution now dispatch through tested -`pp_renderer_gl` viewport/scissor contracts, leaving `App::draw` and UI node -clipping to provide rectangles while the backend owns scissor-state tokens and -the live OpenGL call sequence. The retained viewport/scissor callback endpoints -now share `legacy_ui_gl_dispatch`. -VR UI framebuffer viewport and scissor-test setup now also consumes those -`pp_renderer_gl` contracts, keeping desktop and VR UI rendering aligned while -the desktop XR path moves from the retained OpenVR app path toward OpenXR; its -retained callback endpoints now reuse the shared UI GL bridge. -VR draw blend/depth state snapshots, transitions, restore, and depth-buffer -clears, active texture unit switches, and fallback 2D texture unbinds now use -generic tested `pp_renderer_gl` capability query/apply, clear, active-texture, -and texture-bind dispatch contracts, reducing direct OpenGL execution in the -retained VR app path without changing state restore behavior. The remaining -retained VR draw adapter endpoints for these calls now share -`legacy_ui_gl_dispatch`. -The retained `gl_state` save/restore utility now snapshots and restores through -tested `pp_renderer_gl` saved-state dispatch contracts, covering capability -state, viewport, clear color, framebuffer/program bindings, active texture, -2D texture slots, samplers, and cube-map binding without changing the legacy -utility's public fields. -Legacy `Texture2D` allocation, binding, deletion, mipmap generation, region -update, and framebuffer readback now execute through tested `pp_renderer_gl` -texture dispatch contracts. This keeps the app API stable while moving another -resource lifecycle path behind the renderer backend boundary. -Legacy `RTT` resize/copy blits and byte/float framebuffer readbacks now execute -through tested `pp_renderer_gl` framebuffer dispatch contracts with draw/read -framebuffer binding restore handled by the backend helper. -Legacy `RTT::create`/`RTT::destroy` now route render-target texture allocation, -default texture parameter setup, optional depth renderbuffer allocation, -framebuffer color/depth attachment, framebuffer status checks, binding restore, -and resource deletion through tested `pp_renderer_gl` dispatch helpers. -Legacy `RTT` also exposes an RGBA8 region-readback helper that uses the same -backend framebuffer readback dispatch; canvas pick/history/snapshot and -transform history paths now call that helper instead of binding an RTT and -calling `glReadPixels` directly. -Legacy `RTT` now also exposes an RGBA8 region-update helper that routes dirty -rectangle texture writes through the tested `pp_renderer_gl` texture-update -dispatch; canvas undo, layer restore, and flood-fill apply paths now call it -instead of issuing direct `glTexSubImage2D` calls. -Retained `PBO` recording readbacks now route pixel-buffer allocation, -framebuffer readback, map, unmap, and deletion through tested -`pp_renderer_gl` dispatch helpers; recording thread ownership, progress UI, and -MP4 execution remain tracked by DEBT-0037. -Legacy `RTT::bindFramebuffer` and `RTT::unbindFramebuffer` now use tested -`pp_renderer_gl` draw/read framebuffer binding snapshot and restore contracts, -moving render-target pass entry/exit state management behind the backend. -Legacy `RTT::clear`, `RTT::clear_mask`, `RTT::bindTexture`, and -`RTT::unbindTexture` now dispatch through `pp_renderer_gl` clear, -color-write-mask restore, and texture-bind contracts, keeping render-target -utility operations behind the backend boundary. The retained RTT clear and -masked-clear callback endpoints now share `legacy_ui_gl_dispatch`. -Windows RenderDoc frame capture hooks now also dispatch through -`PlatformServices`, keeping capture integration in the platform service while -leaving non-Windows adapters as no-ops. -Startup data/work/recording/temp path preparation now dispatches through -`PlatformServices`, with Windows creating the Documents/PanoPainter folder -tree in `WindowsPlatformServices` and Apple/Linux/Web behavior preserved in the -legacy adapter until platform shells are injected. -Recording clear now asks `PlatformServices` whether the platform owns recorded -file deletion and dispatches the cleanup through the service, preserving the -current Apple recorded-frame cleanup while removing Apple-specific file cleanup -guards from `App::rec_clear`. -The UI loop now asks `PlatformServices` whether live shader/layout reloading -should run, preserving the previous Windows/macOS reload behavior while removing -the direct `(_WIN32 || __OSX__)` guard from `App::ui_thread_main`. -Layout XML reload read/skip decisions now go through `pp_platform_api` as well, -preserving desktop mtime-based reloads and non-desktop single-load behavior -while removing the direct Windows/macOS guard from `LayoutManager::load`. -`App::stacktrace` and `App::crash_test` now dispatch through `PlatformServices`, -with Windows retaining the debug-break crash hook and the legacy adapter -preserving Apple stacktrace/crash and Android crash-test behavior. -`pano_cli plan-cloud-upload` exposes the app-core cloud upload decision used by -the live cloud upload command for missing-canvas, new-document warning, publish -prompt, and dirty-document save-before-upload states before legacy UI, canvas, -and network execution continue. -`pano_cli plan-cloud-upload-all` exposes the app-core bulk upload file-count, -progress UI, and progress-total clamping decision used by the live upload-all -command before legacy asset listing, OpenGL context guard, progress UI, and -network upload execution continue. -`pano_cli plan-cloud-browse` exposes the app-core cloud browse and selected -download decisions used by the live cloud browse command before legacy dialog, -network download, canvas project-open, layer UI, and action-history execution -continue. -Cloud upload, bulk upload, and browse/download live execution now flows through -the `CloudServices` app-core boundary and `src/legacy_cloud_services.*`, keeping -`App::cloud_upload`, `App::cloud_upload_all`, and `App::cloud_browse` as thin -planning adapters while legacy save, progress UI, network, dialog, canvas-open, -layer-refresh, and action-history work remains tracked under `DEBT-0038`. -Cloud warning/publish/success prompts, bulk progress, and download-progress -prompt creation now route through `src/legacy_app_dialog_services.*`, while -cloud prompt/progress lifetime and network/document execution remain legacy. -The app-owned curl upload/download/license helpers now consume the platform TLS -verification policy through `PlatformServices`, and the retained Asset, -LogRemote, and cloud browse-dialog curl sites consume the same default platform -policy helper; retained cloud/network execution remains tracked under -`DEBT-0038`. -`pp_app_core` now also owns tested cloud transfer request and progress planning -through `plan_cloud_download_transfer`, `plan_cloud_upload_transfer`, and -`plan_cloud_transfer_progress`. Live `App::download` and `App::upload` consume -those plans before retained CURL setup, including missing endpoint rejection, -progress-callback enablement, TLS-verification policy, and zero/overrun progress -guards; `pano_cli plan-cloud-transfer` exposes the same path for automation. -Actual CURL ownership, upload form construction, response/error handling, -progress UI, and downloaded-project execution remain tracked under -`DEBT-0038`. -`pano_cli parse-layout` exercises the XML layout path. Continue expanding -document behavior toward legacy Canvas parity and then port OpenGL classes -behind the renderer boundary. -Brush preset-list add/select/move/remove/clear decisions now consume -`pp_app_core` through `NodePanelBrushPreset` and -`pano_cli plan-brush-preset-list`, so preset UI callbacks share tested -headless index/selection planning. Live preset-list execution now also -dispatches through `BrushPresetListServices` and the shared app-core executor -before the retained legacy bridge mutates child nodes. The remaining direct -`NodePanelBrushPreset` child-node execution, legacy `Brush` cloning, friend -adapter, and preset save/reload behavior stay tracked under `DEBT-0023`. -`App::open_document` now routes through the app-core document-open executor and -`src/legacy_document_open_services.*`, preserving ABR/PPBR import prompts, -unsaved-project discard prompts, project open, layer refresh, title updates, -and history clearing while those live effects remain tracked under -`DEBT-0039`. Accepted ABR/PPBR import prompts now delegate import execution to -the app-core brush package import executor and -`src/legacy_brush_package_import_services.*`, preserving detached legacy preset -panel import threads while retained brush asset execution remains tracked under -`DEBT-0048`. -The retained ABR/PPBR import prompt wiring in `src/legacy_document_open_services.*` -now also routes through a focused helper, so the remaining document-open bridge -debt is further concentrated on unsaved-project prompts, project-open -execution, layer refresh, title updates, and retained history services. -The retained document-browse button wiring in `App::dialog_browse()` now also -routes through a focused helper, so the remaining document-open bridge debt is -further concentrated on unsaved-project prompts, project-open execution, layer -refresh, title updates, and retained history services. -The retained unsaved-project discard prompt wiring in -`LegacyDocumentOpenServices::prompt_discard_unsaved_project()` now also routes -through a focused helper, so the remaining document-open bridge debt is -further concentrated on project-open execution, layer refresh, title updates, -and retained history services. -The retained project-open success/failure handling in `open_legacy_project()` -now also routes through a focused helper, so the remaining document-open -bridge debt is further concentrated on layer refresh, title updates, and -retained history services. -The retained project-open layer/title update work and failure-path error -dialog in `handle_open_legacy_project_result()` now also route through focused -helpers, so the remaining document-open bridge debt is further concentrated on -retained history services. -The retained project-open history-planning call in `open_legacy_project()` -now also routes through a focused helper, so the remaining document-open -bridge debt is further concentrated on retained history services. -The retained history adapter now also reuses the shared -`src/legacy_history_services.*` helper from both document-open and -document-session bridges, so the remaining bridge debt is further concentrated -on retained history services. -`App::request_close`, `App::save_document`, and -`App::continue_document_workflow_after_optional_save` now route through -app-core document-session executors and `src/legacy_document_session_services.*`, -preserving close prompts, save dialogs, save-version routing, existing-project -save execution, and dirty-workflow save-before-continue prompts while retained -legacy UI/canvas behavior remains tracked under `DEBT-0040`. -The retained document-session prompt boxes now consume a pure prompt catalog for -close-unsaved, save-before-workflow, new-document overwrite, Save As overwrite, -and save-error metadata; `pano_cli plan-document-session-prompt` exposes the -same titles, messages, button captions, and cancel visibility for automation. -Close-unsaved, save-before-workflow, new-document overwrite, and Save As -overwrite prompt creation now also goes through -`src/legacy_app_dialog_services.*` before the document-session bridge attaches -its legacy callbacks. -The retained new-document and Save As overwrite prompt wiring in -`src/legacy_document_session_services.*` now also routes through a focused -helper, so the remaining document-session bridge debt is further concentrated -on close prompts, native close requests, save-version routing, app document -field mutation, and keyboard/dialog cleanup. -The retained save-before-continue workflow prompt wiring in -`src/legacy_document_session_services.*` now also routes through a focused -helper, so the remaining document-session bridge debt is further concentrated -on close prompts, native close requests, save-version routing, app document -field mutation, and keyboard/dialog cleanup. -The retained close-unsaved prompt wiring in -`src/legacy_document_session_services.*` now also routes through a focused -helper, so the remaining document-session bridge debt is further concentrated -on save-before-workflow prompts, save-version routing, app document field -mutation, and keyboard/dialog cleanup. -The retained Save Version execution in `src/legacy_document_session_services.*` -now also routes through a focused helper, so the remaining document-session -bridge debt is further concentrated on close prompts, save dialogs, -overwrite prompts, and keyboard/dialog cleanup. -The retained Save dialog button wiring in `src/app_dialogs.cpp` now also -routes through a focused helper, so the remaining document-session bridge debt -is further concentrated on close prompts, save-version routing, app document -field mutation, and keyboard/dialog cleanup. -The retained Save Version dialog wiring in `src/app_dialogs.cpp` now also -routes through a focused helper, so the remaining document-session bridge debt -is further concentrated on close prompts, save dialogs, app document field -mutation, and keyboard/dialog cleanup. -`App::dialog_newdoc` now routes accepted new-document plans through the -app-core new-document executor and `src/legacy_document_session_services.*`, -preserving target overwrite prompts, legacy canvas resize/layer setup, history -clearing, title updates, dirty/new-document flag mutation, and keyboard/dialog -cleanup while retained execution remains tracked under `DEBT-0041`. -`App::dialog_save` and `App::dialog_save_ver` now route accepted Save As and -Save Version plans through app-core document file/version save executors and -`src/legacy_document_session_services.*`, preserving overwrite prompts, -legacy `Canvas::project_save`, app document field updates, title updates, and -keyboard/dialog cleanup while retained execution remains tracked under -`DEBT-0042`. -`App::dialog_export`, `App::dialog_export_layers`, -`App::dialog_export_anim_frames`, `App::dialog_export_depth`, and -`App::dialog_export_cube_faces` now route accepted file/stem/collection and -named export work through app-core document export executors and -`src/legacy_document_export_services.*`; layer/frame collection export target -destination is now planned in `pp_app_core` and selected by `PlatformServices`, -preserving existing platform messages, directory creation, picker-selected -stems, Web prepared-file handoff, and legacy `Canvas` export calls while -retained execution remains tracked under `DEBT-0043`. -`App::dialog_timelapse_export` and `App::dialog_export_mp4` now route -picker-selected MP4 export paths through the app-core document video export -executor and `src/legacy_document_export_services.*`, preserving mobile/Web -suggested-name save callbacks, desktop worker-thread timelapse export, -`App::rec_export`, animation `Canvas::export_anim_mp4` dispatch, and existing -success messages while retained execution remains tracked under `DEBT-0044`. -`App::dialog_ppbr_export` now routes picker-selected PPBR brush package exports -through the app-core brush package export executor and -`src/legacy_brush_package_export_services.*`, preserving dialog metadata -collection, legacy `Image` header ownership, desktop worker-thread export, -mobile/Web save completion, and `NodePanelBrushPreset::export_ppbr` while -export success-dialog metadata now comes from `pp_app_core` and is exposed by -`pano_cli plan-brush-package-export`. Retained execution remains tracked under -`DEBT-0047`. -PPBR package header validation and export target/data-directory planning now -live in `pp_assets::brush_package` and are exercised by -`pp_assets_brush_package_tests` plus `pano_cli plan-brush-package-export`. -The macOS-specific PPBR preview data-directory override now dispatches through -`PlatformServices`, so `NodePanelBrushPreset::export_ppbr` no longer spells a -local `__OSX__` branch while the actual PPBR serialization path remains -legacy-owned. -The live PPBR import/export path consumes those helpers, while legacy -Serializer/Image payload reading, stroke preview generation, preset storage, -and check remain tracked under `DEBT-0047` -and `DEBT-0049`. The compatibility fixture now spells out the observed legacy -cases more directly (`0.0`, `0.1`, `0.2`, `1.1`, `2.1`, rejected `1.2`), but -the parser still keeps the permissive rule until a stricter supported-version -policy is proven safe. -ABR and PPBR import image target planning for brush tips and patterns also now -uses `pp_assets::brush_package`, so the legacy preset panel no longer owns the -`data/brushes`, `data/brushes/thumbs`, `data/patterns`, and -`data/patterns/thumbs` path construction rules. Actual ABR/PPBR parsing, -duplicate policy, preset creation, save/reload, and progress/UI refresh remain -legacy-owned under `DEBT-0048`. - -Implementation tasks: - -- Extract components in this order: - 1. `pp_foundation` - 2. `pp_assets` - 3. `pp_paint` - 4. `pp_document` - 5. `pp_renderer_api` - 6. `pp_renderer_gl` - 7. `pp_paint_renderer` - 8. `pp_ui_core` - 9. `pp_panopainter_ui` - 10. `pp_platform_api` - 11. `pp_platform_*` - 12. `panopainter_app` -- Remove renderer/platform dependencies from pure headers first, especially: - - `Brush` - - document/layer model - - serializer - - UI core headers -- Make UI lifetime safety a first-class extraction criterion: - - define a `pp_ui_core` ownership model for the retained `Node` tree - - replace raw callback targets with scoped connections or checked handles - - move panel/dialog side effects toward app-core command dispatch - - test destroy-during-callback, capture release, popup close, and layout - reload mutation cases before replacing retained UI nodes wholesale -- Keep facade shims where needed, but debt-track every shim. -- Avoid large behavioral rewrites during extraction. -- Each extracted component gets a focused test suite before moving to the next. - -Gate: - -- Old app still launches. -- Component tests pass after every extraction. -- No undocumented stubs or shortcuts. - -## Phase 5: Renderer Boundary And OpenGL Parity - -Goal: make OpenGL an implementation detail and establish parity tests before -adding new backends. - -Status: started. `pp_renderer_api` exists as a headless renderer-neutral target -with renderer feature flags, explicit texture usage flags, texture descriptor, byte-size, viewport, -mesh, readback bounds, command context, render device, shader program -descriptor, mesh, render target, readback byte-size helpers, -texture mip-level validation, resource debug-label validation, -texture-upload/readback command validation, -mipmap-generation command validation, texture-state transition validation, frame-capture byte-size helpers, -readback/copy/frame-capture/blit descriptor validation, frame-capture command validation, render-target blit validation, texture-slot -binding validation, blend-state validation, scissor-state validation, -depth-state validation, trace marker/scope validation, sampler-state validation, -texture/mesh/shader resource-label validation, -recording-device reuse/reset validation, and the canonical PanoPainter shader -catalog now consumed by the legacy OpenGL app initialization path. -`pp_renderer_gl` now exists as the first OpenGL backend library and owns pure -OpenGL capability detection for framebuffer fetch, map-buffer alignment, and -float texture support. It also owns the OpenGL texture upload-type mapping used -by legacy `Texture2D` and `RTT` creation, RGBA pixel-format mapping used by -`RTT` texture allocation, plus image channel-count to texture format mapping -for `Texture2D` image uploads and framebuffer status naming for `RTT` and -`Texture2D` diagnostics. Live stroke rasterization has started moving toward renderer -services: `Canvas::stroke_draw` now consumes a named -`CanvasStrokeRasterizationPlan` through a legacy adapter boundary for -destination feedback/copy decisions, and `pp_paint_renderer` owns pure -stroke material/pass planning for pattern, mixer, dual-brush, and final -composite texture/uniform intent. Live `Canvas::stroke_draw`, stroke commit, -and draw-merge paths consume that material plan for destination feedback, -each-sample pattern, dual-brush, and final composite decisions, and -`NodeStrokePreview` now consumes the same plan for preview material decisions. -Canvas and `NodeStrokePreview` stroke sample execution also delegate optional -destination copy, brush vertex upload, brush-shape draw, and destination unbind -through `execute_legacy_canvas_stroke_sample`, creating the first retained -OpenGL stroke execution service seam. Live stroke `kShader::Stroke` binding, -setup, blend, pattern, and per-sample uniform writes now pass through -`legacy_canvas_stroke_shader_services.h`. Live draw-merge and preview final -stroke composite `kShader::CompDraw` setup now pass through -`legacy_canvas_stroke_composite_services.h`, and `Canvas::stroke_commit` now -reuses that same composite service for commit-time final stroke compositing. -Stroke padding and commit dilate `kShader::StrokePad`/`kShader::StrokeDilate` -setup now pass through `legacy_canvas_stroke_edge_services.h`, leaving -RTT/texture ownership, checkerboard/non-stroke composite shaders, and retained -callback execution under `DEBT-0036`. Stroke sample copy bounds, live face -dirty-box accumulation, and preview padding region math now live as tested -`pp_paint_renderer` planners while retained Canvas/preview code still owns GL -ordering, RTT/texture binding, history mutation, and final dirty state storage. -`NodeStrokePreview` also now consumes a tested preview composite sequence plan -for mixer intent, and retained preview background capture, composite input -binding/draw, and preview texture copy are centralized behind -`legacy_canvas_stroke_preview_services.h`. Preview mixer-pass `CompDraw` setup -now also reuses `legacy_canvas_stroke_composite_services.h`. -It also owns renderer API texture-format to -OpenGL internal/pixel/component token mapping, including depth-stencil formats, -for future backend texture objects. `Texture2D` 2D texture binding, upload, -mipmap generation, framebuffer readback setup, and update component-type tokens -now delegate to `pp_renderer_gl`; retained `Texture2D`, `TextureCube`, and -`RTT` texture allocation, bind, parameter, update, mipmap, and delete dispatch -now share the retained `legacy_gl_texture_dispatch` raw callback bridge. -`TextureCube` cube-map binding, allocation -face targets, RGBA allocation format, and unsigned-byte component type also -delegate to `pp_renderer_gl`. RGBA8/RGBA32F readback formats, checked byte-count math, PBO -pixel-buffer target/usage/access tokens, and PBO allocation/readback/map/unmap/delete -dispatch sequences used by retained recording readbacks now live in -`pp_renderer_gl`; retained `PBO` allocation, framebuffer readback, map, unmap, -and delete dispatch now share the retained `legacy_gl_pixel_buffer_dispatch` -raw callback bridge. Retained `Sampler` create, parameter, border-color, bind, -and unbind dispatch now share the retained `legacy_gl_sampler_dispatch` raw -callback bridge. The framebuffer blit color mask and linear/nearest -filter tokens used by `RTT::resize` and `RTT::copy`, renderer API blit-filter -to OpenGL token mapping, plus the default -render-target texture parameters and parameter dispatch, texture/renderbuffer -targets, depth format, framebuffer targets, binding queries, attachment points, -render-target framebuffer allocation/delete, binding restore, and completion -status used by `RTT::create`/`RTT::destroy` and framebuffer bind/restore paths, -also live in `pp_renderer_gl`; retained `Texture2D` readback and RTT -framebuffer allocation, deletion, bind/restore, blit, readback, and PBO -readback dispatch now share the retained `legacy_gl_framebuffer_dispatch` raw -callback bridge. Depth renderbuffer allocation/storage/delete and -framebuffer depth attach/detach sequences used by retained `RTT` and canvas -object-drawing helpers now execute through tested `pp_renderer_gl` dispatch -contracts and share the retained `legacy_gl_renderbuffer_dispatch` raw callback -bridge. 2D framebuffer-to-texture -copies used by canvas, transform, layer-conversion, panorama UI, brush preview, -and CanvasLayer cube-face generation paths now route through a tested -`pp_renderer_gl` copy dispatch via the retained target-aware framebuffer-copy -utility bridge. The copy bridge remains retained until renderer services own -cube and 2D framebuffer copy commands under `DEBT-0036`. RTT render-target clear, masked color clear -with color-write-mask restore, and texture bind/unbind dispatch now execute -through `pp_renderer_gl`; renderer API render-pass color/depth/stencil -clear-mask and clear-value mapping, and color-write-mask query tokens also live -there. `RTT` no longer spells GL enum names directly. -Renderer API primitive-topology to OpenGL draw-mode mapping, mesh index-type -and primitive-mode decisions used by legacy `Shape` drawing, plus Shape buffer -targets, static upload usage, and vertex attribute component/normalization -tokens, also live in `pp_renderer_gl`. Retained `Shape`, `TextMesh`, and -`NodeColorWheel` mesh buffer/VAO creation, dynamic vertex/index uploads, -fill/stroke/text draws, and buffer/VAO deletion now execute through tested -`pp_renderer_gl` dispatch contracts via the shared retained -`legacy_gl_mesh_dispatch` raw callback bridge. The -PanoPainter cube-face to -OpenGL texture-target mapping used by `TextureCube` also lives in -`pp_renderer_gl`. The legacy app delegates extension, upload-format, -framebuffer diagnostic, framebuffer blit, render-target setup, clear-state, -2D/cube texture setup, mesh draw-mode, and cube-face texture-target -interpretation to that backend library. Sampler wrap, min/mag filter, and -desktop border-color parameter mapping for legacy `Sampler` also lives in -`pp_renderer_gl`. Renderer API sampler filter/address-mode to OpenGL token -mapping, including mirrored-repeat, and aggregate renderer API sampler-state to -OpenGL min/mag/wrap mapping are also tested there. The PanoPainter shader attribute -binding catalog, shader stage tokens, compile/link status queries, active-uniform -count query, and matrix-uniform transpose token also live in `pp_renderer_gl` -and are consumed by legacy `Shader` creation; retained shader source -compilation/deletion, program attach/link/use/delete, attribute rebinding and -location lookup, active-uniform enumeration, uniform-location discovery, and -uniform writes now share the retained `legacy_gl_shader_dispatch` raw callback -bridge. Renderer API blend factor/op to -OpenGL token mapping also lives in `pp_renderer_gl`, with explicit support flags -so `GL_ZERO` remains distinguishable from unsupported enum values. Aggregate -renderer API blend-state to OpenGL enable/factor/equation/color-mask mapping, -depth compare-op to OpenGL depth-function mapping, and aggregate renderer API -depth-state to OpenGL enable/write/compare mapping also live in -`pp_renderer_gl`. Shader uniform hashing, catalog -validation, active-uniform mapping, and the legacy uniform uniqueness check now -delegate to `pp_renderer_gl` as well. `Shader` no longer spells GL enum names -directly. App OpenGL initialization debug severity, debug output, GL info -string, renderer API viewport/scissor rect conversion, default -depth/program-point/line-smooth state, blend factor/equation, and UI -render-target RGBA8 format tokens are now also cataloged and tested in -`pp_renderer_gl`; the legacy convert command now applies its depth, -program-point-size, source-alpha blend, and add-equation startup policy through -a tested backend dispatch contract, and the resize path consumes the same -backend-owned mapping. App clear color-buffer masks, default framebuffer -binding, scissor state, and sampler filter/wrap tokens now share that backend -mapping too. OpenGL extension enumeration query tokens used before runtime -capability detection also live in `pp_renderer_gl`. Legacy font atlas texture -formats, text mesh buffer targets, attribute component/normalization, draw -primitive/index type, upload usage, and active texture unit selection also -delegate to `pp_renderer_gl`; text mesh buffer/VAO creation, deferred index -and vertex uploads, indexed draw calls, and text draw texture-unit activation -now execute through the same tested dispatch contracts used by `Shape` through -`legacy_gl_mesh_dispatch`. -Canvas undo/redo dirty-region texture updates and readbacks now also execute -through retained `RTT` helpers backed by `pp_renderer_gl`, including 2D texture -target, dirty-region offsets, RGBA pixel format, and unsigned-byte component -type mapping. Canvas stroke commit, thumbnail generation, and object-draw -history paths now query saved blend state through the same tested capability -state dispatch before restoring it. -`NodeViewport` preview rendering now also delegates viewport query, -clear-color query, color-buffer clear mask, viewport execution, color clear, -clear-color restore, and blend-state execution through the shared -`legacy_ui_gl_dispatch` adapter and `pp_renderer_gl` mappings. -`NodeImageTexture` preview drawing now delegates its fallback 2D texture bind -and blend-state execution through the shared UI GL adapter. -`NodeImage` drawing and remote-image texture creation now delegate mipmapped -sampler filters, blend-state execution, and RGBA8/RGBA texture format mapping -to `pp_renderer_gl`. -`NodeColorWheel` triangle-buffer setup and draw-state handling now delegate -array-buffer, static-upload, vertex-attribute, primitive-mode, and blend-state -execution to `pp_renderer_gl`. -Simple UI text, text-input, border, scroll, and animation timeline draw paths -now also execute blend-state changes through the shared UI GL adapter. -Canvas layer cube/equirect generation, clear, restore, and snapshot paths now -also delegate cube/2D texture targets, active texture units, blend/clear state, -viewport execution, cube texture binding, color-buffer clears, clear-color -query/restore, and RGBA8 read/write pixel mapping to `pp_renderer_gl`. Its -active-texture, cube-texture binding, viewport, blend capability, clear-color, -and color-buffer clear adapter endpoints now share `legacy_ui_gl_dispatch`; -the cube-face framebuffer-to-texture copy now uses the shared retained -target-aware utility bridge and remains tracked under DEBT-0036 until a -renderer-owned cube copy command replaces it. -`NodePanelGrid` heightmap preview and lightmap baking now delegate texture -readback formats, sampler filters, depth/blend state, depth clears, viewport -queries, color-mask booleans, active texture units, and float render-target -formats to `pp_renderer_gl`, and its CPU lightmap row dispatch now uses the -shared legacy `parallel_for` helper rather than platform-specific worker APIs. -Its live heightmap draw and bake paths now execute depth/blend state changes, -depth clears, color-write-mask toggles, active texture selection, and bake -viewport changes through tested `pp_renderer_gl` dispatch adapters. Its desktop -texture-resize path now reuses `Texture2D::get_image()`, so grid texture -readback also goes through the tested framebuffer-backed texture readback -dispatch instead of direct `glGetTexImage`. Grid depth-state snapshots and sun -overlay viewport queries now also use tested backend query dispatch instead of -direct state reads. -Legacy `util.cpp` OpenGL error naming, framebuffer-to-texture copy helper, and -`gl_state` save/restore now delegate error codes, state queries, framebuffer -targets, texture binding targets, active texture units, shader program use, and -sampler binding to `pp_renderer_gl` through the shared retained bridge headers -instead of owning local raw OpenGL callbacks. -`NodeStrokePreview` brush preview rendering now delegates depth/scissor/blend -state, tested viewport/clear-color query dispatch, clear-color restore, active -texture unit execution, fallback 2D texture unbinds, 2D texture targets, copy -targets, sampler filters/wraps, and destination-feedback copy/fetch decisions -to `pp_renderer_gl` and `pp_paint_renderer`. Its live stroke-mixer and -brush-preview viewport, scissor, and depth/blend state changes now also -execute through tested `pp_renderer_gl` dispatch via the shared -`legacy_ui_gl_dispatch` bridge. -Retained `Canvas` stroke/thumbnail/object/export paths and `NodeCanvas` -panorama rendering use the same tested active-texture dispatch for their -texture-unit switches, and their live viewport, scissor, and generic -depth/blend/scissor capability changes and `NodeCanvas` capability-state -snapshots now route through the same backend dispatch contracts. Retained -`Canvas` stroke draw/commit, thumbnail -generation, object drawing, and `LayerFrame::clear` saved viewport/clear-color -query plus clear-color restore paths also use tested `pp_renderer_gl` dispatch -helpers. The retained `Canvas` active-texture, fallback texture unbind, -viewport/scissor execution, viewport and clear-color query, clear-color -restore, and capability query/apply adapter endpoints now share -`legacy_ui_gl_dispatch`; Canvas and RTT depth renderbuffer -allocation/attachment/delete now share `legacy_gl_renderbuffer_dispatch` while -resource lifetime ownership remains retained under DEBT-0036. -`NodeCanvas` saved viewport/clear-color query, density target color clear, and -clear-color restore paths use the same helpers. `NodeCanvas` and -`NodeStrokePreview` now share that retained UI GL dispatch bridge for -active-texture, fallback texture unbind, viewport/scissor, clear-color, -color-buffer clear, and capability query/apply adapter endpoints. -Retained `CanvasMode` overlay, mask, transform, and canvas-tip pick paths now -also use the same bridge for active-texture, capability query/apply, viewport, -read-framebuffer query, and RGBA8 pixel readback adapter endpoints while their -mode logic remains in the legacy UI implementation. -`NodePanelGrid` heightmap draw and bake setup now also share that bridge for -active-texture selection, depth/blend capability query/apply, viewport -query/execution, depth clears, and color-write-mask adapter endpoints. -Desktop HMD eye rendering now routes eye framebuffer viewport changes through -the tested `pp_renderer_gl` viewport dispatch while platform VR SDK bridges -remain isolated for later platform-shell extraction. -Legacy `Texture2D`, `TextureManager`, `Sampler`, and `RTT` public headers no -longer expose raw OpenGL enum defaults; default texture formats, sampler -filters/wraps, and render-target formats are resolved through backend-owned -overloads. -The Windows entrypoint now delegates generic OpenGL error-code/info-string -tokens, runtime string query ordering, and WGL core-context/pixel-format -attribute catalogs to `pp_renderer_gl`. -The headless OpenGL command planner now consumes `pp_renderer_api` recorded -commands and maps render-pass clear masks/values, viewport/scissor state, -blend/depth/sampler state, texture formats, primitive modes, draw counts, and -blit filters into GL-facing planned command data with explicit unsupported-token -rejection before a runtime GL context is needed. It also plans whole recorded -command streams, preserving per-command planned data while counting render -passes, draws, shader binds, shader uniforms, texture/sampler binds, texture -uploads, mipmap generation, texture transitions, texture copies, texture -readbacks, frame captures, passthrough commands, trace commands, unsupported -commands, and render-pass ordering errors such as state changes outside a pass, -nested passes, texture I/O or blits inside a pass, and unclosed passes. It -also validates executable command dependencies, including shader-before-uniform -and shader-plus-mesh before draw within each render pass, and rejects invalid -texture/sampler bind slots in malformed recorded streams. -The renderer-neutral API now also plans complex paint feedback strategies for -future stroke/layer compositing work: framebuffer-fetch-capable backends can -read destination color directly, while other backends must use ping-pong render -targets backed by texture copy or render-target blit support. This is exposed -through `pano_cli plan-paint-feedback` and tracked by DEBT-0036 until the live -paint renderer consumes the plan. -`pp_paint_renderer` now consumes that lower-level feedback planner through a -stroke composite plan that decides whether a stroke/layer blend can use -fixed-function blending or needs framebuffer-fetch/ping-pong destination -feedback. `pano_cli plan-stroke-composite` exposes the same decision for -automation, including layer blend, stroke blend, dual-brush, and pattern-blend -inputs. Live `Canvas::draw_merge` now uses this planner for its existing -shader-blend gate for layer and primary-brush blend modes while preserving the -legacy trigger policy; actual canvas stroke execution, dual-brush feedback, and -pattern feedback are still legacy OpenGL and remain tracked by DEBT-0036 until -the app calls through renderer services for the whole compositing path. -`pp_paint_renderer::plan_canvas_blend_gate` now also owns the compatibility -mapping from persisted layer and brush blend indices to that planner, including -fallback behavior for unknown nonzero indices. Both `Canvas::draw_merge` and -`NodeCanvas` panorama rendering consume that shared gate, so the live app no -longer has duplicate local blend-trigger logic or duplicate destination-copy -versus framebuffer-fetch decisions in those paths. -The OpenGL shader initialization path now stores a renderer-neutral -`RenderDeviceFeatures` snapshot converted by `pp_renderer_gl`, and those live -canvas gates consume that snapshot instead of rebuilding feature flags from -individual `ShaderManager` extension booleans. -`RenderDeviceFeatures` now carries the float32-linear-filtering bit as well, -so the canvas stroke texture format decision, renderer diagnostics, and grid -lightmap/bake target selection all consume the renderer-neutral feature -snapshot instead of reading `ShaderManager::ext_*` flags directly. The retained -extension booleans are now limited to the shader-manager compatibility adapter -and legacy logging. -`pp_paint_renderer::plan_canvas_stroke_feedback` now models the current stroke -shader's required destination feedback without changing the legacy shader math. -Live `Canvas::stroke_draw` consumes that plan for main-brush, dual-brush, and -stroke-pad destination-copy versus framebuffer-fetch decisions. Thumbnail layer -blending now consumes the same canvas destination-feedback decision for its -legacy `TextureBlend` path. `NodeStrokePreview` uses the same destination -feedback plan for its live brush-preview copy/fetch decision. The full -thumbnail and brush-preview compositing execution remains legacy OpenGL until a -fuller live paint-renderer boundary can take over. -The existing renderer classes are not yet fully -behind the renderer interfaces. - -Implementation tasks: - -- Introduce renderer interfaces: - - `IRenderDevice` - - `ITexture2D` - - `IRenderTarget` - - `IShaderProgram` - - `IMesh` - - `ICommandContext` - - `IReadbackBuffer` - - `IRenderTrace` -- Port current renderer classes behind OpenGL backend types: - - `RTT` - - `Texture2D` - - `Sampler` - - `ShaderManager` - - `Shape` -- Keep OpenGL runtime capability decisions in `pp_renderer_gl` with headless - tests before moving GL object lifetimes behind backend types. -- Preserve current shader behavior and asset paths. -- Add deterministic GPU tests: - - clear - - blit - - texture upload/download - - stroke composite - - erase - - layer blend - - equirect export - - readback bounds -- Add CPU reference tests for final RGBA and stroke-alpha blend modes. -- Compare GPU output to golden/reference data with explicit tolerances. - -Gate: - -- OpenGL readbacks match golden data on Windows and Linux. -- Mobile/WebGL compile gates remain green. - -## Phase 6: Platform Alignment - -Goal: every supported platform consumes the same component targets. - -Status: started. Root CMake configure presets now have matching build presets -for Windows VS 2026/default, Windows clang-cl ASan, Linux clang, Android -standard x64/arm64, Android Quest arm64, Android Focus/Wave arm64, -Emscripten/WebGL, macOS, iOS device, and iOS simulator. `platform-build` -automation now builds the current headless component matrix, including -`pp_platform_api`, `pp_app_core`, platform API tests, brush-package tests, and -the current app-core startup/frame/shutdown/file/document/brush/canvas/history/grid/toolbar/ -tools/about/preferences/status automation tests. The PowerShell wrapper also -normalizes comma-separated `-Presets` and `-Targets` values for reliable -machine-driven partial matrix checks. `panopainter_platform_build_target_matrix_self_test` -keeps the PowerShell and shell wrapper defaults aligned with every current -CMake test executable plus required component targets, and now verifies the -default Android preset set covers standard arm64/x64, Quest arm64, and -Focus/Wave arm64. The shell wrapper now mirrors the PowerShell wrapper's -multi-preset behavior and reports one structured result array. Root CMake -exposes targets: -`panopainter_platform_build_headless`, -`panopainter_platform_build_android_assets`, -`panopainter_platform_build_vcpkg_ui_core`, and -`panopainter_platform_build_apple_remote`; the platform-build self-test guards -those target names and the wrapper matrix now includes -`pp_app_core_app_dialog_tests` plus the current -`pp_paint_renderer_stroke_execution_tests` and -`pp_renderer_gl_gpu_readback_tests` executables with the rest of the CMake -test targets. The `panopainter_package_smoke_readiness_self_test` now keeps the -package-smoke readiness wrapper aligned with the current package matrix while -the remaining platform shell gaps stay debt-tracked. -`package-smoke` now emits a structured package readiness matrix for Windows -AppX, Android standard/Quest/Focus APKs, Apple bundles, Linux app output, and -WebGL output, with blocked prerequisites tied to DEBT-0011. It also has a -readiness-only mode for cheap package blocker inventory without building an app -artifact, and `panopainter_package_smoke_readiness_self_test` keeps the -PowerShell and shell readiness matrices aligned, including retained Linux/WebGL -CMake baseline metadata. The PowerShell wrapper can also run the retained -Android native package checks through `-AndroidNativeChecks`, reporting the -standard `native-lib` build plus Quest/Focus configure checks next to the APK -blocker matrix. Root CMake exposes validation targets -for package readiness, Windows smoke, and Android -checks: `panopainter_package_readiness`, -`panopainter_windows_app_package_smoke`, -`panopainter_android_standard_native_package`, -`panopainter_android_vr_native_package_configure`, and -`panopainter_android_native_package_smoke`, plus the retained Linux/WebGL -blocker target `panopainter_linux_webgl_package_readiness`. App/package -entrypoints still need to consume shared targets and remain covered by debt -until package validation is migrated from legacy package projects to root -CMake. -Retained Linux and WebGL app CMake entrypoints now match the interim platform -baseline used by Android package paths: CMake 3.10 plus target-level -`cxx_std_23`, with `panopainter_retained_platform_cmake_self_test` guarding -against regressions while those entrypoints wait behind root CMake package/app -target migration. -Apple compile validation now runs on the local Mac mini SSH host -`panopainter-mac` through `scripts/automation/apple-remote-build.ps1`. The host -uses Homebrew CMake/Ninja/Git plus full Xcode via `DEVELOPER_DIR`, pulls the -modernization branch from Gitea, initializes the source submodules needed by the -current headless matrix, and builds `macos`, `ios-simulator`, and `ios-device`. -The iOS device compile gate assigns generated bundle identifiers and disables -code signing for test/tool executables under DEBT-0059; signed Apple app bundle -and package-smoke migration remains open under DEBT-0011. -`pp_platform_api` now also owns a tested platform-family policy catalog used by -both `WindowsPlatformServices` and the retained non-Windows fallback for -exported-image publishing, persistent-storage flushing, document browse roots, -working-directory picker availability, prepared-file target planning, -work-directory collection export policy, PPBR data-directory override policy, -SonarPen availability, native UI/window state saving, live asset reload policy, -layout XML file mtime reload policy, recording cleanup policy, default canvas -resolution, and canvas tip visibility. Platform SDK calls and filesystem probes -remain in the platform shells or thin runtime wrappers while those decisions are -headless-testable. -The retained Android standard/Quest/Focus package CMake files now use CMake -3.10, request C++23 through target compile features, include the extracted -modern component/service source set that the legacy package still links -monolithically, and share a generated `nanort` compatibility overlay from -`android/cmake/PanoPainterAndroidLegacyCompat.cmake` instead of dirtying the -vendor submodule. The standard package `native-lib` arm64 target now compiles -and links with the current NDK; Quest and Focus configure with the aligned Yoga -source list and their SDK imported-library paths. Android automation now uses -`sdkmanager` to compare the newest available SDK-managed NDK and CMake packages, -installs newer or missing packages when needed, and selects those versions -before configuring Android presets or retained package paths; on the current -Windows host both NDK `30.0.14904198` and CMake `4.1.2` report -`already-latest-available`. - -Implementation tasks: - -- Convert these builds to shared component targets: - - Windows desktop - - Windows AppX - - macOS - - iOS - - Android standard - - Android Quest - - Android Focus/Wave - - Linux - - WebGL/Emscripten -- Keep platform entrypoints thin: - - window lifecycle - - input dispatch - - clipboard - - file picker/share - - GL context creation - - VR SDK bridge - - packaging only -- Add or refine CMake toolchain/preset support for: - - Android NDK ABIs - - iOS device - - iOS simulator - - macOS - - Emscripten -- Keep SDK-only imported libraries documented until vcpkg triplets are proven. - -Gate: - -- Every platform has a named configure/build command. -- Missing local prerequisites are documented. -- Each platform has at least compile or package validation. - -## Phase 7: Hardening, Coverage, And Breaking-Point Tests - -Status: started. The first opt-in desktop GPU golden/readback gate now lives in -`pp_renderer_gl_gpu_readback_tests` and is selected by the existing -`desktop-gpu` preset. It creates a tiny desktop OpenGL context, clears to a -deterministic 1x1 red fixture, reads back exact RGBA bytes, and skips with a -clear message when no GPU/context helper is available. The first helper is -Windows/WGL-only; macOS/Linux helpers and broader render golden coverage remain -tracked under `DEBT-0036`. - -Goal: tests should try to break components, not only confirm current happy -paths. - -Implementation tasks: - -- Add property/fuzz tests for: - - binary streams - - serializers - - PPI parsing - - ABR parsing - - layout XML parsing - - image metadata parsing - - brush parameter extremes - - layer/frame operations - - undo/redo invariants -- Add stress tests for: - - thousands of stroke samples - - extreme resolutions guarded by memory limits - - rapid layer/frame edits - - corrupt assets - - cancellation during export - - concurrent render/UI task scheduling -- Add coverage for headless libraries on Clang/GCC. -- Require coverage reports for changed components first; do not set a global - threshold until the baseline is meaningful. -- Add tracing spans around: - - project load/save - - render passes - - stroke commit - - readback - - export - - UI layout - - platform I/O -- Logs must include component, thread, frame/stroke id, and timing. - -Gate: - -- No shortcut remains undocumented. -- Every component has unit tests and at least one failure or edge test. - -## Phase 8: Future Backend Readiness - -Goal: prepare Vulkan and Metal without destabilizing the OpenGL parity path. - -Status: in progress. Do not start production Vulkan or Metal work until -`RND-007` and `RND-008` freeze and validate the renderer API backend contract. -`RND-009` and `RND-010` are opt-in lab targets only and must stay outside the -production app path. - -Implementation tasks: - -- Create non-default targets only after OpenGL backend parity: - - `pp_renderer_vulkan_lab` - - `pp_renderer_metal_lab` -- Use `D:\Dev\vkpaint` as reference material for Vulkan painting experiments, - not as direct production code. -- Record and enforce the renderer API contract surface in - `docs/modernization/renderer_api_contract.md` and keep - `panopainter_renderer_api_contract_self_test` as a precondition for - `RND-007` closeout. -- Add and keep a renderer-conformance matrix fixture profile using - `renderer-conformance` labels plus `ctest --preset renderer-conformance` and - `ctest --preset desktop-gpu`, enforced by - `panopainter_renderer_conformance_matrix_self_test` before `RND-008` closeout. -- Before integration, prove: - - ping-pong compositing path - - input-attachment/subpass path where applicable - - feedback-loop or framebuffer-fetch-style path where supported - - synchronization and layout correctness under validation layers -- Keep WebGPU as an optional future portability backend, not the core renderer - contract. - -Gate: - -- Vulkan/Metal lab targets are opt-in. -- OpenGL production backend remains stable. - -## Test Matrix - -| Preset/Label | Purpose | Requires | -| --- | --- | --- | -| `desktop-fast` | Pure component unit tests | No GPU/window | -| `desktop-gpu` | OpenGL backend golden/readback tests | GPU/GL context | -| `fuzz` | Deterministic parser/serializer edge corpus; future libFuzzer entrypoint | No GPU/window today | -| `stress` | Large and adversarial scenarios | Longer runtime | -| `platform-build` | Configure/build each supported platform | Local toolchains | -| `package-smoke` | AppX/APK/Apple/WebGL package smoke | Platform SDKs | - -Acceptance for each phase: - -- Previous phase tests still pass. -- New component has its own tests. -- No undocumented stubs. -- No skipped platform without a debt entry. -- Automation command is recorded in this roadmap or linked docs. - -## Verified Commands - -Last verified on 2026-06-02: - -```powershell -cmake --preset windows-msvc-default -cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_document_tests pp_document_ppi_import_tests pp_document_ppi_export_tests pp_renderer_api_tests pp_paint_renderer_compositor_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pano_cli PanoPainter -ctest --preset desktop-fast --build-config Debug -ctest --preset fuzz --build-config Debug -ctest --preset stress --build-config Debug -powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan" -powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug -powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli -cmake --build --preset windows-msvc-default --target panopainter_validate_shaders -powershell -ExecutionPolicy Bypass -File scripts\automation\analyze.ps1 -Preset windows-msvc-default -NoApp -set VCPKG_ROOT=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg -cmake --preset windows-msvc-vcpkg-headless -powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets windows-msvc-vcpkg-headless -ctest --preset desktop-fast-vcpkg --build-config Debug -cmake --preset android-arm64 -powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64 -powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug -powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -ReadinessOnly -cmake --fresh --preset windows-clangcl-asan -powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos,ios-simulator,ios-device -``` - -Results: - -- `pp_foundation_binary_stream_tests` passed. -- `pp_foundation_event_tests` passed. -- `pp_foundation_log_tests` passed. -- `pp_foundation_parse_tests` passed. -- `pp_foundation_task_queue_tests` passed. -- `pp_foundation_trace_tests` passed. -- `pp_assets_image_format_tests` passed. -- `pp_assets_image_metadata_tests` passed. -- `pp_assets_image_pixels_tests` passed, including RGBA8 PNG decode and corrupt - payload rejection. -- `pp_assets_ppi_header_tests` passed, including PPI thumbnail/body layout, - body summary validation, layer/frame indexing, explicit per-layer metadata - and per-layer frame duration writing, dirty-face PNG payload metadata - validation, targeted layer/frame dirty-face writing, and decoded dirty-face - payload coverage. -- `pp_assets_settings_document_tests` passed. -- `pp_paint_brush_tests` passed. -- `pp_paint_blend_tests` passed. -- `pp_paint_stroke_tests` passed. -- `pp_paint_stroke_script_tests` passed. -- `pp_document_tests` passed, including snapshot construction, alpha-lock - metadata, per-layer frame metadata, frame move, duration, face-pixel payload - storage/replacement/rejection, snapshot-embedded face-payload rejection, and - history invariants. -- `pp_document_ppi_import_tests` passed, including decoded PPI dirty-face - payload attachment to `pp_document` layer/frame storage and out-of-range - payload rejection. -- `pp_document_ppi_export_tests` passed, including pure document metadata, - per-layer frame duration, and PNG-encoded face-payload export to PPI bytes, - plus malformed payload rejection at the export boundary. -- `pp_renderer_api_tests` passed, including shader descriptor validation, - PanoPainter shader catalog validation, explicit texture usage validation, - texture mip-level validation, resource debug-label validation, readback - byte-size and command-order validation, texture-upload byte-count validation, - mipmap-generation command validation, trace marker/scope validation, - frame-capture byte-size and command-order validation, - render-target blit validation, texture-slot binding validation, blend-state - validation, scissor-state validation, render-pass color/depth/stencil clear - validation, shader-uniform write validation, draw descriptor/range - validation, backend-neutral resource factory validation, texture-copy - validation, recording - render-pass clear/scissor/depth/blend/shader-uniform/texture/sampler-bind/ - upload/texture-copy/readback/frame-capture/blit command capture, draw - mesh-input capture, explicit draw-range capture, and invalid catalog - rejection. The same suite now covers complex paint feedback planning for - framebuffer-fetch backends, ping-pong texture-copy/blit fallbacks, simple - no-feedback blends, invalid render-target usage, unsupported backends, and - depth-target rejection. -- `pp_paint_renderer_compositor_tests` passed, including pure - `pp_document` face and six-face frame compositing over per-layer dirty face - payloads plus renderer-neutral six-face texture upload through the recording - backend. - The suite now covers fixed-function stroke composite planning, - framebuffer-fetch planning, ping-pong texture-copy/blit fallback planning, - dual/pattern blend feedback detection, invalid blend mode rejection, - unsupported backend rejection, and invalid render-target rejection. -- `pp_ui_core_color_tests` passed. -- `pp_ui_core_layout_value_tests` passed. -- `pp_ui_core_layout_xml_tests` passed. -- `pano_cli_create_document_smoke` passed. -- `pano_cli_create_animation_document_smoke` passed and reports animation - duration JSON. -- `pano_cli_simulate_document_edits_smoke` passed and reports pure - `pp_document` layer metadata, frame order, active indices, and face-payload - state as JSON. -- `pano_cli_simulate_document_history_smoke` passed and reports real - `pp_document::DocumentHistory` apply/undo/redo state as JSON. -- `pano_cli_simulate_document_export_smoke` passed and reports pure - `pp_document` export to PPI bytes, asset-level decode, and document reimport - round-trip state as JSON. -- `pano_cli_simulate_document_render_smoke` passed and reports pure - `pp_document` to `pp_paint_renderer` six-face frame compositing and - renderer texture-upload command summaries plus OpenGL command-planner - support counts as JSON. -- `pano_cli_simulate_image_import_smoke` passed and reports embedded PNG decode - plus `pp_document` face-payload attachment state as JSON. -- `pano_cli_inspect_image_rejects_unsupported` passed as an expected failure - test. -- `pano_cli_inspect_png_metadata_smoke` passed and reports PNG metadata JSON - for the tiny IHDR fixture. -- `pano_cli_import_image_rejects_truncated_png` passed as an expected failure - test, proving the file-driven image import command rejects a metadata-valid - but undecodable PNG payload. -- `pano_cli_inspect_project_layout_smoke` passed and reports PPI - thumbnail/body byte layout, body summary, layer/frame descriptors, and - dirty-face PNG payload metadata JSON. -- `pano_cli_load_project_metadata_smoke` passed and reports a `pp_document` - projection with per-layer frame counts, durations, and zero loaded face - payloads for the minimal PPI fixture. -- `pano_cli_save_project_roundtrip_smoke` passed and proves the metadata-only - `pp_assets` PPI writer can save a generated multi-frame PPI and reload it - through `pano_cli load-project`. -- `pano_cli_save_project_payload_roundtrip_smoke` passed and proves the - `pp_assets` PPI writer can save a compressed RGBA PNG dirty-face payload to - an explicit layer/frame slot, inspect the serialized descriptor, and reload - it as decoded `pp_document` face-pixel data. -- `pano_cli_save_document_project_roundtrip_smoke` passed and proves a pure - `pp_document` export can be written to a PPI file, inspected for layer/frame - dirty-face descriptors, and loaded back through the PPI import path. -- `pano_cli_apply_stroke_script_roundtrip_smoke` passed and proves a checked-in - stroke script can be parsed, sampled, applied to a pure `pp_document` face - payload, written to PPI, inspected for the expected dirty-face box, and loaded - back as decoded document pixel data. -- `pano_cli_apply_stroke_script_rejects_tiny_canvas` passed as an expected - failure test, proving the stroke-script document command rejects dimensions - outside its bounded automation range before payload allocation. -- `pano_cli_parse_layout_smoke` passed. -- `pano_cli_simulate_stroke_smoke` passed and reports deterministic stroke - sample counts/distances. -- `pano_cli_simulate_stroke_script_smoke` passed and reports deterministic - aggregate stroke-script counts/distances. -- `pp_app_core_document_cloud_tests` passed, covering cloud upload no-canvas, - new-document warning, clean publish prompt, and dirty save-before-upload - decisions, plus cloud browse no-canvas/show-browser and selected-download - decisions, plus bulk upload progress visibility, zero-file, and clamped - progress-total decisions. -- `pano_cli_plan_cloud_upload_clean_smoke`, - `pano_cli_plan_cloud_upload_unsaved_smoke`, - `pano_cli_plan_cloud_upload_new_document_smoke`, and - `pano_cli_plan_cloud_upload_no_canvas_smoke` passed and expose those app-core - cloud upload decisions as JSON. -- `pano_cli_plan_cloud_upload_all_progress_smoke` and - `pano_cli_plan_cloud_upload_all_headless_smoke` passed and expose app-core - bulk upload progress decisions as JSON. -- `pano_cli_plan_cloud_browse_waiting_smoke`, - `pano_cli_plan_cloud_browse_selected_smoke`, and - `pano_cli_plan_cloud_browse_no_canvas_smoke` passed and expose app-core cloud - browse/download-selection decisions as JSON. -- `pp_app_core_document_cloud_tests` now also covers cloud transfer request - validation, progress-callback enablement, TLS-verification policy, and - zero/negative/overrun transfer-progress guards. -- `pano_cli_plan_cloud_transfer_download_smoke`, - `pano_cli_plan_cloud_transfer_upload_smoke`, - `pano_cli_plan_cloud_transfer_rejects_missing_destination`, and - `pano_cli_plan_cloud_transfer_zero_total_smoke` passed and expose the - app-core cloud transfer path as JSON. -- `PanoPainter`, `pp_app_core_document_cloud_tests`, and `pano_cli` built after - live `App::download` and `App::upload` started consuming the transfer plans - before retained CURL setup. -- Android arm64 headless `pp_app_core`, `pano_cli`, and - `pp_app_core_document_cloud_tests` built after the cloud transfer slice. -- `PanoPainter`, `pp_app_core_document_cloud_tests`, and `pano_cli` built after - cloud upload warning/publish/success prompts, bulk upload progress dialogs, - and download-progress messages moved to tested `pp_app_core` metadata plans. -- Focused cloud metadata CTest coverage passed for - `pp_app_core_document_cloud_tests` and representative - `pano_cli_plan_cloud_*` smoke tests, including prompt titles/captions, - progress-dialog titles, and formatted download progress messages. -- Android arm64 headless `pp_app_core`, `pano_cli`, and - `pp_app_core_document_cloud_tests` built after the cloud metadata slice. -- `PanoPainter`, `pp_app_core_document_cloud_tests`, and `pano_cli` built after - live cloud upload, bulk upload, and browse/download execution moved behind - the `CloudServices` boundary and `src/legacy_cloud_services.*`. -- Focused cloud CTest coverage passed for `pp_app_core_document_cloud_tests` - and all `pano_cli_plan_cloud_*` smoke tests after the live bridge split. -- `ctest --preset desktop-fast --build-config Debug` passed with 243 tests - after the cloud bridge split. -- `scripts/automation/package-smoke.ps1 -Preset windows-msvc-default - -Configuration Debug` passed executable/data checks after the cloud bridge - split; package target migration blockers remain under `DEBT-0011`. -- `PanoPainter`, `pp_app_core_document_cloud_tests`, - `pp_app_core_app_dialog_tests`, and `pano_cli` built after cloud - download-progress prompt creation moved onto - `src/legacy_app_dialog_services.*`. -- Focused cloud/app-dialog CTest coverage passed for - `pp_app_core_document_cloud_tests`, `pp_app_core_app_dialog_tests`, all - `pano_cli_plan_cloud_*` smoke tests, and `pano_cli_plan_app_dialog_*` after - the cloud prompt bridge split. -- Android arm64 headless `pp_app_core`, `pano_cli`, - `pp_app_core_document_cloud_tests`, and `pp_app_core_app_dialog_tests` built - after the cloud prompt bridge split. -- `PanoPainter`, `pp_app_core_document_session_tests`, and `pano_cli` built - after `App::open_document` moved live execution behind the document-open - services bridge. A clean rebuild was required once because MSVC reported the - known Debug PDB `LNK1103` corruption, after which the build passed. -- Focused document-open CTest coverage passed for - `pp_app_core_document_route_tests`, `pp_app_core_document_session_tests`, and - the `pano_cli_plan_open_route_*` smoke tests after the live bridge split. -- `PanoPainter`, `pp_app_core_document_session_tests`, and `pano_cli` built - after close request, document save, and dirty-workflow continuation execution - moved behind document-session services. A clean rebuild was required once - because MSVC reported the known Debug PDB `LNK1103` corruption, after which - the build passed. -- Focused document-session CTest coverage passed for - `pp_app_core_document_session_tests`, `pano_cli_simulate_app_session_*`, and - `pano_cli_plan_document_file/version_*` smoke tests after the live bridge - split. -- `PanoPainter`, `pp_app_core_document_session_tests`, and `pano_cli` built - after accepted new-document execution moved behind the new-document services - bridge. A clean rebuild was required once because MSVC reported the known - Debug PDB `LNK1103` corruption, after which the build passed. -- Focused new-document/session CTest coverage passed for - `pp_app_core_document_session_tests`, `pano_cli_plan_new_document_*`, and - `pano_cli_simulate_app_session_*` smoke tests after the live bridge split. -- `PanoPainter`, `pp_app_core_document_session_tests`, and `pano_cli` built - after accepted Save As and Save Version execution moved behind document - file/version save services. A clean rebuild was required once because MSVC - reported the known Debug PDB `LNK1103` corruption, after which the build - passed. -- Focused Save As/Version/session CTest coverage passed for - `pp_app_core_document_session_tests`, `pano_cli_plan_document_file_*`, - `pano_cli_plan_document_version_*`, and `pano_cli_simulate_app_session_*` - smoke tests after the live bridge split. -- `PanoPainter`, `pano_cli`, `pp_app_core_app_dialog_tests`, and - `pp_app_core_document_session_tests` built after close/save/workflow, - new-document overwrite, Save As overwrite, and save-error prompt metadata - moved into the pure document-session prompt catalog. A clean rebuild was - required once because MSVC reported the known Debug PDB `LNK1103` - corruption, after which the build passed. -- Focused document-session prompt CTest coverage passed for - `pp_app_core_app_dialog_tests`, `pp_app_core_document_session_tests`, - `pano_cli_plan_document_session_prompt_*`, - `pano_cli_plan_document_file_*`, `pano_cli_plan_new_document_*`, and - representative `pano_cli_simulate_app_session_*` smoke tests. -- Android arm64 headless `pp_app_core`, `pano_cli`, - `pp_app_core_app_dialog_tests`, and `pp_app_core_document_session_tests` - built after the same prompt-catalog change. -- `PanoPainter`, `pp_app_core_document_export_tests`, and `pano_cli` built - after equirectangular, layers, animation-frame, depth, and cube-face export - execution moved behind document export services. A clean rebuild was required - once because MSVC reported the known Debug PDB `LNK1103` corruption, after - which the build passed. -- Focused export CTest coverage passed for `pp_app_core_document_export_tests`, - `pano_cli_plan_export_start/menu/target_*`, and - `pano_cli_simulate_document_export_smoke` after the live bridge split. -- `PanoPainter`, `pp_app_core_document_export_tests`, and `pano_cli` built - after timelapse and animation MP4 export execution moved behind document - video export services. A clean rebuild was required once because MSVC - reported the known Debug PDB `LNK1103` corruption, after which the app, - export tests, and `pano_cli` targets built cleanly. -- Focused video export CTest coverage passed for - `pp_app_core_document_export_tests`, `pano_cli_plan_export_menu_*`, - `pano_cli_plan_export_target_name_smoke`, and - `pano_cli_simulate_document_export_smoke`. -- `PanoPainter`, `pp_app_core_app_preferences_tests`, and `pano_cli` built - after options-menu preference execution moved behind app preference services. -- Focused preference CTest coverage passed for - `pp_app_core_app_preferences_tests` and the app-preferences CLI smoke tests - after the live bridge split, including VR mode failed-start status coverage. -- `PanoPainter`, `pp_app_core_app_dialog_tests`, and `pano_cli` built after - progress/message/input dialog metadata moved into `pp_app_core` while live - `App` factories kept retained `Node*` creation. -- Focused app-dialog CTest coverage passed for - `pp_app_core_app_dialog_tests` and the `pano_cli_plan_app_dialog_*` smoke - tests, including negative progress-total clamping and rejected empty - input-dialog OK captions. -- Android arm64 headless `pp_app_core`, `pano_cli`, and - `pp_app_core_app_dialog_tests` built after the app-dialog planning slice. -- `PanoPainter`, `pp_app_core_app_dialog_tests`, and `pano_cli` built after - retained progress/message/input `Node*` creation moved into - `src/legacy_app_dialog_services.*`. -- Focused app-dialog CTest coverage passed again for - `pp_app_core_app_dialog_tests` and the `pano_cli_plan_app_dialog_*` smoke - tests after the legacy bridge split. -- Android arm64 headless `pp_app_core`, `pano_cli`, and - `pp_app_core_app_dialog_tests` built after the app-dialog bridge split. -- `PanoPainter`, `pp_app_core_document_session_tests`, - `pp_app_core_app_dialog_tests`, and `pano_cli` built after document-session - prompts moved onto `src/legacy_app_dialog_services.*`. -- Focused document-session/app-dialog CTest coverage passed for - `pp_app_core_document_session_tests`, `pp_app_core_app_dialog_tests`, - `pano_cli_plan_document_session_prompt_*`, `pano_cli_plan_app_dialog_*`, and - `pano_cli_simulate_app_session_*` after the document-session prompt bridge - split. -- Android arm64 headless `pp_app_core`, `pano_cli`, - `pp_app_core_document_session_tests`, and `pp_app_core_app_dialog_tests` - built after the document-session prompt bridge split. -- `PanoPainter`, `pp_app_core_app_startup_tests`, and `pano_cli` built after - startup preference/runtime execution and startup resource sequencing moved - behind app startup services. -- Focused startup CTest coverage passed for `pp_app_core_app_startup_tests`, - `pano_cli_plan_app_startup_smoke`, and - `pano_cli_plan_app_startup_rejects_negative_counter`, with startup resource - sequencing also covered by `pano_cli_plan_app_startup_resources_smoke` and - `pano_cli_plan_app_startup_resources_rejects_bad_size`. -- `PanoPainter`, `pp_app_core_app_frame_tests`, and `pano_cli` built after - app frame surface/update/tick/resize/draw-pass decisions moved into - `pp_app_core`. -- Focused frame CTest coverage passed for `pp_app_core_app_frame_tests`, - `pano_cli_plan_app_frame_vr_smoke`, and - `pano_cli_plan_app_frame_idle_missing_canvas_smoke`, with resize automation - covered by `pano_cli_plan_app_frame_resize_smoke` and - `pano_cli_plan_app_frame_rejects_bad_resize`. On 2026-06-05, UI observer - clipping/on-screen/scissor projection coverage was added through - `pp_app_core_app_frame_tests`, `pano_cli_plan_app_frame_observer_smoke`, - `pano_cli_plan_app_frame_observer_clipped_smoke`, and - `pano_cli_plan_app_frame_rejects_bad_observer`. -- `PanoPainter`, `pp_app_core_app_input_tests`, and `pano_cli` built after - app input routing and normalization moved into `pp_app_core`. -- Focused app-input CTest coverage passed for `pp_app_core_app_input_tests`, - `pano_cli_plan_app_input_pointer_smoke`, - `pano_cli_plan_app_input_gesture_smoke`, - `pano_cli_plan_app_input_key_vr_smoke`, - `pano_cli_plan_app_input_ui_toggle_smoke`, - `pano_cli_plan_app_input_stylus_smoke`, - `pano_cli_plan_app_input_rejects_bad_float`, and - `pano_cli_plan_app_input_rejects_missing_ui_panel`. -- `PanoPainter`, `pp_app_core_app_thread_tests`, and `pano_cli` built after - render/UI task dispatch, queue draining, UI-loop timer cadence, async redraw, - and start/stop decisions moved into `pp_app_core`. -- Focused app-thread CTest coverage passed for `pp_app_core_app_thread_tests`, - `pano_cli_plan_app_thread_dispatch_smoke`, - `pano_cli_plan_app_thread_ui_loop_smoke`, - `pano_cli_plan_app_thread_stop_smoke`, and - `pano_cli_plan_app_thread_rejects_bad_timer`. -- `PanoPainter`, `pp_app_core_app_shutdown_tests`, and `pano_cli` built after - shutdown cleanup staging moved into `pp_app_core`. -- Focused shutdown CTest coverage passed for `pp_app_core_app_shutdown_tests`, - `pano_cli_plan_app_shutdown_smoke`, and - `pano_cli_plan_app_shutdown_rejects_unknown_option`. -- `PanoPainter`, `pp_app_core_command_convert_tests`, and `pano_cli` built - after command-line panorama conversion planning moved into `pp_app_core`. -- Focused command-convert CTest coverage passed for - `pp_app_core_command_convert_tests`, - `pano_cli_plan_command_convert_smoke`, - `pano_cli_plan_command_convert_rejects_empty_project`, and - `pano_cli_plan_command_convert_rejects_bad_resolution`. -- `PanoPainter`, `pp_app_core_brush_package_export_tests`, and `pano_cli` built - after PPBR brush package export request validation and dispatch moved behind - app-core brush package services. -- Focused PPBR export CTest coverage passed for - `pp_app_core_brush_package_export_tests`, - `pano_cli_plan_brush_package_export_smoke`, - `pano_cli_plan_brush_package_export_rejects_empty_path`, and - `pano_cli_plan_brush_package_export_dest_without_data_smoke`. -- `PanoPainter`, `pp_app_core_brush_package_import_tests`, and `pano_cli` built - after ABR/PPBR brush package import execution moved behind app-core brush - import services. -- Focused brush import CTest coverage passed for - `pp_app_core_brush_package_import_tests`, - `pano_cli_plan_brush_package_import_ppbr_smoke`, - `pano_cli_plan_brush_package_import_abr_smoke`, - `pano_cli_plan_brush_package_import_rejects_empty_path`, and - `pano_cli_plan_brush_package_import_rejects_unknown_kind`. -- `PanoPainter`, `pp_assets_brush_package_tests`, - `pp_app_core_brush_package_export_tests`, and `pano_cli` built after PPBR - header validation and export path/data-directory planning moved into - `pp_assets`. -- Focused PPBR asset CTest coverage passed for `pp_assets_brush_package_tests` - and the brush package export CLI tests, including path-without-directory - rejection and legacy no-export-data data-directory planning. -- `PanoPainter`, `pp_assets_brush_package_tests`, - `pp_app_core_brush_package_import_tests`, and `pano_cli` built after ABR and - PPBR imported brush tip/pattern target paths moved into `pp_assets`. -- Focused brush import storage CTest coverage passed for - `pp_assets_brush_package_tests` and the brush package import/export CLI - smoke/failure tests. -- `PanoPainter`, `pp_app_core_brush_ui_tests`, and `pano_cli` built after brush - preset-list add/select/move/remove/clear planning moved into `pp_app_core`. -- Focused brush preset-list CTest coverage passed for - `pp_app_core_brush_ui_tests` and `pano_cli_plan_brush_preset_list_*` smoke - tests. -- `pp_app_core_document_recording_tests` passed, covering recording start/stop, - clear, platform recorded-file cleanup, frame-count reset, export progress - totals, oversized progress-total clamping, and recording-worker encode-wake - eligibility. -- `pano_cli_plan_recording_session_stopped_smoke`, - `pano_cli_plan_recording_session_running_smoke`, and - `pano_cli_plan_recording_session_platform_cleanup_smoke` passed and expose - app-core recording lifecycle/export decisions as JSON. On 2026-06-05, - `pano_cli_plan_recording_session_missing_encoder_smoke` was added for the - worker no-encode path. -- `PanoPainter`, `pp_app_core_document_recording_tests`, - `pp_app_core_app_dialog_tests`, and `pano_cli` built after MP4 recording - export progress-dialog metadata moved into `pp_app_core` and retained - progress bar creation routed through `src/legacy_app_dialog_services.*`. -- Focused recording/app-dialog CTest coverage passed for - `pp_app_core_document_recording_tests`, `pp_app_core_app_dialog_tests`, - `pano_cli_plan_recording_session_*`, and `pano_cli_plan_app_dialog_*`, - including the MP4 export progress-dialog metadata smoke. -- Android arm64 headless `pp_app_core`, `pano_cli`, - `pp_app_core_document_recording_tests`, and `pp_app_core_app_dialog_tests` - built after the recording progress bridge split. -- `pp_app_core_document_resize_tests` passed, covering resize dialog state, - unknown current-resolution labeling, selected-resolution mapping, square - canvas sizing, history-clearing intent, invalid selection rejection, service - dispatch order, optional history clearing, and invalid-dimension rejection. -- `pano_cli_plan_document_resize_smoke` and - `pano_cli_plan_document_resize_rejects_invalid_selection` passed and expose - live document-resize planning as JSON automation. -- `pp_app_core_document_layer_tests` passed, covering changed layer rename, - unchanged no-op rename, empty-name rejection, overlong-name rejection, rename - executor dispatch, no-op rename dialog finish, malformed rename-plan - rejection, layer add/duplicate/select/reorder/remove planning, metadata - planning, bad-index rejection, bad-opacity rejection, bad-blend-mode - rejection, transient highlight behavior, layer operation executor dispatch, - layer operation side-effect dispatch, no-op operation preservation, - malformed operation rejection, Layer menu labels/actions, merge-down routing, - animated merge blocking, missing selection handling, bad Layer menu state - rejection, Layer menu executor dispatch, no-op menu execution preservation, - merge-plan validation, unsupported animated merge rejection, merge executor - dispatch, and malformed merge-plan rejection. -- `pano_cli_plan_layer_rename_smoke`, - `pano_cli_plan_layer_rename_no_op_smoke`, and - `pano_cli_plan_layer_rename_rejects_empty_name` passed and expose live - layer-rename planning as JSON automation. -- `pano_cli_plan_layer_menu_merge_smoke`, - `pano_cli_plan_layer_menu_clear_smoke`, - `pano_cli_plan_layer_menu_merge_animated_blocked_smoke`, - `pano_cli_plan_layer_menu_missing_selection_smoke`, and - `pano_cli_plan_layer_menu_rejects_bad_state` passed and expose live Layer - menu planning as JSON automation. -- `pano_cli_plan_layer_merge_smoke` and - `pano_cli_plan_layer_merge_animated_rejected` passed and expose live - merge execution planning as JSON automation. -- `pano_cli_plan_layer_operation_add_smoke`, - `pano_cli_plan_layer_operation_reorder_no_op_smoke`, - `pano_cli_plan_layer_operation_highlight_smoke`, and - `pano_cli_plan_layer_operation_rejects_bad_opacity` passed and expose live - layer-panel operation planning as JSON automation. -- `pp_app_core_document_animation_tests` passed, covering animation frame - add/duplicate/remove planning, selected-frame rejection, last-frame remove - rejection, duration floor/overflow handling, timeline move edge behavior, - goto/next/previous wrapping, onion-size rejection, service dispatch ordering, - frame-click selection planning, no-reload playback step planning, - playback toggle start/stop planning, animation panel action planning, - invalid panel timeline state rejection, non-mutating duration no-ops, tested - onion-skin frame range/alpha falloff planning consumed by live `NodeCanvas` - panorama drawing, tested timeline mouse-scrub cursor-to-frame planning - consumed by live `NodeAnimationTimeline`, tested animation panel layer/frame - view-model projection consumed by live `NodePanelAnimation`, stale selected - frame preservation, and malformed execution payload rejection. -- `pano_cli_plan_animation_operation_add_smoke`, - `pano_cli_plan_animation_operation_duration_floor_smoke`, - `pano_cli_plan_animation_operation_next_wrap_smoke`, - `pano_cli_plan_animation_operation_select_smoke`, - `pano_cli_plan_animation_operation_playback_smoke`, - `pano_cli_plan_animation_operation_toggle_playback_start_smoke`, - `pano_cli_plan_animation_operation_toggle_playback_stop_smoke`, - `pano_cli_plan_animation_panel_action_next_smoke`, - `pano_cli_plan_animation_panel_action_toggle_stop_smoke`, - `pano_cli_plan_animation_panel_action_rejects_bad_timeline`, - `pano_cli_plan_animation_panel_view_smoke`, - `pano_cli_plan_animation_panel_view_allows_stale_selection`, - `pano_cli_plan_animation_panel_view_rejects_empty_frames`, - `pano_cli_plan_animation_timeline_scrub_smoke`, - `pano_cli_plan_animation_timeline_scrub_clamps_left`, - `pano_cli_plan_animation_timeline_scrub_rejects_bad_duration`, - `pano_cli_plan_animation_operation_rejects_remove_last_frame`, and - `pano_cli_plan_animation_operation_rejects_bad_selection` passed and expose - live animation-panel planning as JSON automation. -- `pp_app_core_brush_ui_tests` passed, covering brush color channel validation, - invalid color rejection, texture-path validation, preset-brush availability, - preserve-current-color intent, stroke-settings refresh intent, texture-list - add target path planning, user-texture removal intent, clamped reorder intent, - stroke-control slider/toggle/blend/reset planning, service dispatch ordering, - texture/preset/list/stroke-control execution payloads, execution failure - preservation, and invalid execution payload rejection. -- `pano_cli_plan_brush_operation_color_smoke`, - `pano_cli_plan_brush_operation_texture_smoke`, - `pano_cli_plan_brush_operation_preset_smoke`, - `pano_cli_plan_brush_operation_rejects_bad_color`, and - `pano_cli_plan_brush_operation_rejects_empty_texture` passed and expose live - brush/color/preset UI planning as JSON automation. -- `pano_cli_plan_brush_texture_list_add_smoke`, - `pano_cli_plan_brush_texture_list_remove_user_smoke`, - `pano_cli_plan_brush_texture_list_move_edge_smoke`, and - `pano_cli_plan_brush_texture_list_rejects_bad_source` passed and expose live - brush/pattern texture-list planning as JSON automation. -- `pano_cli_plan_brush_stroke_control_float_smoke`, - `pano_cli_plan_brush_stroke_control_toggle_smoke`, - `pano_cli_plan_brush_stroke_control_blend_smoke`, - `pano_cli_plan_brush_stroke_control_reset_smoke`, - `pano_cli_plan_brush_stroke_control_rejects_bad_setting`, and - `pano_cli_plan_brush_stroke_control_rejects_bad_blend` passed and expose live - stroke-panel slider/toggle/blend/reset planning as JSON automation. -- `pano_cli_plan_brush_stroke_panel_view_smoke`, - `pano_cli_plan_brush_stroke_panel_view_rejects_bad_float`, and - `pano_cli_plan_brush_stroke_panel_view_rejects_bad_blend` passed and expose - live stroke-panel state projection as JSON automation. -- `pp_app_core_grid_ui_tests` passed, covering heightmap pick/load/reload/clear - planning, lightmap capability and limit checks, missing-heightmap no-op - behavior, and commit canvas gating. -- `pano_cli_plan_grid_operation_pick_smoke`, - `pano_cli_plan_grid_operation_load_smoke`, - `pano_cli_plan_grid_operation_render_supported_smoke`, - `pano_cli_plan_grid_operation_render_unsupported_smoke`, - `pano_cli_plan_grid_operation_rejects_empty_reload`, and - `pano_cli_plan_grid_operation_rejects_bad_samples` passed and expose live - grid/heightmap/lightmap planning as JSON automation. -- `pp_app_core_canvas_tool_ui_tests` passed, covering toolbar mode selection, - copy/cut transform action planning, pick no-op outside draw mode, and - touch-lock toggling, full draw-toolbar binding projection, binding-to-action - conversion, plus toolbar active-state derivation for draw, copy, and bucket - modes, service dispatch ordering, pick no-op execution, and malformed - execution payload rejection. -- `pp_app_core_canvas_hotkey_tests` passed, covering E draw/erase toggles, - Ctrl+Z/Ctrl+Shift+Z history planning, Ctrl+S/Ctrl+Shift+S document save - intents, Tab UI toggles, brush-size brackets, Android back and two-finger - undo, no-op Ctrl-less Z, bad-count rejection, executor dispatch, and - malformed brush-size execution rejection. -- `pano_cli_plan_canvas_hotkey_ctrl_z_smoke`, - `pano_cli_plan_canvas_hotkey_save_dirty_version_smoke`, - `pano_cli_plan_canvas_hotkey_erase_smoke`, - `pano_cli_plan_canvas_hotkey_two_finger_undo_smoke`, and - `pano_cli_plan_canvas_hotkey_rejects_bad_count` passed and expose live - canvas keyboard/touch command planning as JSON automation. -- `pano_cli_plan_canvas_tool_draw_smoke`, - `pano_cli_plan_canvas_tool_copy_smoke`, - `pano_cli_plan_canvas_tool_pick_noop_smoke`, - `pano_cli_plan_canvas_tool_touch_lock_smoke`, and - `pano_cli_plan_canvas_tool_rejects_unknown` passed and expose live draw - toolbar planning as JSON automation. -- `pano_cli_plan_canvas_tool_toolbar_smoke` and - `pano_cli_plan_canvas_tool_toolbar_rejects_unknown` passed and expose the - full live draw-toolbar binding set as JSON automation. -- `pano_cli_plan_canvas_tool_state_draw_smoke`, - `pano_cli_plan_canvas_tool_state_copy_smoke`, and - `pano_cli_plan_canvas_tool_state_rejects_unknown` passed and expose draw - toolbar active-state refresh as JSON automation. -- `PanoPainter`, `pano_cli`, and `pp_app_core_canvas_tool_ui_tests` built on - Windows after `App::init_toolbar_draw()` moved to the app-core toolbar - binding plan; the build required the documented clean after a stale - debug-info `LNK1103`. -- Android arm64 headless `pp_app_core`, `pano_cli`, and - `pp_app_core_canvas_tool_ui_tests` built after the same toolbar binding - planner change. -- `pp_app_core_document_canvas_tests` passed, covering clear-current-layer - undo/dirty intent, no-canvas no-op behavior, and invalid clear color - rejection, service dispatch color forwarding, no-op execution preservation, - invalid execution color rejection, and canvas-to-`pp_document` - snapshot projection with layer visibility, opacity, alpha-lock, blend mode, - frame duration, active layer/frame, captured RGBA8 face payload attachment, - default-name, no-canvas, bad blend, bad payload, and bad duration coverage. -- `pano_cli_plan_canvas_clear_smoke`, - `pano_cli_plan_canvas_clear_no_canvas_smoke`, and - `pano_cli_plan_canvas_clear_rejects_bad_color` passed and expose toolbar - canvas clear planning as JSON automation. -- `pano_cli_plan_canvas_document_snapshot_smoke` and - `pano_cli_plan_canvas_document_snapshot_payload_smoke` plus the no-canvas - rejection smoke passed and expose live-canvas-to-`pp_document` projection, - including captured-versus-pending renderer payload-readback counts, as JSON - automation. -- Live Save, Save As, Save Version, and save-before-workflow execution now - prepare a payload-bearing canvas document snapshot and save-readiness report - through `src/legacy_document_session_services.*`; payload-complete snapshots - now run the pure PPI exporter and log the app-core save-writer route/byte - count before delegating to the retained `Canvas::project_save` writer, - keeping behavior stable while moving the app path onto the document/canvas - boundary. -- `pano_cli plan-canvas-document-snapshot` now emits the same save-readiness - report (`payloadComplete` and `canExportPpi`) used by the live save bridge, - the same save-writer route, and payload-complete snapshots now run the pure - `pp_document` PPI exporter and decoded-project summary before emitting - `ppiExport` JSON. -- `pano_cli plan-canvas-project-save-target` now exposes the app-core planner - for retained project-save target paths, including the target PPI path, - temporary `.tmp.ppi` path, and timelapse `.pptl` sidecar. The live - `Canvas::project_save_thread` consumes the same planner before retained - serialization, reducing inline path compatibility logic in the legacy writer. - It also reports the app-core write-mode plan for direct first saves versus - existing-target temporary writes, including the retained fallback to direct - target writes when the temporary file cannot be opened. - The command now reports the app-core commit plan for direct saves, successful - temporary swaps, target-remove failures, and rename-after-remove failures, - including whether the target may be missing after a failed swap. - It also reports the app-core post-commit side-effect plan, including - clean/new-document metadata updates, timelapse sidecar gating, platform - flush, progress cleanup, and title refresh. -- The same payload-complete snapshot automation now uploads the active document - frame through `pp_paint_renderer::upload_document_frame_faces` and the - `RecordingRenderDevice`, emitting `rendererUpload` JSON with texture, - transition, command, byte, and active-frame payload counts. -- Live equirectangular, layer, animation-frame, and cube-face export bridges now - capture the payload-bearing canvas document snapshot and run the - renderer-neutral upload report before retained `Canvas` export execution; - failures are logged and retained export still continues to preserve behavior. -- `pp_app_core` now owns the document-snapshot export route decision for - equirectangular, layer, animation-frame, and cube-face writers, including - PNG/JPEG target support, collection/cube target support, platform support, - and incomplete renderer-payload fallback reasons. `pano_cli - plan-export-snapshot-route` exposes the same policy as JSON, and the live - retained export bridge consumes the app-core current-platform support helper - instead of local Web writer gates. -- `pp_app_core_document_import_tests` passed, covering wide equirectangular, - legacy vertical cube strip, regular transform-placement, and invalid-dimension - import route decisions, equirectangular service dispatch, transform import - dispatch, empty-path rejection, and invalid execution dimension rejection. -- `pano_cli_plan_image_import_wide_equirect_smoke`, - `pano_cli_plan_image_import_transform_smoke`, and - `pano_cli_plan_image_import_rejects_invalid_dimensions` passed and expose File - > Import route planning as JSON automation. -- `pp_app_core_file_menu_tests` passed, covering top-level File menu routing for - creation/open/import, save intents, export/submenu/cloud actions, and unknown - command rejection, plus executor dispatch for dialog, picker, save, export, - share, resize, and cloud actions. -- `pano_cli_plan_file_menu_import_smoke`, - `pano_cli_plan_file_menu_save_as_smoke`, - `pano_cli_plan_file_menu_export_smoke`, - `pano_cli_plan_file_menu_cloud_upload_smoke`, and - `pano_cli_plan_file_menu_rejects_unknown` passed and expose top-level File - menu routing as JSON automation. -- `pp_app_core_document_export_tests` passed, now also covering export menu - dialog routing, demo-mode MP4/timelapse license gating, and missing-canvas - handling, plus export menu executor dispatch for all dialog, blocked, and - unavailable actions before legacy export dialogs continue. -- `pano_cli_plan_export_menu_png_smoke`, - `pano_cli_plan_export_menu_mp4_demo_blocked_smoke`, - `pano_cli_plan_export_menu_no_canvas_smoke`, and - `pano_cli_plan_export_menu_rejects_unknown` passed and expose File menu export - routing as JSON automation. -- `pp_app_core_history_ui_tests` passed, covering undo/redo availability, - no-op history commands, clear-history stack/memory state, memory-only clear, - negative metric rejection, service dispatch order, empty-history no-op - execution, and invalid execution metric rejection. -- `pano_cli_plan_history_operation_undo_smoke`, - `pano_cli_plan_history_operation_redo_empty_smoke`, - `pano_cli_plan_history_operation_clear_smoke`, and - `pano_cli_plan_history_operation_rejects_negative_count` passed and expose - toolbar/canvas history planning as JSON automation. -- `pp_app_core_quick_ui_tests` passed, covering quick brush/color slot - selection, active-slot popup decisions, invalid slot rejection, restore-state - validation, reset-state validation, service dispatch order, explicit - brush/color restore indices, and malformed execution payload rejection. -- `pano_cli_plan_quick_operation_select_brush_smoke`, - `pano_cli_plan_quick_operation_open_color_smoke`, - `pano_cli_plan_quick_operation_restore_smoke`, - `pano_cli_plan_quick_operation_reset_smoke`, - `pano_cli_plan_quick_operation_rejects_bad_slot`, and - `pano_cli_plan_quick_operation_rejects_bad_restore` passed and expose live - quick-panel planning as JSON automation. -- `pp_app_core_tools_menu_tests` passed, covering Tools submenu routing, - root-closing commands, platform-only SonarPen gating, executor dispatch, - unavailable no-op actions, floating panel chrome metadata, already-visible - panel no-ops, and animation panel non-droppable state. -- `pano_cli_plan_tools_menu_shortcuts_smoke`, - `pano_cli_plan_tools_menu_sonarpen_unavailable_smoke`, - `pano_cli_plan_tools_panel_layers_smoke`, - `pano_cli_plan_tools_panel_visible_noop_smoke`, and - `pano_cli_plan_tools_panel_rejects_unknown` passed and expose live Tools - menu/panel planning as JSON automation. -- `pp_app_core_about_menu_tests` passed, covering About/help/what's-new dialog - routing, versioned what's-new labels, crash diagnostic gating, performance - workload metadata, no-canvas performance-test blocking, dispatch through the - `AboutMenuServices` executor boundary, and no-op unavailable actions. -- `pano_cli_plan_about_menu_news_smoke`, - `pano_cli_plan_about_menu_performance_no_canvas_smoke`, - `pano_cli_plan_about_menu_crash_disabled_smoke`, and - `pano_cli_plan_about_menu_rejects_unknown` passed and expose live About menu - planning as JSON automation. -- `pp_app_core_main_toolbar_tests` passed, covering live toolbar/status direct - dialog routing, undo/redo availability, clear-history availability, no-canvas - clear blocking, negative history metric rejection, and dispatch through the - `MainToolbarServices` executor boundary without invoking no-op actions. -- `pano_cli_plan_main_toolbar_undo_smoke`, - `pano_cli_plan_main_toolbar_redo_empty_smoke`, - `pano_cli_plan_main_toolbar_clear_canvas_no_canvas_smoke`, and - `pano_cli_plan_main_toolbar_rejects_negative_count` passed and expose live - toolbar/status planning as JSON automation. -- `PanoPainter`, `pp_app_core_main_toolbar_tests`, - `pp_app_core_app_dialog_tests`, and `pano_cli` built after toolbar - test-message dialog metadata moved into `pp_app_core` and live message-box - creation routed through `src/legacy_app_dialog_services.*`. -- Focused main-toolbar/app-dialog CTest coverage passed for - `pp_app_core_main_toolbar_tests`, `pp_app_core_app_dialog_tests`, - `pano_cli_plan_main_toolbar_*`, and `pano_cli_plan_app_dialog_*`, including - the toolbar message-box dialog metadata smoke. -- Android arm64 headless `pp_app_core`, `pano_cli`, - `pp_app_core_main_toolbar_tests`, and `pp_app_core_app_dialog_tests` built - after the toolbar message-box bridge split. -- `pp_app_core_document_sharing_tests` passed, covering saved-path gating before - platform share execution. -- `pano_cli_plan_share_file_unsaved_smoke` and - `pano_cli_plan_share_file_saved_smoke` passed and expose app-core share - decisions as JSON. -- `pp_app_core_document_platform_io_tests` passed, covering empty selected-path - filtering and non-empty picked-path callback planning before platform picker - callbacks, plus empty/non-empty display-file planning before platform - display callbacks, plus virtual keyboard show/hide planning before platform - keyboard callbacks, plus cursor visibility planning before platform cursor - callbacks, plus clipboard read/write planning before platform clipboard - callbacks. -- `pano_cli_plan_picked_path_empty_smoke` and - `pano_cli_plan_picked_path_selected_smoke` passed and expose app-core picker - selected-path decisions as JSON. -- `pano_cli_plan_display_file_empty_smoke` and - `pano_cli_plan_display_file_selected_smoke` passed and expose app-core - display-file decisions as JSON. -- `pano_cli_plan_keyboard_visibility_hidden_smoke` and - `pano_cli_plan_keyboard_visibility_visible_smoke` passed and expose app-core - virtual keyboard decisions as JSON. -- `pano_cli_plan_cursor_visibility_hidden_smoke` and - `pano_cli_plan_cursor_visibility_visible_smoke` passed and expose app-core - cursor visibility decisions as JSON. -- `pano_cli_plan_clipboard_read_smoke`, - `pano_cli_plan_clipboard_write_smoke`, and - `pano_cli_plan_clipboard_write_empty_smoke` passed and expose app-core - clipboard decisions as JSON, including empty write text. -- `pp_platform_api_tests` passed, covering the SDK-free `PlatformServices` - interface for startup storage path preparation, clipboard read/write, empty - clipboard writes, cursor visibility dispatch, virtual-keyboard visibility - dispatch, external file display dispatch, file sharing dispatch, native - app/window close dispatch, UI-thread lifecycle dispatch, render-context - lifecycle dispatch, render-target binding dispatch, render platform hint - dispatch, render debug callback dispatch, render-capture frame hook dispatch, - recording cleanup dispatch, exported-image publish dispatch, persistent - storage flush dispatch, document browse-root dispatch, - working-directory picker policy and display-path formatting dispatch, - canvas input tip visibility and pressure remap dispatch, - native UI/window state save dispatch, prepared-file writable target dispatch, - prepared-file export-dialog policy dispatch, work-directory document export - collection policy dispatch, network TLS verification policy dispatch, - default network TLS policy coverage, PPBR export data-directory policy - dispatch, SonarPen availability/startup dispatch, VR lifecycle dispatch, - layout/asset file load policy coverage, - live asset/layout reload policy dispatch, - diagnostic hook dispatch, per-frame platform hook dispatch, picker callback - dispatch, and prepared-file save/download callback dispatch. The live Windows - app now - consumes this interface through an injected - `WindowsPlatformServices` instance isolated in - `src/platform_windows/windows_platform_services.*`; other platforms still - use the legacy fallback adapter, now isolated in - `src/platform_legacy/legacy_platform_services.*` instead of being owned by - `app_events.cpp`. -- `panopainter_validate_shaders` passed, validating 25 shader programs and 7 - shader includes for stage markers and include graph integrity. -- `pp_renderer_gl_capabilities_tests` passed on default MSVC, vcpkg-headless, - and Android arm64 configure/build, covering framebuffer fetch, map-buffer - alignment, desktop GL core float support, GLES float/half-float extensions, - WebGL exclusion behavior, upload types for RGBA8/RGBA16F/RGBA32F internal - formats, image channel-count format mapping including invalid counts, and - RGBA8/RGBA32F readback format and byte-count mapping, PBO pixel-buffer - target/usage/access mapping, framebuffer status names, framebuffer blit color - mask and linear/nearest filters, plus Shape index-type and fill/stroke primitive mode mapping, - PanoPainter cube-face texture-target order, and the linear clamp-to-edge - render-target texture parameter set used by `RTT::create`. - Sampler parameter validation covers wrap S/T/R plus min/mag filter ordering - used by legacy `Sampler::set` and `Sampler::set_filter`, plus the desktop - border-color parameter name used by `Sampler::set_border`. - Legacy `TextureCube` allocation/bind/delete and `Sampler` - create/configure/border/bind/unbind paths now execute through tested - `pp_renderer_gl` dispatch contracts, keeping cube-map and sampler resource - lifecycle reachable without a live GL context. - Shader attribute binding catalog validation covers the current `pos`, `uvs`, - `uvs2`, `col`, and `nor` bindings and rejects empty, unnamed, null-name, and - duplicate-name catalogs while preserving legacy shared locations. Shader - uniform catalog validation covers the 43 legacy uniform - names used by `Shader`, preserves the legacy hash ids, and rejects empty, - unnamed, null-name, mismatched-hash, and duplicate-name catalogs. - Legacy `Shader` program use/delete, uniform writes, attribute-location - lookups, shader source compilation, shader deletion, program attach/link, - attribute rebinding, active-uniform count/enumeration, and uniform-location - discovery now execute through tested `pp_renderer_gl` dispatch contracts via - `legacy_gl_shader_dispatch`. - Retained `Shape`, `TextMesh`, and `NodeColorWheel` mesh buffer/VAO creation, - zero-byte dynamic-buffer creation, dynamic buffer uploads, indexed and - non-indexed draws, and resource deletion now execute through tested - `pp_renderer_gl` dispatch contracts via `legacy_gl_mesh_dispatch`. -- `pp_renderer_gl_command_plan_tests` covers the headless OpenGL command - planner for recorded render-pass clear masks/values, viewport/scissor state, - blend/depth/sampler state, texture format mapping, mesh/draw primitive modes, - draw counts, shader bind/uniform names and byte counts, texture - upload/mipmap/transition/copy/readback/capture metadata, blit filters and - byte totals, planned command names, unsupported enum/state rejection, whole - recorded stream planning, valid trace/render/shader/draw/blit ordering, typed - texture-command counts, broken render-pass order detection, and executable - draw/uniform dependency failures. -- PowerShell analyze automation returns JSON summaries and includes the shader - validation target and renderer-boundary guard. -- `windows-msvc-vcpkg-headless` configured through the Visual Studio bundled - vcpkg root, installed the manifest dependencies, built the headless component - matrix, and passed `desktop-fast-vcpkg`. -- `pp_ui_core` built and tested against vcpkg tinyxml2 on - `windows-msvc-vcpkg-headless` and against the vendored fallback on - `windows-msvc-default` and `android-arm64`. -- `windows-clangcl-asan` configures headlessly with clang-cl 18.1.8 and - release MSVC runtime selection; build remains blocked and debt-tracked in - DEBT-0014 because the selected VS 2026-preview STL requires Clang 20 or - newer. -- `PanoPainter.exe` built through CMake at - `out/build/windows-msvc-default/Debug/PanoPainter.exe`. -- PowerShell build/test automation wrappers return JSON summaries and passed - local smoke checks. -- Renderer-boundary automation fails if active non-backend source code - reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL - implementation files. -- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict - renderer feature flags, renderer-owned resource factory and - command-order/render-pass-clear/scissor-state/depth-state/blend-state/ - texture-usage/texture-bind/sampler-bind/shader-uniform/texture-upload/ - mipmap-generation/texture-transition/readback/frame-capture/blit validation plus explicit draw - descriptor and texture-copy validation; it creates validated textures, - render targets, shaders, meshes, and readback buffers with validated debug - labels, then records commands, trace markers/scopes, render-pass - color/depth/stencil clear intent, scissor state, depth state, blend state, - shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw - ranges, texture uploads/mipmap generations/state transitions/copies/readbacks, frame captures, - and render-target blits, giving automation a backend-neutral render path that - does not require a window or GL context. Clearing the recording device now - resets active render-pass and trace-scope state so interrupted automation can - reuse a recorder without carrying stale frame state forward. -- `pano_cli record-render` exercises that headless recording renderer and emits - JSON command counts, backend feature flags, resource creation counts, target dimensions, backend - name, trace marker/scope and draw summary, labeled descriptor counts, - render-pass/depth-clear counts, and draw - descriptor vertex/index totals, scissor/depth/blend-state plus - shader-uniform/texture/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/ - frame-capture/blit command/byte totals for agent automation. When - `pp_renderer_gl` is available, it also emits an `openGlPlan` JSON object with - planned command count, support status, render-pass/draw/shader-bind/uniform/ - texture-upload/mipmap/transition/copy/readback/capture/passthrough/trace - counts, unsupported command count, render-pass order error count, dependency - error count, and unclosed-pass state. The - `--exercise-clear` mode deliberately clears an interrupted trace/render pass, - verifies stale trace-scope state is rejected, verifies the render context can - be reused, and then emits that reset status in JSON. It also has an - expected-failure smoke for oversized render/readback targets. -- `pano_cli simulate-document-history` exercises pure document history - apply/undo/redo behavior and emits JSON layer/frame/history state for agent - automation. -- `pano_cli simulate-document-edits` exercises pure document layer/frame edit - operations and emits JSON metadata, frame order, face-payload state, and - selection-mask state for agent automation. -- `pano_cli simulate-image-import` exercises embedded PNG decode through - `pp_assets` and `pp_document` face-payload attachment through JSON - automation. -- `pano_cli import-image` accepts a PNG file path, decodes RGBA8 pixels through - `pp_assets`, attaches them to a pure `pp_document` face payload, and has - checked-in decodable-PNG plus truncated-PNG rejection smoke tests. -- `pano_cli export-image` writes deterministic RGBA8 PNGs through `pp_assets` - and has a save/import round-trip smoke test. Full legacy canvas export - remains a future `pano_cli` task. -- `pano_cli save-project` exposes generated multi-layer, multi-frame PPI - writing with layer metadata and targeted dirty-face layer/frame payloads - through JSON automation and is covered by metadata-only and - dirty-face-payload save/load round-trip smoke tests. Full legacy canvas save - parity remains tracked by DEBT-0013. -- `pp_assets::create_ppi_project` exposes the underlying generated PPI writer - for non-uniform layer metadata and frame-duration extraction work. -- `pp_document::export_ppi_project_document` exposes pure document-to-PPI byte - export through CTest coverage; legacy Canvas save integration remains tracked - by DEBT-0010/DEBT-0013. -- `pano_cli simulate-document-export` exposes the same export path through JSON - automation for agents. -- `pano_cli save-document-project` exposes file-writing document export - automation for inspect/load round trips. -- `pano_cli apply-stroke-script` exposes file-driven stroke-script application - to a pure document face payload and writes a PPI artifact for inspect/load - round-trip automation. -- Live document save/session paths consume the payload-bearing canvas snapshot - boundary before retained PPI serialization: existing Save, Save As, Save - Version, and save-before-workflow now log payload completeness and PPI - readiness from `pp_app_core` while legacy `Canvas::project_save` still owns - the actual file write, progress/threading behavior, and compatibility quirks. -- `pp_app_core` now exposes - `export_document_canvas_save_snapshot_to_ppi`, which refuses snapshots that - still need renderer payload readback and exports payload-complete or - metadata-only snapshots through the pure `pp_document` PPI writer. The - document-canvas tests decode the generated bytes, and - `pano_cli plan-canvas-document-snapshot` reports `ppiExport` readiness, - byte count, and dirty-face count for agent automation. -- Payload-complete canvas snapshot automation also crosses the renderer - boundary now: `pano_cli plan-canvas-document-snapshot` feeds the same snapshot - through `pp_paint_renderer::prepare_document_frame_export_readiness`, which - records the renderer-neutral active-frame texture upload stream and encodes - the composited active-frame cube faces as PNG bytes, so agents validate the - same document/canvas-to-renderer readiness report consumed by live export. It - also emits `depthExport` readiness from the paint-renderer depth plan, keeping - CLI automation aligned with the live depth export adapter while final renderer - readback remains retained. -- Live image/collection/cube export adapters now prepare and log the same - document/canvas plus shared renderer-upload and face-PNG export readiness - reports. Cube-face export now writes the pure document/renderer PNG bytes to - the `pp_app_core` planned legacy face filenames through a tested - write/publish service executor before falling back to retained `Canvas` - execution on failure. PNG equirectangular export now writes a - `pp_paint_renderer` equirectangular PNG from the same composited document - frame through the app-core file write/publish executor before falling back to - retained `Canvas` execution; payload-complete - layer and animation-frame PNG collections now write pure - `pp_paint_renderer` equirectangular PNG sequences through a tested app-core - collection write/publish executor before retained fallback. Depth export now - prepares the same document/canvas snapshot, logs the shared renderer-upload - readiness report, and records a tested paint-renderer depth render plan before - retained `Canvas` execution. JPEG equirectangular export now writes a - pure `pp_paint_renderer`/`pp_assets` JPEG with GPano XMP metadata through the - same app-core file write/publish executor before retained fallback. - Web prepared-file handoff, video, and incomplete-readback - collection cases remain on their prior retained writer paths. Actual broader - writer replacement remains tracked under export debt. -- Snapshot creation now rejects invalid embedded RGBA8 face payloads before - document export or history can persist malformed state. -- Package-smoke wrappers validate the Windows CMake app executable/runtime - `data/` copy and report structured package readiness for AppX, Android - standard/Quest/Focus APKs, Apple bundles, Linux app output, and WebGL - outputs. Actual package building remains blocked by DEBT-0011 until those - targets are migrated to root CMake. Readiness-only mode now reports the same - matrix without building the app first, and the package readiness self-test - keeps wrapper package kinds, retained Linux/WebGL CMake metadata, and blocker - metadata aligned. Root CMake target - `panopainter_linux_webgl_package_readiness` now exposes the filtered retained - Linux/WebGL readiness matrix from the CMake target graph. -- Android standard arm64/x64, Quest arm64, and Focus/Wave arm64 configure - through the platform-build wrapper by default. Focused validation compiled - representative headless component/tool targets across all four presets, and - the full refreshed component/test matrix remains the default gate for local - platform sweeps. The retained Android standard package CMake path also now - configures/builds `native-lib` directly for arm64 using C++23 and the shared - modern component source set, while Quest and Focus package CMake paths - configure with the same compatibility helper and current Yoga source list. - The Android platform-build wrapper and retained package helper now query - `sdkmanager`, install newer/missing SDK Manager NDK/CMake packages when - needed, select that pair automatically, and report the selected versions plus - update decisions in their structured output. `package-smoke.ps1 - -ReadinessOnly -AndroidNativeChecks -PackageKinds - android-standard-apk,android-quest-apk,android-focus-apk` now runs those - retained native checks from the package-smoke surface while keeping APK - readiness blocked on root CMake package-target migration. Root CMake has - named package validation targets for the native gate; - `cmake --build --preset windows-msvc-default --config Debug --target - panopainter_android_native_package_smoke` validates the latest SDK-managed - NDK/CMake pair and reports the still-blocked APK package state. -- The Windows app artifact package smoke is reachable from root CMake via - `cmake --build --preset windows-msvc-default --config Debug --target - panopainter_windows_app_package_smoke`, which builds the CMake `PanoPainter` - app target, validates the executable/runtime `data/` copy, and reports the - still-blocked Windows AppX package state. -- Retained Linux and WebGL app CMake files now use CMake 3.10 and target-level - C++23 instead of global C++14 flags; `python - scripts/dev/check_retained_platform_cmake.py` and CTest - `panopainter_retained_platform_cmake_self_test` guard those baselines while - the actual Linux/WebGL app/package targets remain outside root CMake. -- Root CMake exposes validation targets - platform-build sweep, the Android standard/Quest/Focus root CMake asset - component sweep, the vcpkg-backed UI core dependency boundary, and the - archived. `cmake --build --preset - windows-msvc-default --config Debug --target - panopainter_platform_build_android_assets` validated `pp_assets` across - Android arm64, Android x64, Quest arm64, and Focus/Wave arm64 with the - latest SDK-managed NDK/CMake pair; `cmake --build --preset - windows-msvc-default --config Debug --target - panopainter_platform_build_vcpkg_ui_core` validated `pp_ui_core` and - `pp_ui_core_layout_xml_tests` through the vcpkg tinyxml2 preset. -- Desktop VR drawing now routes generic OpenGL scissor/depth/blend state, - blend/depth state snapshots and restores, depth clears, active texture units, - and fallback 2D texture unbinds through tested renderer GL backend dispatch; - platform VR SDK bridges remain isolated for later platform-shell extraction. - Eye framebuffer viewport execution in the retained HMD path also routes - through tested `pp_renderer_gl` viewport dispatch. -- Canvas mode overlay, mask, and transform paths now route generic OpenGL - blend/depth state execution, active texture unit switches, transform/cut - viewport execution, 2D framebuffer-to-texture copy dispatch, RGBA8 readback - formats, and RTT-backed transform history region readbacks through the - renderer GL backend mapping. Canvas-tip pick readback now routes through the - tested framebuffer readback dispatch using the active read framebuffer, with - only local OpenGL adapter endpoints retained in `src/canvas_modes.cpp`. - Paint-mode blend/depth state snapshots also use tested capability-state query - dispatch. -- `NodeCanvas` panorama UI rendering now routes sampler defaults, saved - viewport/clear/blend/depth/scissor state, tested viewport and clear-color - query dispatch, color clears, clear-color restore, active texture units, - fallback 2D texture unbinds, 2D framebuffer-to-texture copy dispatch, and - RGBA8 render-target formats through the renderer GL backend mapping. - Its live viewport, generic blend/depth/scissor capability changes, and - density/offscreen color-buffer clears now execute through tested - `pp_renderer_gl` dispatch adapters, and its saved blend/depth/scissor state - queries now use tested capability-state query dispatch. -- Canvas resource setup now routes stroke-buffer RGBA8/RGBA16F/RGBA32F - formats, flood-fill texture upload format/type, brush/stencil/mix sampler - filters and wraps, and cube-strip import channel formats through the renderer - GL backend mapping. The clamp-to-border sampler wrap is now cataloged and - tested in `pp_renderer_gl`. -- Early canvas draw helpers now route pick readbacks, stroke mixer depth/scissor - and blend state, saved viewport/clear-state queries, active texture units, - fallback 2D texture unbinds, and stroke background copy targets through the - renderer GL backend mapping. Stroke mixer viewport/scissor execution also - routes through the tested backend dispatch contract. -- Canvas stroke commit now routes saved viewport/clear/blend state, history - readbacks, active texture units, fallback 2D texture unbinds, and layer - compositing copy targets through the renderer GL backend mapping; the - RTT-backed dirty-region readbacks now execute through the retained `RTT` - region-readback helper rather than direct `glReadPixels`, and 2D framebuffer - copies now execute through the retained utility bridge instead of direct - `glCopyTexSubImage2D`. -- Canvas layer merge rendering and explicit layer-merge compositing now route - depth/blend state, active texture units, fallback 2D texture unbinds, and - merge framebuffer copy targets through the renderer GL backend mapping. -- Canvas layer cube/equirect generation and frame clears now share - `legacy_ui_gl_dispatch` for active-texture selection, cube texture binding, - viewport execution, blend capability execution, clear-color query/restore, - and color-buffer clear adapter endpoints backed by tested renderer GL - backend dispatch contracts. The cube-face framebuffer-to-texture copy now - uses the shared retained target-aware utility bridge and remains tracked by - DEBT-0036 until renderer services own copy execution. -- `NodePanelGrid` live heightmap drawing and bake setup now route depth/blend - state, depth clears, color-write-mask toggles, active texture selection, and - bake viewport execution through tested renderer GL backend dispatch - contracts. Its desktop texture-resize readback now uses the retained - `Texture2D::get_image()` helper, so it consumes the same tested - framebuffer-backed texture readback dispatch instead of `glGetTexImage`. - Grid depth-state and viewport snapshots now also use tested backend query - dispatch. -- Retained simple UI draw paths now share `legacy_ui_gl_dispatch` for - blend-state execution, fallback 2D texture unbinds, `NodeViewport` viewport - query/restore, color-buffer clear, and clear-color restore. This covers - `NodeBorder`, `NodeImage`, `NodeImageTexture`, `NodeColorWheel`, - `NodeAnimationTimeline`, `NodeScroll`, `NodeText`, `NodeTextInput`, and - `NodeViewport` without changing their legacy draw ordering. -- Retained paint UI surface paths now use tested `pp_renderer_gl` viewport - query, clear-color query, clear-color restore, and color-buffer clear helpers - in `NodeCanvas` and `NodeStrokePreview`, removing direct query/clear calls - from those draw bodies while keeping their legacy compositing order. Their - active-texture, fallback texture unbind, viewport/scissor, clear-color, - color-buffer clear, and capability query/apply adapter endpoints are now - centralized in `legacy_ui_gl_dispatch` instead of being duplicated in each - node implementation. -- Retained Canvas stroke draw/commit, thumbnail, object-render, and - `LayerFrame::clear` paths now use the same tested backend viewport query, - clear-color query, and clear-color restore helpers, removing direct - viewport/clear-state queries from `src/canvas.cpp` and the frame clear path. - Their active-texture selection, fallback 2D texture unbind, viewport/scissor - execution, clear-color restore, and capability query/apply adapter endpoints - now share `legacy_ui_gl_dispatch`. -- Retained Canvas and RTT depth renderbuffer allocation, framebuffer depth - attachment, and renderbuffer deletion now share - `legacy_gl_renderbuffer_dispatch`, removing duplicated raw renderbuffer - callbacks from `src/canvas.cpp` and `src/rtt.cpp` while resource lifetime - ownership remains open under DEBT-0036. -- Retained `Texture2D`, `TextureCube`, and RTT texture allocation, deletion, - binding, parameter setup, 2D update, and mipmap dispatch now share - `legacy_gl_texture_dispatch`, removing duplicated raw texture callbacks from - `src/texture.cpp` and `src/rtt.cpp` while texture resource ownership remains - retained under DEBT-0036. -- Retained `Texture2D` readback plus RTT framebuffer allocation, deletion, - bind/restore, blit, readback, and PBO readback dispatch now share - `legacy_gl_framebuffer_dispatch`, removing duplicated raw framebuffer and - readback callbacks from `src/texture.cpp` and `src/rtt.cpp` while - framebuffer/readback ownership remains retained under DEBT-0036. -- Retained `Sampler` create, parameter, border-color, bind, and unbind dispatch - now share `legacy_gl_sampler_dispatch`, and retained `PBO` allocation, - framebuffer readback, map, unmap, and delete dispatch now share - `legacy_gl_pixel_buffer_dispatch`; this removes another pair of raw resource - callback clusters from `src/texture.cpp` and `src/rtt.cpp` while sampler and - pixel-buffer ownership remain retained under DEBT-0036. -- Retained `Shape`, `TextMesh`, and `NodeColorWheel` mesh buffer/VAO creation, - dynamic vertex/index uploads, fill/stroke/text draws, and buffer/VAO deletion - now share `legacy_gl_mesh_dispatch`, removing duplicated raw mesh callback - clusters from `src/shape.cpp`, `src/font.cpp`, and `src/node_colorwheel.cpp` - while mesh resource ownership remains retained under DEBT-0036. -- Retained shader source compilation/deletion, program attach/link/use/delete, - attribute rebinding and location lookup, active-uniform enumeration, - uniform-location discovery, and vec/mat/scalar uniform writes now share - `legacy_gl_shader_dispatch`, removing duplicated raw shader/program/uniform - callback ownership from `src/shader.cpp` while shader-program ownership - remains retained under DEBT-0036. -- Retained `gl_state` save/restore and `copy_framebuffer_to_texture_target` - now reuse `legacy_ui_gl_dispatch`, `legacy_gl_framebuffer_dispatch`, - `legacy_gl_shader_dispatch`, and `legacy_gl_sampler_dispatch`, removing the - local raw state/copy callback cluster from `src/util.cpp` while renderer - state and framebuffer-copy execution remain retained under DEBT-0036. -- Retained app startup, app clear, app UI viewport/scissor, command-convert - renderer state, and desktop VR draw-state endpoints now share - `legacy_ui_gl_dispatch` for capability, blend equation, clear, viewport, - scissor, active-texture, and 2D texture-unbind callbacks, removing duplicated - local raw callback clusters from `src/app.cpp`, `src/app_commands.cpp`, and - `src/app_vr.cpp` while app/VR renderer execution remains retained under - DEBT-0036. -- Retained RTT clear and masked-clear endpoints now share - `legacy_ui_gl_dispatch` for boolean color-mask query, color-mask apply, - clear-color, and buffer-clear callbacks, removing the local raw clear - callback cluster from `src/rtt.cpp` while RTT render-target execution remains - retained under DEBT-0036. -- Retained app startup logging, Windows early context logging/window-title - detection, and shader capability detection now share - `legacy_gl_runtime_dispatch` for runtime string and extension enumeration - callbacks, plus the global OpenGL error drain and Windows debug message - callback installation now use the same retained runtime dispatch. This removes - duplicated raw runtime-query clusters from `src/app.cpp`, `src/main.cpp`, and - `src/app_shaders.cpp` and the last active one-off runtime entrypoints from - `src/util.cpp` and `src/platform_windows/windows_platform_services.cpp` while - runtime/capability probing remains retained under DEBT-0036. -- Retained desktop HMD viewport setup, text atlas texture-unit activation, - Windows/legacy platform default framebuffer binding, and platform render-hint - enable callbacks now reuse `legacy_ui_gl_dispatch` instead of local raw GL - adapters while renderer/context ownership remains retained under DEBT-0036 - and DEBT-0017. -- Canvas draw-merge shader-blend selection now consumes the extracted - `pp_paint_renderer` stroke composite planner for current layer and primary - brush blend modes, while preserving legacy OpenGL compositing execution under - DEBT-0036. -- `NodeCanvas` panorama rendering now consumes the same tested - `pp_paint_renderer` canvas blend-gate planner as `Canvas::draw_merge`, so - layer and primary-brush blend-trigger compatibility is centralized. -- Shader initialization now publishes the OpenGL backend's renderer-neutral - feature snapshot through the legacy shader manager, and live canvas blend - gates consume that `RenderDeviceFeatures` value instead of hand-built - framebuffer-fetch/texture-copy flags. -- Canvas draw-merge and `NodeCanvas` panorama shader-blend paths now use the - shared canvas blend-gate plan to decide whether they can read destination - color through framebuffer fetch or must copy the destination texture before - the legacy OpenGL blend draw. -- `STR-016` completed on 2026-06-13 in `b9ed78e1`; the per-layer composite - block moved out of `Canvas::draw_merge()`, and `draw_checkerboard`/helper-shape is archived. -- `Canvas::draw_merge_branch_orchestration()` and - `Canvas::draw_merge_temporary_paint_branch()` later kept narrowing - `DEBT-0036`; archived. -- Canvas main-brush, dual-brush, and stroke-pad draw paths now use the tested - `pp_paint_renderer` stroke-feedback plan to decide whether framebuffer fetch - supplies destination color or the legacy OpenGL path must copy the target - texture before drawing. -- Canvas stroke commit ordering now has a tested `pp_paint_renderer` - `CanvasStrokeCommitSequencePlan` for history readback, dirty-state update, - scratch copies, erase/composite draw selection, dilate, and texture slot - roles. A retained commit adapter skeleton consumes that semantic sequence, - while the live Canvas body still owns history/layer mutation and OpenGL - execution until the next wiring slice. -- Live `Canvas::stroke_commit` now consumes that semantic commit sequence - through retained callbacks, so the legacy body no longer owns the loop order - directly. The callbacks still execute the existing OpenGL RTT, texture, - sampler, shader, history, and layer mutation work under DEBT-0036. -- `Canvas::layer_merge` now shares the retained stroke composite shader helper - for its source-over-destination `CompDraw` uniform setup, while keeping the - existing layer dirty mutation, framebuffer copy, texture binding, and draw - order local to the Canvas layer-merge path. -- `Canvas::draw_merge` now shares a retained draw-merge shader setup helper for - checkerboard backgrounds and the final merged texture redraw over the grid; - layer traversal, blend destination copies, sampler/texture binding, and draw - ordering remain in the legacy Canvas path. -- `Canvas::draw_merge` non-stroke layer blending now shares the retained - draw-merge shader setup helper for `TextureBlend` uniforms, preserving the - optional `TexBG` uniform when copy-based blend destination feedback is active. - Layer traversal, framebuffer copies, sampler/texture binding, and draw - ordering remain in the legacy Canvas path. -- `NodeStrokePreview` preview background drawing now shares the retained - draw-merge shader setup helper for checkerboard uniforms, preserving its - colorize condition and preview MVP while leaving capture ordering, texture - copies, stroke setup, and draw execution in the retained preview path. -- `NodeCanvas` live temporary-stroke drawing now shares the retained stroke - composite shader setup helper for static `CompDraw` uniforms, while keeping - per-onion-frame alpha updates, texture binding, temporary stroke texture - selection, and draw execution in the retained node path. -- `NodeCanvas` live temporary-erase drawing now uses a retained stroke erase - shader setup helper for `CompErase` uniforms, while keeping per-onion-frame - alpha updates, texture binding, temporary erase texture selection, and draw - execution in the retained node path. -- `Canvas::stroke_commit` and `Canvas::draw_merge` retained erase compositing - now share the stroke erase shader setup helper, while texture binding, mask - RTT binding, dirty/layer mutation, framebuffer feedback, and draw execution - remain in retained Canvas code. -- Desktop VR temporary erase/draw compositing now shares the retained stroke - erase and composite shader setup helpers, while sampler/texture binding, - per-eye/view transforms, temporary stroke texture selection, and draw - execution remain in retained VR code. -- `CanvasModeMaskCut::apply` retained mask-cut compositing now shares the - stroke composite shader setup helper, while render-task ordering, framebuffer - copy bounds, sampler/texture binding, and draw execution remain in retained - canvas-mode code. -- `NodeStrokePreview::draw` now shares the retained draw-merge texture shader - setup helper for preview `Texture`, `Tex`, and `MVP` setup, while preview - texture binding, sampler binding, and draw execution remain in the node path. -- `Canvas::import_layer` retained object drawing now shares the draw-merge - texture shader setup helper for flat and spherical imports, while texture - upload, sampler/texture binding, draw callbacks, and object geometry remain in - retained Canvas code. -- `Canvas::thumbnail_generate` checkerboard redraw and final texture blending - now share the retained draw-merge shader setup helpers, while destination - copies, sampler/texture binding, readback, and draw execution remain in - retained Canvas code. -- `Canvas::draw_objects` retained object compositing now shares the retained - draw-merge texture shader setup helper, while temporary face readback, - sampler/texture binding, draw execution, and dirty-box updates remain in - retained Canvas code. -- Desktop VR checkerboard background drawing now shares the retained draw-merge - shader setup helper, while render state transitions, per-plane transforms, - and draw execution remain in retained VR code. -- `Canvas::thumbnail_generate` thumbnail layer blending now shares the retained - draw-merge shader setup helper for per-layer `TextureBlend` uniforms, while - destination feedback copies, sampler/texture binding, grid redraw, readback, - and draw execution remain in retained Canvas code. -- `NodeImage` and `NodeImageTexture` retained plain image drawing now share the - retained draw-merge texture shader setup helper for `Texture`, `Tex`, and - `MVP` setup. Texture/sampler binding, atlas setup, blend state, and draw - execution remain in the node paths. -- `NodeViewport` preview drawing and `NodePanelGrid` sun-overlay drawing now - share the retained draw-merge texture shader setup helper for `Texture`, - `Tex`, and `MVP` setup. Texture/sampler binding, viewport/state setup, and - draw execution remain in the node paths. -- Retained `TextureAlpha` setup now shares the draw-merge shader setup helper - for Canvas draw-merge layer redraws, depth export merged-layer rendering, - NodeCanvas cached/live layer redraws, and desktop VR layer redraws, while - sampler/texture binding, render-task ordering, per-frame alpha updates, and - draw execution remain in retained code. -- Depth export `TextureColorize` setup now shares the retained draw-merge - shader setup helper, while sampler/texture binding, layer color selection, - and draw execution remain in retained Canvas code. -- `NodeCanvas` smoothing-mask `TextureMask` setup now shares the retained - draw-merge shader setup helper, while smoothing-mask texture binding, - per-face MVP updates, blend state, and draw execution remain retained. -- Slider `Color` and `ColorHue` setup now shares retained UI overlay helpers, - while slider geometry, hue direction, border drawing, and draw execution - remain retained. -- Scrollbar, text-input, and VR frame/cursor/controller shader setup now share - retained UI/preview helpers, while geometry, blend/depth state, - sampler/texture binding, and draw execution remain retained. -- Remaining live shader setup outside retained helper headers now shares - retained helper surfaces for canvas modes, equirect layer export, - `NodeCanvas` debug dirty bounds, atlas image drawing, and text drawing, while - geometry, framebuffer flow, texture/sampler binding, blend/depth state, - readback, and draw execution remain retained. -- `Canvas::stroke_draw` face dirty-box planning now shares a retained stroke - execution helper wrapping `pp_paint_renderer`, retained stroke commit step - dispatch clamps malformed step counts to the fixed plan array, and - compositor coverage now includes malformed retained commit plans plus - all-input stroke-preview composite planning. Live stroke rasterization, - callback execution, texture binding, and history mutation remain retained. -- `NodeStrokePreview::stroke_draw_compute` now shares the retained stroke - execution helper for preview quad frame planning. Preview sample execution, - mixer passes, texture binding, and final draw ordering remain retained. -- `NodeStrokePreview` preview stroke frame sequencing now shares one retained - local executor for dual and main preview sample traversal, and - `pp_paint_renderer_compositor_tests` now covers - `legacy_node_stroke_preview_execution_services.h` feedback fallback clamping - plus retained preview composite slot intent. Preview texture binding, - framebuffer copies, mixer ownership, and final OpenGL draw ordering remain - retained. -- `Canvas::stroke_draw` current and dual live sample frame/face traversal plus - dirty tracking now share one retained stroke execution helper surface, while - shader timing, sampler/texture binding, framebuffer ownership, pad execution, - and final OpenGL draw ordering remain in the legacy Canvas path. -- `Canvas::stroke_draw` main and dual live-pass dirty semantics now share one - retained helper around the face loop, current/dual dirty accumulation, and - dual-pass dirty preservation, while shader timing, sampler/texture binding, - framebuffer ownership, pad execution, and final OpenGL draw ordering remain - in the legacy Canvas path. -- `Canvas::stroke_draw` main and dual live-pass per-face framebuffer - bind/unbind execution now shares one retained helper, and pad-face array - assembly now shares one retained utility, while shader activation timing, - texture/sampler binding, framebuffer ownership, pad execution, and final - OpenGL draw ordering remain in the legacy Canvas path. -- `NodeStrokePreview` dual-pass and main-pass frame-loop execution plus - full-frame copy-back now share one local retained helper surface, while - checkerboard/background capture ordering, texture-unit binding, mixer - ownership, and final composite semantics remain in the preview node. -- `NodeStrokePreview` live-pass sampler binding, dual/main pass texture - binding, checkerboard/background capture wrapping, and final preview - copy-back now share named local helpers, while mixer state execution and - per-sample GL ordering remain in the preview node. -- `Canvas::stroke_draw` main, pad, and dual live-pass texture-input - binding/unbinding intent now shares retained stroke execution helpers, while - sampler binding, concrete GL object mapping, framebuffer ownership, and final - draw execution remain in the legacy Canvas path. -- `NodeStrokePreview::stroke_draw_samples` now shares one local helper for - destination bind/unbind, framebuffer copy callback wrapping, sample-point - assembly, and brush-vertex upload/draw, while mixer-pass state execution and - higher-level pass orchestration remain in the preview node. -- `Canvas::stroke_draw` live-pass sampler bind/unbind plus semantic - texture-input dispatch now shares retained stroke execution helpers, while - concrete GL object mapping, framebuffer ownership, shader timing, and final - draw execution remain in the legacy Canvas path. -- `Canvas::stroke_draw_samples()` now shares - `execute_legacy_canvas_stroke_sample_polygon(...)` for polygon - triangulation, sample-point assembly, and retained destination-copy / upload - / draw helper handoff, while direct GL callback wiring and the remaining - live draw ownership stay in the legacy Canvas path. -- `Canvas::stroke_draw_mix()` now shares - `execute_legacy_canvas_stroke_mix_pass(...)` for visible-plane filtering, - retained sampler/texture-slot binding, and final plane draw ordering, while - mixer framebuffer/state setup and per-plane shader material/MVP preparation - remain in the legacy Canvas path. -- `Canvas::stroke_draw_samples()` now shares - `execute_legacy_canvas_stroke_face_sample_polygon(...)` for face-indexed - destination bind/copy/unbind and brush upload/draw dispatch, while concrete - GL object callbacks remain in the legacy Canvas path. -- `Canvas::draw_merge()` non-erase live temporary-stroke composite now shares - `execute_legacy_canvas_stroke_temporary_composite(...)` for setup, sampler - bind, texture bind, draw, and texture unbind ordering, while erase-path and - broader final composite ownership remain in the legacy Canvas path. -- `Canvas::draw_merge()` erase live temporary-stroke composite now also shares - `execute_legacy_canvas_stroke_temporary_composite(...)`, leaving only the - concrete GL object callbacks and broader final composite ownership in the - legacy Canvas path. -- `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(...)`, while - temporary-stroke branch selection, framebuffer copies, and final merged-plane - ownership remain in the legacy Canvas path. -- `Canvas::draw_merge()` end-of-plane merged-texture copy plus optional - checkerboard/final-texture redraw ordering now routes through - `execute_legacy_canvas_draw_merge_final_plane_composite(...)`, while - per-plane iteration and the concrete framebuffer, sampler, texture, and draw - callbacks remain in the legacy Canvas path. -- `NodeStrokePreview::draw_stroke_immediate()` now shares - `execute_legacy_node_stroke_preview_pass_sequence(...)` for - dual-pass/background/main-pass/final-composite/copy-back ordering, while the - remaining local preview ownership is concentrated around `stroke_draw_mix()` - setup/execution. -- `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(...)`, while brush mutation, - camera wiring, retained `Stroke` population, and live GL execution remain in - the preview node. -- `NodeStrokePreview::stroke_draw_mix()` now shares one local helper for mixer - framebuffer bind/unbind, viewport/scissor/blend state, texture-slot - binding, and final plane draw, while material planning and shader uniform - setup remain in the preview node. -- `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(...)`, while the preview node - keeps only the concrete GL save/restore, texture-object bind, and plane-draw - callbacks. -- `pp_paint_renderer_stroke_execution_tests` now covers retained stroke texture - input binding order, sample execution destination-copy behavior, live-pass - face-framebuffer dirty tracking, and pad-face destination-copy behavior - without depending on the broader compositor test translation unit. -- `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 through - `legacy_canvas_stroke_preview_services.h`. -- `pp_paint_renderer_stroke_execution_tests` now also covers direct retained - frame-sample callback ordering and dirty-tracking wrapper timing/state - semantics, including pre-update dirty visibility and - `previous_pass_dirty_box` override behavior. -- `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. -- `Canvas::stroke_draw` pad-pass destination bind/copy/unbind ordering now - shares the retained stroke execution helper callback surface, while shader - setup, pad color selection, framebuffer ownership, and final OpenGL draw - remain in the legacy Canvas path. -- `NodeStrokePreview` final composite sampler/input binding and texture-slot - intent now share one local retained helper, while mixer execution, - per-sample stroke callbacks, framebuffer copies, and final OpenGL draw - ownership remain in the preview node. -- `Canvas::stroke_draw` pad-region planning now shares the retained stroke - execution helper wrapping `pp_paint_renderer`, while pad color selection, - dirty-face iteration, framebuffer copies, quad upload, and draw execution - remain retained. -- `Canvas::stroke_draw` pad-pass dirty-face iteration, pad-region planning, and - NDC quad assembly now share a retained stroke execution helper callback - boundary, while Canvas still owns framebuffer copies, brush-shape uploads, - and draw execution. -- `Canvas::stroke_commit` retained commit input texture/sampler binding, - erase/composite draw dispatch, committed-copy to the dilate scratch texture, - and dilate draw now share `legacy_canvas_stroke_commit_services.h`; history - readback, `ActionStroke` population, layer dirty-box mutation, and retained - RTT/framebuffer ownership remain in the legacy Canvas path. -- `Canvas::stroke_draw_compute` frame planning now shares the retained stroke - execution helper for brush-quad construction, mixer feedback bounds, 2D/3D - projection selection intent, and frame assembly, while legacy projection - geometry, stroke samples, and live draw execution remain retained. -- `Canvas::stroke_draw` current and dual stroke frame-face traversal now shares - the retained stroke execution helper, while framebuffer binding, shader - uniform timing, dirty-box mutation, sampler/texture binding, and live draw - execution remain retained. -- `Canvas::stroke_draw` current and dual stroke dirty-box mutation now shares - the retained stroke execution helper, while framebuffer binding, shader - uniform timing, sampler/texture binding, and live draw execution remain - retained. -- `Canvas::stroke_draw` current and dual stroke per-face framebuffer/sample - callback ordering now shares the retained stroke execution helper, while - framebuffer ownership, shader uniform timing, sampler/texture binding, and - live draw execution remain retained. -- Remaining simple color, hue, color-quad, grid heightmap, and pen/line - preview shader setup in UI nodes and canvas modes now shares retained helper - surfaces, while geometry, texture/sampler binding, blend/depth state, - readback, and draw execution remain retained. -- `NodeCanvas` density-resolve drawing and desktop VR UI drawing now share the - retained draw-merge texture shader setup helper, while render target, - sampler/texture binding, viewport/state restoration, and draw execution - remain in retained node/VR code. -- `CanvasModeTransform::on_Draw` and `CanvasModeFloodFill::on_Draw` now share - the retained draw-merge texture shader setup helper for `Texture`, `Tex`, and - `MVP` setup. Active texture selection, sampler/texture binding, and draw - execution remain in the canvas mode paths. -- `Canvas::stroke_draw_mix` now shares the retained stroke composite shader - helper for mixer-pass `CompDraw` setup, while preserving its caller-specific - texture slot uniforms. Mixer framebuffer/scissor state, sampler and texture - binding, and draw execution remain retained. -- `NodeCanvas` panorama drawing now uses the same retained draw-merge shader - setup helper for checkerboard grids and final cached-layer texture redraws; - onion-frame traversal, blend copies, texture binding, and draw order remain - retained in the node. -- `NodeCanvas` non-stroke layer blending now shares the retained draw-merge - shader setup helper for `TextureBlend` uniforms, preserving the optional - destination texture uniform when copy-based blending is active. Framebuffer - lifetime, destination copies, texture binding, and draw execution remain - local to the node. -- Canvas thumbnail layer blending now uses the same canvas destination-feedback - plan for framebuffer-fetch versus texture-copy decisions; the thumbnail draw - itself still executes through retained OpenGL canvas code under DEBT-0036. -- Canvas equirectangular import drawing and depth export rendering now route - depth/blend state and active texture units through the renderer GL backend - mapping. -- Canvas thumbnail generation and object-drawing helpers now route saved - viewport/clear/blend state, active texture units, readback format/type, - framebuffer copy targets, and depth renderbuffer allocation plus framebuffer - depth attach/detach through tested renderer GL backend dispatch contracts; - `src/canvas.cpp` no longer owns raw renderbuffer callbacks. -- Retained Canvas, NodeCanvas, NodeStrokePreview, and HMD viewport/scissor/ - capability execution now compiles through the renderer GL backend dispatch - adapters with `pp_legacy_paint_document`, `pp_panopainter_ui`, and - `panopainter_app`. CanvasMode overlay, mask, transform, and canvas-tip pick - paths now also consume the shared retained UI GL dispatch for active-texture, - capability query/apply, viewport, read-framebuffer query, and RGBA8 pixel - readback adapter endpoints, removing another local raw-GL adapter cluster - from `src/canvas_modes.cpp`. `NodePanelGrid` heightmap draw and bake setup - now uses the same bridge for active-texture, depth/blend capability - query/apply, viewport query/execution, depth clears, and color-write-mask - adapter endpoints instead of owning another local dispatch cluster. -- Windows desktop OpenGL context creation now consumes a tested - `windows_wgl_core_context_3_3_config()` catalog from `pp_renderer_gl`, moving - the active WGL context/pixel-format attribute literals out of the platform - entrypoint. -- Known remaining warnings: legacy project/vendor diagnostics, Visual Studio - vcpkg-manifest warning, `LNK4099` missing libyuv PDBs, and `LNK4098` runtime - library conflict from retained vendor binaries. - -## Current Debt Log - -The canonical debt log is now `docs/modernization/debt.md`. Keep this section -as a reminder only; do not add new debt entries here. - -| ID | Status | Owner | Item | Reason | Validation | Removal Condition | -| --- | --- | --- | --- | --- | --- | --- | -| DEBT-0001 | Open | TBD | Existing platform build files remain alongside new CMake | Required for incremental migration | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets | -| DEBT-0002 | Open | TBD | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace or document permanent vendored status after vcpkg triplet evaluation | -| DEBT-0003 | Open | TBD | Existing singletons remain during initial split | Avoid behavior changes while introducing boundaries | App launch and component tests | Replace singleton reaches with context/service injection at component boundaries | - -## Current Capability Map Seed - -Use this as the starting checklist for Phase 0 inventory. - -- Project I/O: PPI open/save, thumbnails, version metadata, autosave/save-as - flows. -- Image I/O: JPEG/PNG import/export, cube faces, equirectangular export, - depth export. -- Brush system: ABR import, PPBR import/export, presets, tip/pattern/dual brush, - pressure, jitter, blend modes. -- Painting: six cube faces, temporary stroke buffers, erase, flood fill, masks, - alpha lock, layer compositing. -- Layers and animation: layer add/remove/move/merge, blend/opacity/visibility, - frame add/remove/duplicate/duration, MP4/timelapse export. -- UI: XML layout, Yoga layout, panels, dialogs, color tools, brush tools, - layers, animation timeline, settings, shortcuts, manual/changelog/about. -- Input: mouse, keyboard, touch, gestures, Wacom tablet, stylus pressure, - VR controllers. -- Platform services: clipboard, file picker, save picker, directory picker, - share/display file, keyboard show/hide, cursor visibility. -- VR/platform variants: OpenXR desktop target with retained OpenVR fallback, - Quest, Focus/Wave, Android standard, - iOS/macOS, Linux, WebGL. -- Cloud/network: upload, download, browse, license/check flows. -- Recording/export: PBO readbacks, MP4 encoder, timelapse frames. +Use `docs/modernization/tasks.md` for the current architecture task bundles, +ordered by real code-moving priority. +Use `docs/modernization/tasks-done.md` only for history. diff --git a/docs/modernization/tasks-done.md b/docs/modernization/tasks-done.md new file mode 100644 index 00000000..0cd074e8 --- /dev/null +++ b/docs/modernization/tasks-done.md @@ -0,0 +1,5007 @@ +# Modernization Completed And Superseded Tasks + +Status: live +Last updated: 2026-06-16 + +This file is the archived task history that was previously kept in +`docs/modernization/tasks.md`. + +Active architecture work now lives in `docs/modernization/tasks.md`. +This archive keeps completed, blocked, and superseded task writeups so the +current queue can stay short and operational. + +## 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: tracker hygiene was normalized under `AUD-001`; + historical score rows still warrant caution before being used 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` recorded 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` recorded 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` recorded 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: + +- The first `desktop-gpu` golden/readback gate was added and validated. +- The gate was skipped with a clear message when no GPU/context was available. +- The roadmap and debt log recorded the golden coverage and the remaining debt. + +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: + +- The preview parity lane gained a second `desktop-gpu` GPU readback gate. +- The gate was skipped with a clear message when no GPU/context was available. +- The roadmap and debt log recorded the GPU readback coverage and the remaining + debt. + +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: + +- The compositor path gained a `desktop-gpu` readback gate alongside the + existing GPU fixtures. +- The gate was skipped with a clear message when no GPU/context was available. +- The roadmap and debt log recorded the compositor output coverage for this + gate. + +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 depended on the unpublished fmt submodule pointer pre-DEP-001 | 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: + +Historical tracker normalization was completed without changing code: duplicate +`STR-010`, duplicate/ambiguous `STR-016`, and duplicate `STR-030` were +documented, the completed-task table shape was normalized, and stale `pending` +rows archived. + +Done Checks: + +- Each historical task id has one active section or one 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|completed-task table shape" 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: 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` +Completed: 2026-06-13 (`b9ed78e1`) + +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`
`& .\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` | diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 728ded73..2fe57c48 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -1,5005 +1,663 @@ # Modernization Task Tracker Status: live -Last updated: 2026-06-15 +Last updated: 2026-06-16 -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. +This file now tracks only active architecture work. +Completed, blocked, and superseded task history moved to +`docs/modernization/tasks-done.md`. ## 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: tracker hygiene was normalized under `AUD-001`; - historical score rows still warrant caution before being used 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` +- Keep this file short. If a task is done, blocked for a long time, or no + longer relevant, move it to `tasks-done.md` instead of letting the active + queue sprawl. +- Keep tasks architecture-first. Build, test, tool, planner, CLI, and + automation cleanup are secondary unless they directly unblock or accompany a + real ownership transfer in the live app. +- Prefer coherent bundles over tiny adapter nibbles. Each task here should make + visible progress in a hotspot file or a legacy target, not just add another + seam around the same code. +- Use legacy-target shrinkage and hotspot reduction as the main progress signal. + If a slice does not materially reduce a large file or move code out of + `PP_PANOPAINTER_*` or `PP_LEGACY_*`, it is probably not first-priority work. +- Do not restart Vulkan, Metal, or package-only work until the app shell, + platform split, UI split, and canvas/render split are materially thinner. + +## Current Architecture Read + +- The extracted pure targets are real and useful: `pp_foundation`, + `pp_assets`, `pp_paint`, `pp_document`, `pp_renderer_api`, `pp_renderer_gl`, + `pp_paint_renderer`, `pp_ui_core`, `pp_platform_api`, and `pp_app_core`. +- The remaining app still lives mostly in legacy containment or thick shell + targets: + - `pp_panopainter_ui`: 34 files, about 9102 lines + - `panopainter_app`: 29 files, about 8817 lines + - `pp_legacy_paint_document`: 7 files, about 5709 lines + - `pp_legacy_app`: 20 files, about 4368 lines + - `pp_legacy_ui_core`: 20 files, about 3770 lines +- The biggest single-file choke points are still `src/canvas.cpp`, + `src/app_layout.cpp`, `src/canvas_modes.cpp`, `src/node.cpp`, + `src/main.cpp`, `src/node_panel_brush.cpp`, `src/node_stroke_preview.cpp`, + `src/node_canvas.cpp`, `src/app.cpp`, and `src/app_dialogs.cpp`. +- The platform boundary is not finished: + - `pp_platform_api` still compiles Apple implementation files + - Apple and Linux platform services still reach `App::I` + - `platform_legacy` is still part of the live app shell +- The app runtime boundary is not finished: + - render/UI queues are static `App` state + - detached workers still launch from canvas, cloud, brush, grid, preview, and + event code + - thread-affinity rules are enforced by convention and asserts instead of + explicit runtime contracts +- The UI ownership boundary is not finished: + - base `Node` still carries raw parent/manager pointers beside shared handles + - callback captures still frequently close over retained nodes and `App::I` + - checked overlay/lifetime helpers exist but are not yet the default scene + graph model +- Historical score/progress claims are not useful for prioritization here. + The live app still mostly runs through the same shell and hotspot files, so + the queue is now ordered by code movement instead. + +## Active Bundles + +### Priority Order + +- `P0`: shrink the biggest live app hotspots and legacy ownership first +- `P0`: make app runtime, UI ownership, and renderer access safe enough for + future backend work +- `P1`: finish supporting boundaries that unblock or stabilize the thinner app +- `P2`: only then clean up the remaining workflow adapters + +### Bundle 1 - Break The Canvas And Preview Hotspots + +Priority: `P0` + +Why this bundle is first: +This is where the biggest block of real app behavior still lives. +If these files stay large and stateful, the rest of the modernization still +looks like a wrapper around the old renderer shell. + +#### ARC-RND-001 - Split `canvas.cpp` Into Document State And Render Execution Shells + +Status: Ready + +Why now: +`src/canvas.cpp` is still the biggest single architectural blocker at about +4128 lines. + +Write scope: +- `src/canvas.cpp` +- `src/canvas_layer.cpp` +- `src/canvas_actions.cpp` +- `src/legacy_canvas_*services*.h` +- new `src/legacy_canvas_*.*` helpers if needed + +Read scope: +- `src/document/*` +- `src/paint_renderer/*` +- `src/renderer_api/*` + +Done when: +- `canvas.cpp` stops being the place where document state, render sequencing, + history reads, and GL-side execution all meet. +- Non-render state/query/update helpers move out first. +- The remaining file reads as a render shell with explicit helper boundaries. +- The touched slice makes a substantial reduction in inline ownership, not just + a thin wrapper extraction. + +Mini-model packet: +- Do not try to delete `Canvas` in one slice. +- Prioritize ownership separation over clever abstractions. +- Aim for a visible reduction in `canvas.cpp`, on the order of hundreds of + lines, not a token helper extraction. + +#### ARC-RND-002 - Isolate Preview And Canvas View Render Execution + +Status: Ready + +Why now: +`src/node_stroke_preview.cpp` and `src/node_canvas.cpp` still own a large amount +of live preview/canvas render sequencing around the renderer boundary. + +Write scope: +- `src/node_stroke_preview.cpp` +- `src/node_canvas.cpp` +- `src/legacy_node_stroke_preview_execution_services.h` +- `src/legacy_canvas_stroke_preview_services.h` +- `src/paint_renderer/compositor.*` + +Read scope: +- `src/renderer_api/*` +- `src/renderer_gl/*` + +Done when: +- Preview/canvas files stop carrying large inline render-pass orchestration. +- Concrete GL texture/bind/copy work is pushed behind explicit renderer-facing + service seams. +- The next renderer backend would have one place to implement preview/canvas + execution contracts instead of reading node code. +- The touched node files are materially smaller and less stateful afterward. + +Mini-model packet: +- Keep the existing `pp_paint_renderer` planner surface and extend it only when + the node files clearly need a missing renderer-owned callback contract. +- Prefer deleting inline orchestration over adding another planner layer around + the same node code. + +#### ARC-RND-003 - Replace App-Facing `Texture2D`/`RTT` Use With Renderer API Contracts + +Status: Ready + +Why now: +Future Vulkan and Metal work needs the live app to stop treating retained +OpenGL resource classes as the renderer boundary. + +Write scope: +- `src/node_canvas.cpp` +- `src/node_stroke_preview.cpp` +- `src/canvas.cpp` +- `src/texture.*` +- `src/rtt.*` +- `src/renderer_api/*` +- `src/paint_renderer/*` + +Read scope: +- `src/renderer_gl/*` +- `src/legacy_gl_*dispatch.h` +- `src/app_shaders.cpp` + +Done when: +- App/UI-facing render work talks to `pp_renderer_api` resource and command + abstractions or narrow retained renderer services, not directly to + `Texture2D`, `RTT`, or GL dispatch helpers. +- The remaining `Texture2D`/`RTT` references are contained in retained GL + backend/adapters with explicit removal conditions. +- Canvas, preview, and export paths expose the same execution contract a Vulkan + or Metal implementation would need. +- The touched slice shrinks app/UI render ownership instead of only renaming + wrappers. + +Mini-model packet: +- Do not start a Vulkan or Metal backend in this task. +- Use existing `pp_renderer_api` and `pp_paint_renderer` contracts first. +- Treat direct GL classes in UI/app code as debt to move behind backend-owned + services. + +### Bundle 2 - Thin The App Shell + +Priority: `P0` + +Why this bundle is next: +`app_layout.cpp`, `app_dialogs.cpp`, and `app.cpp` still make the modernized +targets look like helpers under one old monolith. + +#### ARC-APP-001 - Split `app_layout.cpp` Into UI Binding Modules + +Status: Ready + +Why now: +`src/app_layout.cpp` is still a 2026-line mixed file that builds menus, +attaches callbacks, computes planner inputs, and mutates UI state directly. + +Write scope: +- `src/app_layout.cpp` +- `src/legacy_app_shell_services.*` +- new `src/app_layout_*.*` or `src/legacy_*_ui_services.*` files if needed + +Read scope: +- `src/app_core/*menu*.h` +- `src/app_core/brush_ui.h` +- `src/app_core/document_layer.h` +- `src/app_core/main_toolbar.h` + +Done when: +- `app_layout.cpp` becomes a composition/binding file instead of a giant mixed + controller. +- File-menu, toolbar, tools-menu, about-menu, and layer-menu wiring each live + in named helper modules or services. +- The split follows planner/service boundaries already present in `pp_app_core`. +- The touched slice materially shrinks the file instead of just moving a few + lambdas around. + +Mini-model packet: +- Start by carving out one coherent family at a time, not by reshuffling lines. +- Preserve the current planner calls; the goal is ownership, not new behavior. +- Aim for a real file-size drop, not cosmetic decomposition. + +#### ARC-APP-002 - Split `app_dialogs.cpp` Into Workflow Adapters And Widget Openers + +Status: Ready + +Why now: +`src/app_dialogs.cpp` still mixes document workflow decisions, export routing, +dialog construction, and overlay ownership. + +Write scope: +- `src/app_dialogs.cpp` +- `src/legacy_app_dialog_services.*` +- `src/legacy_document_session_services.*` +- `src/legacy_document_open_services.*` +- `src/legacy_document_export_services.*` + +Read scope: +- `src/app_core/app_dialog.h` +- `src/app_core/document_session.h` +- `src/app_core/document_export.h` + +Done when: +- `app_dialogs.cpp` is reduced to thin entrypoints plus named helper modules. +- Dialog creation/opening is clearly separated from document/export workflow + routing. +- The remaining direct node-specific code is isolated to retained dialog + adapters. +- The slice removes a meaningful amount of mixed live ownership from + `app_dialogs.cpp`. + +Mini-model packet: +- Preserve existing planner usage. +- Prefer new narrow helper files over leaving another giant dialog utility file. +- Do not spend time extending dialog planners or CLI surfaces unless the live + adapter gets thinner in the same slice. + +#### ARC-APP-003 - Reduce `app.cpp` To Frame, Queue, And Composition Shell + +Status: Ready + +Why now: +`src/app.cpp` still carries startup, frame flow, queue draining, recording, +observer math, and composition logic in one 950-line file. + +Write scope: +- `src/app.cpp` +- `src/legacy_app_startup_services.*` +- `src/legacy_recording_services.*` +- small new `src/app_runtime_*.*` helpers if needed + +Read scope: +- `src/app_core/app_frame.h` +- `src/app_core/app_shutdown.h` +- `src/app_core/app_startup.h` +- `src/app_core/document_recording.h` + +Done when: +- `app.cpp` reads like a shell over `pp_app_core` planners and named retained + services. +- Startup, frame/update, queue/thread, and recording glue are split into named + helpers instead of living inline. +- `App` keeps ownership of composition state only where it truly has to. +- The file becomes materially thinner in the same slice. + +Mini-model packet: +- Keep thread behavior unchanged. +- Split by responsibility boundaries already present in `pp_app_core`. +- Prefer moving live ownership out over creating new planner wrappers. + +#### ARC-APP-004 - Move Render/UI Queues Into An Owned App Runtime Service + +Status: Ready + +Why now: +`App` still owns static render/UI queues, mutexes, condition variables, and +thread ids. That makes thread safety hard to reason about and keeps platform +entrypoints coupled to the singleton. + +Write scope: +- `src/app.h` +- `src/app.cpp` +- `src/app_events.cpp` +- `src/main.cpp` +- new `src/app_runtime_*.*` or retained runtime service files if needed + +Read scope: +- `src/app_core/app_thread.h` +- `src/platform_api/platform_services.h` +- render/UI task call sites under `src/*.cpp` + +Done when: +- Render and UI task queues are owned by an explicit runtime object or service + with startup, drain, stop, and thread-affinity APIs. +- `App` composes that runtime instead of exposing static global queue state. +- Platform event code and retained services post work through the runtime + contract rather than by reaching `App::I` static queues. +- Shutdown behavior remains deterministic and the touched slice reduces + singleton/thread coupling. + +Mini-model packet: +- Keep public behavior and thread ordering unchanged. +- Prefer a small runtime owner over broad task-system redesign. +- Make ownership and shutdown semantics explicit before adding new features. + +#### ARC-APP-005 - Replace Detached App Workers With Joinable Or Service-Owned Work + +Status: Ready + +Why now: +Canvas imports/exports/saves, cloud transfer, brush import/export, grid +lightmap work, stroke preview, and event persistence still launch detached +threads. That is not a safe modernization foundation. + +Write scope: +- `src/canvas.cpp` +- `src/app_cloud.cpp` +- `src/app_events.cpp` +- `src/legacy_cloud_services.*` +- `src/legacy_brush_package_import_services.*` +- `src/legacy_brush_package_export_services.*` +- `src/legacy_grid_ui_services.*` +- `src/node_dialog_cloud.*` +- `src/node_stroke_preview.*` + +Read scope: +- `src/app_core/app_thread.h` +- `src/foundation/task_queue.*` +- `src/legacy_recording_services.*` + +Done when: +- Touched worker families are owned by joinable `std::jthread`, a scoped worker + object, or an injected task service with cancellation/shutdown semantics. +- Worker callbacks do not capture raw retained nodes or `this` across unknown + lifetime without a checked handle, weak ownership, or explicit owner. +- App shutdown can stop the touched worker family without racing UI/layout or + renderer destruction. +- Detached `std::thread` count drops materially in app-facing code. + +Mini-model packet: +- Start with one coherent worker family, such as cloud or brush package import. +- Do not rewrite all threading at once. +- Preserve the existing UI/progress behavior while changing ownership. + +### Bundle 3 - Finish The UI Core Split + +Priority: `P0` + +Why this bundle is still top priority: +Until generic `Node` and control code leaves `pp_legacy_ui_core`, the UI +architecture remains mostly the old one with a modern overlay/lifetime helper +attached to it. + +#### ARC-UI-001 - Move Generic Node And Control Code Out Of `pp_legacy_ui_core` + +Status: Ready + +Why now: +`pp_ui_core` has layout, color, node lifetime, and overlay lifetime, but the +generic widget layer still sits in `pp_legacy_ui_core`. + +Write scope: +- `src/node.cpp` +- `src/layout.cpp` +- generic `src/node_*` base control files from `PP_LEGACY_UI_CORE_SOURCES` +- `src/ui_core/*` +- `CMakeLists.txt` +- `cmake/PanoPainterSources.cmake` + +Read scope: +- `src/node_panel_*` +- `src/node_dialog_*` + +Done when: +- Generic controls and base node/layout code are clearly separated from + PanoPainter-specific panels and dialogs. +- `pp_ui_core` grows as the home of generic widgets and node behavior. +- `pp_panopainter_ui` keeps only app-specific panels, dialogs, canvas, preview, + and workflow nodes. +- The touched slice removes real file ownership from `pp_legacy_ui_core`, not + just adds wrappers around it. + +Mini-model packet: +- Start with the controls that have no app-specific policy: + button, checkbox, icon, image, scroll, slider, text, text input. +- Do not mix panel/dialog rewrites into the same slice. +- Prefer target ownership moves over purely internal helper reshuffles. + +#### ARC-UI-002 - Make Checked Handles The Default UI Ownership Model + +Status: Ready + +Why now: +`pp_ui_core` has lifetime and overlay handle helpers, but retained UI code still +mixes raw `Node*`, shared ownership, direct `add_child(...)`, and callback +captures across mutation points. + +Write scope: +- `src/node.*` +- `src/layout.*` +- `src/legacy_ui_overlay_services.*` +- retained `src/node_dialog_*` and `src/node_panel_*` files touched by a slice +- `src/ui_core/node_lifetime.*` +- `src/ui_core/overlay_lifetime.*` + +Read scope: +- existing popup/dialog call sites found with `add_child`, `remove_child`, and + `on_*` callback captures + +Done when: +- New or touched UI surfaces open, close, and dispatch callbacks through checked + handles or scoped connections by default. +- Raw `Node*` fields and callback parameters are documented or reshaped as + non-owning views, not lifetime owners. +- Destroy-during-callback and close-during-dispatch behavior is owned by + `pp_ui_core` rather than each panel/dialog. +- App-specific panels become view/controller shells over safe UI-core + lifetime primitives. + +Mini-model packet: +- Convert one popup/dialog family at a time. +- Do not redesign the UI appearance in this task. +- Prefer deleting raw lifetime assumptions over adding more guard comments. + +#### ARC-UI-003 - Split UI Rendering From Scene Graph And App State + +Status: Ready + +Why now: +The generic node layer still mixes layout, input, rendering, direct app access, +and retained OpenGL resource usage. That blocks both a cleaner UI component and +future renderer backends. + +Write scope: +- `src/node.cpp` +- `src/node_canvas.cpp` +- `src/node_stroke_preview.cpp` +- generic control files moved toward `src/ui_core/*` +- `src/renderer_api/*` +- `src/paint_renderer/*` + +Read scope: +- `src/font.*` +- `src/shape.*` +- `src/texture.*` +- `src/rtt.*` + +Done when: +- Scene graph/layout/input code has a renderer-neutral draw contract. +- Generic controls do not need to know app singleton state or concrete GL + resource classes. +- App-specific canvas and preview rendering depends on renderer-facing services + rather than base `Node` internals. +- The touched slice makes `pp_ui_core` more reusable without hiding app policy + inside it. + +Mini-model packet: +- Keep visual behavior unchanged. +- Do not move PanoPainter-specific panel policy into `pp_ui_core`. +- Use renderer-neutral contracts first; backend implementation follows later. + +### Bundle 4 - Make The Platform Boundary Real + +Priority: `P1` + +Why this bundle is not `P0`: +It matters, but moving platform code first will not change the day-to-day shape +of the app as much as reducing `canvas.cpp`, the app shell, and the generic UI +layer. + +#### ARC-PLT-001 - Split `pp_platform_api` From Concrete Platform Code + +Status: Ready + +Why now: +`pp_platform_api` is supposed to be the SDK-free policy and interface layer, +but it still compiles `src/platform_apple/apple_platform_services.*`. + +Write scope: +- `CMakeLists.txt` +- `src/platform_api/*` +- `src/platform_apple/*` + +Read scope: +- `cmake/PanoPainterSources.cmake` +- `src/platform_windows/*` +- `src/platform_linux/*` + +Done when: +- `pp_platform_api` contains only platform-neutral interfaces, policies, and + shared helpers. +- Apple implementation files are built by a concrete platform target instead of + the API target. +- The dependency direction is obvious from CMake without reading debt notes. + +Mini-model packet: +- Start in `CMakeLists.txt` around `pp_platform_api`. +- Keep the change structural; do not broaden into new feature work. +- Preserve current Apple service entrypoints while moving ownership. + +#### ARC-PLT-002 - Remove `App::I` Reach From Apple And Linux Services + +Status: Ready + +Why now: +The current Apple and Linux service files still call into the app singleton, +which means the platform layer is not a platform layer yet. + +Write scope: +- `src/platform_apple/*` +- `src/platform_linux/*` +- `src/app_events.cpp` +- `src/app.h` + +Read scope: +- `src/platform_api/platform_services.h` +- `src/platform_legacy/legacy_platform_services.*` + +Done when: +- `src/platform_apple/*` and `src/platform_linux/*` no longer call `App::I`. +- The app injects the minimum callbacks or bridge state those services need. +- Platform files stop depending on app-global state for clipboard, sharing, FPS + title updates, or native UI saves. + +Mini-model packet: +- Keep the interface small. Prefer injected callbacks/bridges over passing the + whole `App`. +- Do not rewrite Windows in the same slice. + +#### ARC-PLT-003 - Remove App-Owned Cross-Platform Handle Storage + +Status: Ready + +Why now: +`src/platform_legacy/legacy_platform_services.cpp` and `src/app.h` still keep +platform-handle state on `App`, which blocks a real `pp_platform_*` shell split. + +Write scope: +- `src/platform_legacy/legacy_platform_services.*` +- `src/app.h` +- `src/app_events.cpp` +- `src/platform_windows/*` + +Read scope: +- `src/main.cpp` +- Apple/Android/Web/Linux entrypoint files only as needed + +Done when: +- `App` no longer owns platform-specific handle fields that belong to shells. +- The legacy platform adapter becomes thin composition or disappears for the + touched path. +- Platform setup state lives with the relevant `pp_platform_*` implementation. + +Mini-model packet: +- Keep this slice about state ownership, not feature behavior. +- Prefer moving state to shell-local structs or service singletons owned by the + platform target. + +### Bundle 5 - Retire The Thick Workflow Bridges -Goal: +Priority: `P2` + +Why this bundle is later: +These bridges still matter, but many recent slices spent too much effort +polishing adapters without changing the bulk shape of the live app. This bundle +stays active only after the main hotspots are moving. + +#### ARC-WKF-001 - Thin Document Open/Save/Session Bridges To Pure Adapters + +Status: Ready + +Why now: +The document session/open/save planners exist, but the live bridges still own a +lot of retained dialog, metadata, title, and save execution behavior. + +Write scope: +- `src/legacy_document_open_services.*` +- `src/legacy_document_session_services.*` +- `src/legacy_document_export_services.*` +- `src/legacy_history_services.*` + +Read scope: +- `src/app_core/document_route.h` +- `src/app_core/document_session.h` +- `src/app_core/document_export.h` + +Done when: +- The remaining bridge files are thin adapters from planner outputs to retained + execution. +- Save/open/session flows stop mutating app/document/UI state inline across + multiple bridge layers. +- Title updates, history clearing, overwrite prompts, and save routing are each + owned in one obvious place. + +Mini-model packet: +- Preserve current planner contracts. +- Favor one adapter per workflow family over catch-all helper growth. + +#### ARC-WKF-002 - Split Cloud And Brush Package Work Out Of Retained UI Nodes -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` recorded 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` recorded 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` recorded 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: - -- The first `desktop-gpu` golden/readback gate was added and validated. -- The gate was skipped with a clear message when no GPU/context was available. -- The roadmap and debt log recorded the golden coverage and the remaining debt. - -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: - -- The preview parity lane gained a second `desktop-gpu` GPU readback gate. -- The gate was skipped with a clear message when no GPU/context was available. -- The roadmap and debt log recorded the GPU readback coverage and the remaining - debt. - -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: - -- The compositor path gained a `desktop-gpu` readback gate alongside the - existing GPU fixtures. -- The gate was skipped with a clear message when no GPU/context was available. -- The roadmap and debt log recorded the compositor output coverage for this - gate. - -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 depended on the unpublished fmt submodule pointer pre-DEP-001 | 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: - -Historical tracker normalization was completed without changing code: duplicate -`STR-010`, duplicate/ambiguous `STR-016`, and duplicate `STR-030` were -documented, the completed-task table shape was normalized, and stale `pending` -rows archived. - -Done Checks: - -- Each historical task id has one active section or one 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|completed-task table shape" 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: 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` -Completed: 2026-06-13 (`b9ed78e1`) - -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`
`& .\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` | +Status: Ready + +Why now: +Cloud browse/download/upload and brush package import/export still close over +retained nodes, worker threads, and direct UI ownership. + +Write scope: +- `src/legacy_cloud_services.*` +- `src/node_dialog_cloud.*` +- `src/legacy_brush_package_import_services.*` +- `src/legacy_brush_package_export_services.*` +- `src/node_panel_brush.cpp` + +Read scope: +- `src/app_core/document_cloud.h` +- `src/app_core/brush_package_import.h` +- `src/app_core/brush_package_export.h` +- `src/assets/brush_package.*` + +Done when: +- Network transfer execution, thumbnail loading, and brush package worker + ownership are isolated behind named services. +- Retained nodes become view/controller shells instead of workflow owners. +- Cloud and brush package code no longer need to be understood through panel or + dialog internals first. + +Mini-model packet: +- Split worker ownership from UI ownership first. +- Do not try to redesign cloud UX or brush preset UX in the same slice. + +## Deferred On Purpose + +- Vulkan and Metal lab work +- package-only and automation-only cleanup +- scorekeeping tasks that do not move app architecture + +These remain in history only until the app shell, platform split, UI split, and +canvas/render split are materially thinner. diff --git a/src/app.cpp b/src/app.cpp index eebcb0f2..1e970407 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -32,10 +32,6 @@ App* App::I = nullptr; // singleton -std::deque App::render_tasklist; -std::mutex App::render_task_mutex; -std::condition_variable App::render_cv; - namespace { pp::app::CanvasToolMode canvas_tool_mode_from_canvas_mode(kCanvasMode mode) noexcept @@ -130,17 +126,6 @@ void apply_app_scissor_test(bool enabled) } } -std::thread App::render_thread; -std::thread::id App::render_thread_id; -bool App::render_running = false; - -std::deque App::ui_tasklist; -std::mutex App::ui_task_mutex; -std::condition_variable App::ui_cv; -std::thread App::ui_thread; -std::thread::id App::ui_thread_id; -bool App::ui_running = false; - void App::create() { const auto initial_surface = pp::app::plan_app_initial_surface(); @@ -353,7 +338,7 @@ void App::async_redraw() if (plan.set_redraw) redraw = true; if (plan.notify_ui) - ui_cv.notify_all(); + runtime_.notify_ui_worker(); } void App::async_end() @@ -699,252 +684,40 @@ void App::rec_loop() void App::render_thread_tick() { - static uint32_t count = 0; - render_thread_id = std::this_thread::get_id(); - std::deque working_list; - pp::app::AppQueueDrainPlan drain_plan; - - // move the task list locally to free the queue for other threads - { - std::unique_lock lock(render_task_mutex); - drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size()); - render_running = drain_plan.mark_running; - if (!drain_plan.drain_tasks) - return; - working_list = std::move(render_tasklist); - } - - // execute the tasks - if (drain_plan.wrap_in_render_context) - { - async_start(); - while (!working_list.empty()) - { - //LOG("render task %d", count); - //LOG("task %s", working_list.front().name.c_str()); - count++; - working_list.front()(); - working_list.pop_front(); - } - async_end(); - } + runtime_.render_thread_tick(*this); } void App::render_thread_main() { - BT_SetTerminate(); - - uint32_t count = 0; - render_thread_id = std::this_thread::get_id(); - render_running = pp::app::plan_app_thread_start().mark_running; - while (render_running) - { - std::deque working_list; - pp::app::AppQueueDrainPlan drain_plan; - - // move the task list locally to free the queue for other threads - { - std::unique_lock lock(render_task_mutex); - render_cv.wait(lock, [this] { return render_tasklist.empty() && render_running ? false : true; }); - drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist.size()); - working_list = std::move(render_tasklist); - } - - // execute the tasks - if (drain_plan.wrap_in_render_context) - { - async_start(); - while (!working_list.empty()) - { - //LOG("render task %d", count); - //LOG("task %s", working_list.front().name.c_str()); - count++; - working_list.front()(); - working_list.pop_front(); - } - async_end(); - } - } + runtime_.render_thread_main(*this); } void App::ui_thread_tick() { - ui_thread_id = std::this_thread::get_id(); - - std::deque working_list; - pp::app::AppUiTickPlan tick_plan; - - // move the task list locally to free the queue for other threads - { - std::unique_lock lock(ui_task_mutex); - tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist.size(), redraw); - ui_running = tick_plan.mark_running; - working_list = std::move(ui_tasklist); - } - - // execute the tasks - if (tick_plan.execute_tasks) - { - while (!working_list.empty()) - { - //LOG("ui task %d", count); - working_list.front()(); - working_list.pop_front(); - } - } - - if (tick_plan.tick_app) - tick(0); - - const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, 0); - if (redraw_plan.enqueue_render_frame) - { - if (redraw_plan.update_before_render) - update(0); - render_task([this] - { - bind_default_render_target(); - clear(); - draw(0); - async_swap(); - }); - } + runtime_.ui_thread_tick(*this); } void App::ui_thread_main() { - BT_SetTerminate(); - - uint32_t count = 0; - ui_thread_id = std::this_thread::get_id(); - ui_running = pp::app::plan_app_thread_start().mark_running; - - attach_ui_thread(); - - LOG("ui thread init()"); - init(); - - auto t_start = std::chrono::high_resolution_clock::now(); - float t_frame = 0; - float t_fps_counter = 0; - float t_reloader = 0; - int rendered_frames = 0; - while (ui_running) - { - std::deque working_list; - - // move the task list locally to free the queue for other threads - { - std::unique_lock lock(ui_task_mutex); - ui_cv.wait_for(lock, std::chrono::milliseconds(idle_ms), - [this] { return ui_tasklist.empty() && ui_running ? false : true; }); - working_list = std::move(ui_tasklist); - } - - // execute the tasks - if (!working_list.empty()) - { - while (!working_list.empty()) - { - //LOG("ui task %d", count); - count++; - working_list.front()(); - working_list.pop_front(); - } - } - - auto t_now = std::chrono::high_resolution_clock::now(); - float dt = std::chrono::duration(t_now - t_start).count(); - t_start = t_now; - - const auto timer_plan = pp::app::plan_app_ui_loop_timers( - dt, - t_frame, - t_fps_counter, - t_reloader, - rendered_frames, - platform_enables_live_asset_reloading()); - if (timer_plan) { - if (timer_plan.value().update_platform_frame) - update_platform_frame(dt); - t_frame = timer_plan.value().frame_accumulator; - t_fps_counter = timer_plan.value().fps_accumulator; - t_reloader = timer_plan.value().reload_accumulator; - rendered_frames = timer_plan.value().rendered_frames_after_report; - - if (timer_plan.value().report_rendered_frames) - report_rendered_frames(timer_plan.value().reported_frame_count); - - if (timer_plan.value().check_live_asset_reload) { - if (ShaderManager::reload()) - { - stroke->update_controls(); - redraw = true; - } - if (layout.reload()) - redraw = true; - if (layout_designer.reload()) - redraw = true; - } - } - - const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(redraw, rendered_frames); - if (redraw_plan.tick_app) - tick(dt); - - if (redraw_plan.enqueue_render_frame) - { - if (redraw_plan.update_before_render) - update(t_frame); - render_task([this, t_frame] - { - bind_default_render_target(); - clear(); - draw(t_frame); - async_swap(); - }); - if (redraw_plan.reset_frame_accumulator) - t_frame = 0; - rendered_frames = redraw_plan.rendered_frames; - } - } - detach_ui_thread(); + runtime_.ui_thread_main(*this); } void App::render_thread_start() { - const auto plan = pp::app::plan_app_thread_start(); - if (plan.start_thread) - render_thread = std::thread(&App::render_thread_main, this); - render_running = plan.mark_running; + runtime_.render_thread_start(*this); } void App::render_thread_stop() { - const auto plan = pp::app::plan_app_thread_stop(render_thread.joinable()); - if (plan.mark_not_running) - render_running = false; - if (plan.notify_worker) - render_cv.notify_all(); - if (plan.join_thread) - render_thread.join(); + runtime_.render_thread_stop(); } void App::ui_thread_start() { - const auto plan = pp::app::plan_app_thread_start(); - if (plan.start_thread) - ui_thread = std::thread(&App::ui_thread_main, this); - ui_running = plan.mark_running; + runtime_.ui_thread_start(*this); } void App::ui_thread_stop() { - const auto plan = pp::app::plan_app_thread_stop(ui_thread.joinable()); - if (plan.mark_not_running) - ui_running = false; - if (plan.notify_worker) - ui_cv.notify_all(); - if (plan.join_thread) - ui_thread.join(); + runtime_.ui_thread_stop(); } diff --git a/src/app.h b/src/app.h index 5a196573..2ce924e8 100644 --- a/src/app.h +++ b/src/app.h @@ -25,6 +25,7 @@ #include "layout.h" #include "app_core/document_session.h" #include "app_core/app_thread.h" +#include "app_runtime.h" namespace pp::platform { class PlatformServices; @@ -76,21 +77,6 @@ struct VRController virtual float get_trigger_value() const { return 1.f; } }; -struct AppTask : public std::packaged_task -{ - size_t task_id; -#ifdef _DEBUG - std::string name; -#endif - template AppTask(F f) : std::packaged_task(f) - { - task_id = typeid(f).hash_code(); -#ifdef _DEBUG - name = typeid(f).name(); -#endif - } -}; - class App { public: @@ -345,16 +331,13 @@ public: void cmd_convert(std::string pano_path, std::string out_path); + AppRuntime& runtime() noexcept { return runtime_; } + const AppRuntime& runtime() const noexcept { return runtime_; } + ////////////////////////////////////////////////////////////////////////// // RENDER THREAD ////////////////////////////////////////////////////////////////////////// - static std::deque render_tasklist; - static std::mutex render_task_mutex; - static std::condition_variable render_cv; - static std::thread render_thread; - static std::thread::id render_thread_id; - static bool render_running; void render_thread_tick(); void render_thread_main(); void render_thread_start(); @@ -362,94 +345,30 @@ public: bool is_render_thread() { - return std::this_thread::get_id() == render_thread_id; + return runtime_.is_render_thread(); } - // don't capture a reference to this ptr as the object may be destroyed - // by the time the task is executed template std::future render_task_async(T task, bool unique = false) { - AppTask pt(task); - auto f = pt.get_future(); - const auto dispatch = pp::app::plan_app_task_dispatch( - is_render_thread(), - unique, - 0U, - render_running, - false, - false); - if (dispatch.execute_immediately) - { - pt(); - } - else if (dispatch.queue_task) - { - { - std::lock_guard lock(render_task_mutex); - const auto queue_dispatch = pp::app::plan_app_task_dispatch( - false, - unique, - render_tasklist.size(), - render_running, - false, - false); - // remove any previously queued task from the same lambda - if (queue_dispatch.remove_matching_unique_task) - render_tasklist.erase(std::remove_if(render_tasklist.begin(), render_tasklist.end(), - [id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist.end()); - render_tasklist.push_back(std::move(pt)); - } - if (dispatch.notify_worker) - render_cv.notify_all(); - } - return f; + return runtime_.render_task_async(std::move(task), unique); } template void render_task(T task) { - AppTask pt(task); - auto f = pt.get_future(); - const auto dispatch = pp::app::plan_app_task_dispatch( - is_render_thread(), - false, - 0U, - render_running, - true, - false); - if (dispatch.execute_immediately) - { - pt(); - } - else if (dispatch.queue_task) - { - { - std::lock_guard lock(render_task_mutex); - render_tasklist.push_back(std::move(pt)); - } - if (dispatch.notify_worker) - render_cv.notify_all(); - } - if (dispatch.wait_for_completion) - f.get(); + runtime_.render_task(std::move(task)); } void render_sync() { - render_task([] {}); + runtime_.render_sync(); } ////////////////////////////////////////////////////////////////////////// // UI THREAD ////////////////////////////////////////////////////////////////////////// - static std::deque ui_tasklist; - static std::mutex ui_task_mutex; - static std::condition_variable ui_cv; - static std::thread ui_thread; - static std::thread::id ui_thread_id; - static bool ui_running; void ui_thread_tick(); void ui_thread_main(); void ui_thread_start(); @@ -457,83 +376,29 @@ public: bool is_ui_thread() { - return std::this_thread::get_id() == ui_thread_id; + return runtime_.is_ui_thread(); } - // don't capture a reference to this ptr as the object may be destroyed - // by the time the task is executed template std::future ui_task_async(T task, bool unique = false) { - AppTask pt(task); - auto f = pt.get_future(); - const auto dispatch = pp::app::plan_app_task_dispatch( - is_ui_thread(), - unique, - 0U, - ui_running, - false, - false); - if (dispatch.execute_immediately) - { - pt(); - } - else if (dispatch.queue_task) - { - { - std::lock_guard lock(ui_task_mutex); - const auto queue_dispatch = pp::app::plan_app_task_dispatch( - false, - unique, - ui_tasklist.size(), - ui_running, - false, - false); - // remove any previously queued task from the same lambda - if (queue_dispatch.remove_matching_unique_task) - ui_tasklist.erase(std::remove_if(ui_tasklist.begin(), ui_tasklist.end(), - [id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist.end()); - ui_tasklist.push_back(std::move(pt)); - } - if (dispatch.notify_worker) - ui_cv.notify_all(); - } - return f; + return runtime_.ui_task_async(std::move(task), unique); } template void ui_task(T task) { - AppTask pt(task); - auto f = pt.get_future(); - const auto dispatch = pp::app::plan_app_task_dispatch( - is_ui_thread(), - false, - 0U, - ui_running, - true, - true); - if (dispatch.execute_immediately) - { - pt(); - } - else if (dispatch.queue_task) - { - { - std::lock_guard lock(ui_task_mutex); - ui_tasklist.push_back(std::move(pt)); - } - if (dispatch.notify_worker) - ui_cv.notify_all(); - } - if (dispatch.wait_for_completion) - f.get(); - if (dispatch.request_redraw) + runtime_.ui_task(std::move(task)); + if (runtime_.request_redraw()) redraw = true; + runtime_.clear_request_redraw(); } void ui_sync() { - ui_task([] {}); + runtime_.ui_sync(); } + +private: + AppRuntime runtime_; }; diff --git a/src/app_cloud.cpp b/src/app_cloud.cpp index 8b4d7e6f..17996c72 100644 --- a/src/app_cloud.cpp +++ b/src/app_cloud.cpp @@ -19,7 +19,7 @@ void App::cloud_upload() void App::cloud_upload_all() { - std::thread([this] { + pp::panopainter::queue_legacy_cloud_worker_task([this] { BT_SetTerminate(); auto names = Asset::list_files(data_path, ".*\\.ppi"); @@ -28,7 +28,7 @@ void App::cloud_upload_all() const auto status = pp::panopainter::execute_legacy_cloud_bulk_upload_plan(*this, plan); if (!status.ok()) LOG("Cloud bulk upload action failed: %s", status.message); - }).detach(); + }); } void App::cloud_browse() diff --git a/src/app_runtime.cpp b/src/app_runtime.cpp new file mode 100644 index 00000000..f9addee8 --- /dev/null +++ b/src/app_runtime.cpp @@ -0,0 +1,258 @@ +#include "pch.h" +#include "app_runtime.h" + +#include "app.h" + +bool AppRuntime::is_render_thread() const noexcept +{ + return std::this_thread::get_id() == render_thread_id_; +} + +bool AppRuntime::is_ui_thread() const noexcept +{ + return std::this_thread::get_id() == ui_thread_id_; +} + +void AppRuntime::notify_render_worker() noexcept +{ + render_cv_.notify_all(); +} + +void AppRuntime::notify_ui_worker() noexcept +{ + ui_cv_.notify_all(); +} + +void AppRuntime::render_thread_tick(App& app) +{ + static uint32_t count = 0; + render_thread_id_ = std::this_thread::get_id(); + std::deque working_list; + pp::app::AppQueueDrainPlan drain_plan; + + { + std::unique_lock lock(render_task_mutex_); + drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size()); + render_running_ = drain_plan.mark_running; + if (!drain_plan.drain_tasks) + return; + working_list = std::move(render_tasklist_); + } + + if (drain_plan.wrap_in_render_context) + { + app.async_start(); + while (!working_list.empty()) + { + count++; + working_list.front()(); + working_list.pop_front(); + } + app.async_end(); + } +} + +void AppRuntime::render_thread_main(App& app) +{ + BT_SetTerminate(); + + render_thread_id_ = std::this_thread::get_id(); + render_running_ = pp::app::plan_app_thread_start().mark_running; + while (render_running_) + { + std::deque working_list; + pp::app::AppQueueDrainPlan drain_plan; + + { + std::unique_lock lock(render_task_mutex_); + render_cv_.wait(lock, [this] { return render_tasklist_.empty() && render_running_ ? false : true; }); + drain_plan = pp::app::plan_app_render_queue_drain(render_tasklist_.size()); + working_list = std::move(render_tasklist_); + } + + if (drain_plan.wrap_in_render_context) + { + app.async_start(); + while (!working_list.empty()) + { + working_list.front()(); + working_list.pop_front(); + } + app.async_end(); + } + } +} + +void AppRuntime::ui_thread_tick(App& app) +{ + ui_thread_id_ = std::this_thread::get_id(); + + std::deque working_list; + pp::app::AppUiTickPlan tick_plan; + + { + std::unique_lock lock(ui_task_mutex_); + tick_plan = pp::app::plan_app_ui_thread_tick(ui_tasklist_.size(), app.redraw); + ui_running_ = tick_plan.mark_running; + working_list = std::move(ui_tasklist_); + } + + if (tick_plan.execute_tasks) + { + while (!working_list.empty()) + { + working_list.front()(); + working_list.pop_front(); + } + } + + if (tick_plan.tick_app) + app.tick(0); + + const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, 0); + if (redraw_plan.enqueue_render_frame) + { + if (redraw_plan.update_before_render) + app.update(0); + app.render_task([&app] + { + app.bind_default_render_target(); + app.clear(); + app.draw(0); + app.async_swap(); + }); + } +} + +void AppRuntime::ui_thread_main(App& app) +{ + BT_SetTerminate(); + + ui_thread_id_ = std::this_thread::get_id(); + ui_running_ = pp::app::plan_app_thread_start().mark_running; + + app.attach_ui_thread(); + + LOG("ui thread init()"); + app.init(); + + auto t_start = std::chrono::high_resolution_clock::now(); + float t_frame = 0; + float t_fps_counter = 0; + float t_reloader = 0; + int rendered_frames = 0; + while (ui_running_) + { + std::deque working_list; + + { + std::unique_lock lock(ui_task_mutex_); + ui_cv_.wait_for(lock, std::chrono::milliseconds(app.idle_ms), + [this] { return ui_tasklist_.empty() && ui_running_ ? false : true; }); + working_list = std::move(ui_tasklist_); + } + + if (!working_list.empty()) + { + while (!working_list.empty()) + { + working_list.front()(); + working_list.pop_front(); + } + } + + auto t_now = std::chrono::high_resolution_clock::now(); + float dt = std::chrono::duration(t_now - t_start).count(); + t_start = t_now; + + const auto timer_plan = pp::app::plan_app_ui_loop_timers( + dt, + t_frame, + t_fps_counter, + t_reloader, + rendered_frames, + app.platform_enables_live_asset_reloading()); + if (timer_plan) { + if (timer_plan.value().update_platform_frame) + app.update_platform_frame(dt); + t_frame = timer_plan.value().frame_accumulator; + t_fps_counter = timer_plan.value().fps_accumulator; + t_reloader = timer_plan.value().reload_accumulator; + rendered_frames = timer_plan.value().rendered_frames_after_report; + + if (timer_plan.value().report_rendered_frames) + app.report_rendered_frames(timer_plan.value().reported_frame_count); + + if (timer_plan.value().check_live_asset_reload) { + if (ShaderManager::reload()) + { + app.stroke->update_controls(); + app.redraw = true; + } + if (app.layout.reload()) + app.redraw = true; + if (app.layout_designer.reload()) + app.redraw = true; + } + } + + const auto redraw_plan = pp::app::plan_app_ui_loop_redraw(app.redraw, rendered_frames); + if (redraw_plan.tick_app) + app.tick(dt); + + if (redraw_plan.enqueue_render_frame) + { + if (redraw_plan.update_before_render) + app.update(t_frame); + app.render_task([&app, t_frame] + { + app.bind_default_render_target(); + app.clear(); + app.draw(t_frame); + app.async_swap(); + }); + if (redraw_plan.reset_frame_accumulator) + t_frame = 0; + rendered_frames = redraw_plan.rendered_frames; + } + } + app.detach_ui_thread(); +} + +void AppRuntime::render_thread_start(App& app) +{ + const auto plan = pp::app::plan_app_thread_start(); + if (plan.start_thread) + render_thread_ = std::thread(&AppRuntime::render_thread_main, this, std::ref(app)); + render_running_ = plan.mark_running; +} + +void AppRuntime::render_thread_stop() +{ + const auto plan = pp::app::plan_app_thread_stop(render_thread_.joinable()); + if (plan.mark_not_running) + render_running_ = false; + if (plan.notify_worker) + render_cv_.notify_all(); + if (plan.join_thread) + render_thread_.join(); +} + +void AppRuntime::ui_thread_start(App& app) +{ + const auto plan = pp::app::plan_app_thread_start(); + if (plan.start_thread) + ui_thread_ = std::thread(&AppRuntime::ui_thread_main, this, std::ref(app)); + ui_running_ = plan.mark_running; +} + +void AppRuntime::ui_thread_stop() +{ + const auto plan = pp::app::plan_app_thread_stop(ui_thread_.joinable()); + if (plan.mark_not_running) + ui_running_ = false; + if (plan.notify_worker) + ui_cv_.notify_all(); + if (plan.join_thread) + ui_thread_.join(); +} diff --git a/src/app_runtime.h b/src/app_runtime.h new file mode 100644 index 00000000..b066d112 --- /dev/null +++ b/src/app_runtime.h @@ -0,0 +1,216 @@ +#pragma once + +#include "app_core/app_thread.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class App; + +struct AppTask : public std::packaged_task +{ + size_t task_id; +#ifdef _DEBUG + std::string name; +#endif + template AppTask(F f) : std::packaged_task(f) + { + task_id = typeid(f).hash_code(); +#ifdef _DEBUG + name = typeid(f).name(); +#endif + } +}; + +class AppRuntime +{ +public: + [[nodiscard]] bool is_render_thread() const noexcept; + [[nodiscard]] bool is_ui_thread() const noexcept; + [[nodiscard]] bool request_redraw() const noexcept { return request_redraw_; } + void clear_request_redraw() noexcept { request_redraw_ = false; } + + void notify_render_worker() noexcept; + void notify_ui_worker() noexcept; + + void render_thread_tick(App& app); + void render_thread_main(App& app); + void render_thread_start(App& app); + void render_thread_stop(); + + void ui_thread_tick(App& app); + void ui_thread_main(App& app); + void ui_thread_start(App& app); + void ui_thread_stop(); + + template + std::future render_task_async(T task, bool unique = false) + { + AppTask pt(task); + auto f = pt.get_future(); + const auto dispatch = pp::app::plan_app_task_dispatch( + is_render_thread(), + unique, + 0U, + render_running_, + false, + false); + if (dispatch.execute_immediately) + { + pt(); + } + else if (dispatch.queue_task) + { + { + std::lock_guard lock(render_task_mutex_); + const auto queue_dispatch = pp::app::plan_app_task_dispatch( + false, + unique, + render_tasklist_.size(), + render_running_, + false, + false); + if (queue_dispatch.remove_matching_unique_task) + render_tasklist_.erase(std::remove_if(render_tasklist_.begin(), render_tasklist_.end(), + [id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist_.end()); + render_tasklist_.push_back(std::move(pt)); + } + if (dispatch.notify_worker) + render_cv_.notify_all(); + } + return f; + } + + template + void render_task(T task) + { + AppTask pt(task); + auto f = pt.get_future(); + const auto dispatch = pp::app::plan_app_task_dispatch( + is_render_thread(), + false, + 0U, + render_running_, + true, + false); + if (dispatch.execute_immediately) + { + pt(); + } + else if (dispatch.queue_task) + { + { + std::lock_guard lock(render_task_mutex_); + render_tasklist_.push_back(std::move(pt)); + } + if (dispatch.notify_worker) + render_cv_.notify_all(); + } + if (dispatch.wait_for_completion) + f.get(); + } + + void render_sync() + { + render_task([] {}); + } + + template + std::future ui_task_async(T task, bool unique = false) + { + AppTask pt(task); + auto f = pt.get_future(); + const auto dispatch = pp::app::plan_app_task_dispatch( + is_ui_thread(), + unique, + 0U, + ui_running_, + false, + false); + if (dispatch.execute_immediately) + { + pt(); + } + else if (dispatch.queue_task) + { + { + std::lock_guard lock(ui_task_mutex_); + const auto queue_dispatch = pp::app::plan_app_task_dispatch( + false, + unique, + ui_tasklist_.size(), + ui_running_, + false, + false); + if (queue_dispatch.remove_matching_unique_task) + ui_tasklist_.erase(std::remove_if(ui_tasklist_.begin(), ui_tasklist_.end(), + [id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist_.end()); + ui_tasklist_.push_back(std::move(pt)); + } + if (dispatch.notify_worker) + ui_cv_.notify_all(); + } + return f; + } + + template + void ui_task(T task) + { + AppTask pt(task); + auto f = pt.get_future(); + const auto dispatch = pp::app::plan_app_task_dispatch( + is_ui_thread(), + false, + 0U, + ui_running_, + true, + true); + if (dispatch.execute_immediately) + { + pt(); + } + else if (dispatch.queue_task) + { + { + std::lock_guard lock(ui_task_mutex_); + ui_tasklist_.push_back(std::move(pt)); + } + if (dispatch.notify_worker) + ui_cv_.notify_all(); + } + if (dispatch.wait_for_completion) + f.get(); + if (dispatch.request_redraw) + request_redraw_ = true; + } + + void ui_sync() + { + ui_task([] {}); + } + +private: + std::deque render_tasklist_; + std::mutex render_task_mutex_; + std::condition_variable render_cv_; + std::thread render_thread_; + std::thread::id render_thread_id_; + bool render_running_ = false; + + std::deque ui_tasklist_; + std::mutex ui_task_mutex_; + std::condition_variable ui_cv_; + std::thread ui_thread_; + std::thread::id ui_thread_id_; + bool ui_running_ = false; + bool request_redraw_ = false; +}; diff --git a/src/legacy_cloud_services.cpp b/src/legacy_cloud_services.cpp index aae0d062..0c5cfe17 100644 --- a/src/legacy_cloud_services.cpp +++ b/src/legacy_cloud_services.cpp @@ -21,6 +21,79 @@ pp::foundation::Status execute_legacy_cloud_download_selection_action( namespace { +class LegacyCloudWorker final { +public: + LegacyCloudWorker() + : worker_([this](std::stop_token stop_token) { + run(stop_token); + }) + { + } + + ~LegacyCloudWorker() + { + shutdown(); + } + + void post(std::function task) + { + { + std::lock_guard lock(mutex_); + if (stopping_) + return; + tasks_.push_back(std::move(task)); + } + cv_.notify_one(); + } + +private: + void shutdown() + { + { + std::lock_guard lock(mutex_); + stopping_ = true; + } + cv_.notify_all(); + } + + void run(std::stop_token stop_token) + { + for (;;) { + std::function task; + { + std::unique_lock lock(mutex_); + cv_.wait(lock, [&] { + return stopping_ || stop_token.stop_requested() || !tasks_.empty(); + }); + if ((stopping_ || stop_token.stop_requested()) && tasks_.empty()) + break; + task = std::move(tasks_.front()); + tasks_.pop_front(); + } + + if (task) { + try { + task(); + } catch (...) { + LOG("cloud worker task failed"); + } + } + } + } + + std::mutex mutex_; + std::condition_variable cv_; + std::deque> tasks_; + bool stopping_ = false; + std::jthread worker_; +}; + +LegacyCloudWorker& cloud_worker() +{ + static LegacyCloudWorker worker; + return worker; +} + #if WITH_CURL int progress_callback_download(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) @@ -233,28 +306,12 @@ void execute_cloud_download_thread( execute_cloud_download_flow(app, request); } -void launch_cloud_download_thread( - App& app, - const pp::app::CloudDownloadRequest& request) -{ - std::thread([app = &app, request] { - execute_cloud_download_thread(*app, request); - }).detach(); -} - void execute_cloud_publish_worker(App& app, bool save_before_upload) { BT_SetTerminate(); execute_cloud_publish_transfer_and_success_prompt(app, save_before_upload); } -void launch_cloud_publish_thread(App& app, bool save_before_upload) -{ - std::thread([app = &app, save_before_upload] { - execute_cloud_publish_worker(*app, save_before_upload); - }).detach(); -} - void wire_cloud_publish_prompt_buttons( const std::shared_ptr& dialog, std::function upload_thread) @@ -273,7 +330,9 @@ void setup_cloud_publish_prompt(App& app, bool save_before_upload) const auto prompt_plan = pp::app::plan_cloud_publish_prompt(); auto dialog = app.message_box(prompt_plan.title, prompt_plan.message, prompt_plan.show_cancel); wire_cloud_publish_prompt_buttons(dialog, [&app, save_before_upload] { - launch_cloud_publish_thread(app, save_before_upload); + queue_legacy_cloud_worker_task([app = &app, save_before_upload] { + execute_cloud_publish_worker(*app, save_before_upload); + }); }); } @@ -379,7 +438,9 @@ public: void start_download(const pp::app::CloudDownloadRequest& request) override { - launch_cloud_download_thread(app_, request); + queue_legacy_cloud_worker_task([app = &app_, request] { + execute_cloud_download_thread(*app, request); + }); } private: @@ -395,6 +456,11 @@ void show_cloud_save_required_warning(App& app) } // namespace +void queue_legacy_cloud_worker_task(std::function task) +{ + cloud_worker().post(std::move(task)); +} + pp::foundation::Status execute_legacy_cloud_upload_plan( App& app, const pp::app::CloudUploadPlan& plan) diff --git a/src/legacy_cloud_services.h b/src/legacy_cloud_services.h index 89920202..e6d43043 100644 --- a/src/legacy_cloud_services.h +++ b/src/legacy_cloud_services.h @@ -3,6 +3,8 @@ #include "app_core/document_cloud.h" #include "foundation/result.h" +#include + class App; class NodeDialogCloud; @@ -22,4 +24,6 @@ namespace pp::panopainter { pp::app::CloudDownloadSelectionAction action, NodeDialogCloud& dialog); +void queue_legacy_cloud_worker_task(std::function task); + } // namespace pp::panopainter diff --git a/src/legacy_ui_overlay_services.cpp b/src/legacy_ui_overlay_services.cpp index ce0d4d0c..0866b6fa 100644 --- a/src/legacy_ui_overlay_services.cpp +++ b/src/legacy_ui_overlay_services.cpp @@ -174,6 +174,25 @@ void close_legacy_overlay_handle_ignoring_status( (void)close_legacy_overlay_node(anchor, overlay); } +void close_legacy_overlay_handle_and_reset( + Node& anchor, + pp::ui::NodeHandle& overlay) noexcept +{ + if (overlay.valid()) { + close_legacy_overlay_handle_ignoring_status(anchor, overlay); + overlay = {}; + } +} + +void close_legacy_overlay_handles_and_reset( + Node& anchor, + pp::ui::NodeHandle& popup_overlay, + pp::ui::NodeHandle& tick_overlay) noexcept +{ + close_legacy_overlay_handle_and_reset(anchor, popup_overlay); + close_legacy_overlay_handle_and_reset(anchor, tick_overlay); +} + void close_legacy_overlay_handles_if_open( Node& anchor, const pp::foundation::Result& popup_overlay, diff --git a/src/legacy_ui_overlay_services.h b/src/legacy_ui_overlay_services.h index 3a0448c5..529eaa3c 100644 --- a/src/legacy_ui_overlay_services.h +++ b/src/legacy_ui_overlay_services.h @@ -111,6 +111,13 @@ void close_legacy_popup_overlay(Node& node) noexcept; void close_legacy_overlay_handle_ignoring_status( Node& anchor, pp::ui::NodeHandle overlay) noexcept; +void close_legacy_overlay_handle_and_reset( + Node& anchor, + pp::ui::NodeHandle& overlay) noexcept; +void close_legacy_overlay_handles_and_reset( + Node& anchor, + pp::ui::NodeHandle& popup_overlay, + pp::ui::NodeHandle& tick_overlay) noexcept; void close_legacy_overlay_handles_if_open( Node& anchor, const pp::foundation::Result& popup_overlay, diff --git a/src/main.cpp b/src/main.cpp index 5cd3519b..2bf372e9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -932,8 +932,8 @@ int main(int argc, char** argv) wglMakeCurrent(NULL, NULL); running = 1; - App::I->render_thread_start(); - App::I->ui_thread_start(); + App::I->runtime().render_thread_start(*App::I); + App::I->runtime().ui_thread_start(*App::I); #ifdef _DEBUG glad_set_pre_callback(_pre_call_callback); @@ -1035,8 +1035,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) running = 0; if (hmd_renderer.joinable()) hmd_renderer.join(); - App::I->ui_thread_stop(); - App::I->render_thread_stop(); + App::I->runtime().ui_thread_stop(); + App::I->runtime().render_thread_stop(); App::I->terminate(); delete App::I; PostQuitMessage(0); diff --git a/src/node.cpp b/src/node.cpp index 352903f8..10090042 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -42,7 +42,7 @@ void Node::app_redraw() { App::I->redraw = true; - App::I->render_cv.notify_all(); + App::I->runtime().notify_render_worker(); } void Node::watch(std::function observer) diff --git a/src/node_dialog_cloud.cpp b/src/node_dialog_cloud.cpp index f24c4440..769c07a9 100644 --- a/src/node_dialog_cloud.cpp +++ b/src/node_dialog_cloud.cpp @@ -68,7 +68,7 @@ void NodeDialogCloud::init_controls() btn_cancel = find("btn-cancel"); pp::panopainter::bind_legacy_click_destroys_node(*btn_cancel, *this); container = find("files-list"); - std::thread(&NodeDialogCloud::load_thumbs_thread, this).detach(); + load_thumbs_worker_ = std::jthread(&NodeDialogCloud::load_thumbs_thread, this); } void NodeDialogCloud::loaded() @@ -79,6 +79,7 @@ void NodeDialogCloud::removed(Node* parent) { NodeBorder::removed(parent); closed = true; + load_thumbs_worker_.request_stop(); } NodeText* NodeDialogCloud::create_loading_status_text() @@ -143,24 +144,30 @@ std::vector NodeDialogCloud::create_cloud_file_items(const return nodes; } -void NodeDialogCloud::load_thumbs_thread() +void NodeDialogCloud::load_thumbs_thread(std::stop_token stop) { #if WITH_CURL BT_SetTerminate(); - CURL *curl = curl_easy_init(); + auto curl = std::unique_ptr(curl_easy_init(), curl_easy_cleanup); std::string res; if (curl) { + if (stop.stop_requested() || closed) + return; + auto* text = create_loading_status_text(); auto* align = text->m_parent; - if (!load_cloud_file_list(curl, res, *text)) + if (!load_cloud_file_list(curl.get(), res, *text)) { return; } pp::panopainter::destroy_legacy_node(*align); + if (stop.stop_requested() || closed) + return; + LOG("CLOUD LIST: %s", res.c_str()); auto names = split(res, ','); @@ -171,13 +178,12 @@ void NodeDialogCloud::load_thumbs_thread() { const auto& n = names[i]; auto* node = nodes[i]; - if (closed) + if (stop.stop_requested() || closed) break; - if (!load_cloud_thumb(curl, n, node, res)) + if (!load_cloud_thumb(curl.get(), n, node, res)) break; } - curl_easy_cleanup(curl); } #endif //CURL } diff --git a/src/node_dialog_cloud.h b/src/node_dialog_cloud.h index 184cefdd..a389a0c1 100644 --- a/src/node_dialog_cloud.h +++ b/src/node_dialog_cloud.h @@ -6,6 +6,8 @@ #include "node_text_input.h" #include +#include +#include class NodeDialogCloudItem : public NodeBorder { @@ -46,8 +48,11 @@ public: void init_controls(); virtual void loaded() override; virtual void removed(Node* parent) override; - void load_thumbs_thread(); + void load_thumbs_thread(std::stop_token stop); NodeText* create_loading_status_text(); bool load_cloud_file_list(CURL* curl, std::string& response, NodeText& status_text); std::vector create_cloud_file_items(const std::vector& names); + +private: + std::jthread load_thumbs_worker_; }; diff --git a/src/node_panel_stroke.cpp b/src/node_panel_stroke.cpp index 6bc8bf80..237a4302 100644 --- a/src/node_panel_stroke.cpp +++ b/src/node_panel_stroke.cpp @@ -722,14 +722,10 @@ void NodePanelStroke::execute_stroke_control_plan(const pp::app::BrushStrokeCont void NodePanelStroke::close_popup_overlay_handles() noexcept { - if (m_popup_overlay_handle.valid()) { - pp::panopainter::close_legacy_overlay_handle_ignoring_status(*this, m_popup_overlay_handle); - m_popup_overlay_handle = {}; - } - if (m_tick_overlay_handle.valid()) { - pp::panopainter::close_legacy_overlay_handle_ignoring_status(*this, m_tick_overlay_handle); - m_tick_overlay_handle = {}; - } + pp::panopainter::close_legacy_overlay_handles_and_reset( + *this, + m_popup_overlay_handle, + m_tick_overlay_handle); } kEventResult NodePanelStroke::handle_event(Event* e) diff --git a/src/node_popup_menu.cpp b/src/node_popup_menu.cpp index 7b9d9d93..9544451f 100644 --- a/src/node_popup_menu.cpp +++ b/src/node_popup_menu.cpp @@ -4,6 +4,7 @@ #include "node_popup_menu.h" #include "node_button_custom.h" #include "app.h" +#include Node* NodePopupMenu::clone_instantiate() const { @@ -33,19 +34,22 @@ kEventResult NodePopupMenu::handle_event(Event* e) case kEventType::MouseDownL: break; case kEventType::MouseUpL: - if (m_mouse_inside) { - for (int i = 0; i < m_children.size(); i++) + auto self = std::static_pointer_cast(shared_from_this()); + if (m_mouse_inside) { - if (m_children[i]->m_mouse_inside) + for (int i = 0; i < m_children.size(); i++) { - if (on_select) - on_select(this, i); - break; + if (m_children[i]->m_mouse_inside) + { + if (on_select) + on_select(self.get(), i); + break; + } } } + close_popup(); } - close_popup(); break; default: return kEventResult::Available; @@ -61,13 +65,19 @@ void NodePopupMenu::added(Node* parent) m_mouse_ignore = false; m_flood_events = true; m_capture_children = false; + auto self = std::static_pointer_cast(shared_from_this()); for (int i = 0; i < m_children.size(); i++) { if (auto b = std::dynamic_pointer_cast(m_children[i])) { - b->on_click = [this, i](Node* target) { - if (on_select) - on_select(this, i); + std::weak_ptr weak_self = self; + b->on_click = [weak_self, i](Node* target) { + auto self = weak_self.lock(); + if (!self) { + return; + } + if (self->on_select) + self->on_select(self.get(), i); }; } } diff --git a/test.cpp b/test.cpp new file mode 100644 index 00000000..48aa231b --- /dev/null +++ b/test.cpp @@ -0,0 +1,7 @@ +#include +struct A{}; +int main(){ + std::shared_ptr p; + A* a = nullptr; + p = a; +}