Files
panopainter/docs/modernization/tasks.md

799 lines
29 KiB
Markdown

# Modernization Task Tracker
Status: live
Last updated: 2026-06-17
This file is the active execution queue. It is written for a coordinator that
can assign bounded packets to smaller parallel workers. Completed and stale
history belongs in `docs/modernization/tasks-done.md`, not here.
## Operating Rules
- Prioritize working-app ownership transfer over planners, CLI commands,
package-only cleanup, or test-only work.
- Every coding task must remove or narrow a real retained dependency, hotspot,
unsafe ownership path, or thread/runtime ambiguity.
- Tests are validation and guardrails. A task that only adds tests is not a P0
modernization slice unless it directly enables a blocked ownership move.
- Do not broaden worker scopes. If a task crosses file boundaries, split it
into worker packets with disjoint write scopes and integrate centrally.
- No new `App::I`, `Canvas::I`, owning raw `Node*`, detached worker, direct GL
resource dependency, or platform SDK dependency may be introduced in moved
code.
- Raw pointers may remain only as documented non-owning implementation details
backed by a checked owner, handle, scoped connection, or explicit lifetime
contract.
- Preserve current app behavior first. UI appearance, file formats, brush
behavior, platform behavior, and rendering output are not to be redesigned in
modernization slices.
- Use CMake source ownership as the progress signal. Shrinking
`PP_PANOPAINTER_*` and `PP_LEGACY_*` ownership matters more than adding new
helpers around the same retained code.
## Current Audit Snapshot
Validation performed during the 2026-06-17 review:
- `python scripts/dev/check_component_boundaries.py`: passed.
- `python scripts/dev/check_renderer_api_contract.py`: passed.
Key facts:
- Pure component boundaries currently pass their static checks.
- Remaining architectural risk is concentrated in the working app, retained
app/UI/canvas targets, singleton reach, raw node ownership, and direct GL
resource usage.
- `PP_PANOPAINTER_APP_SOURCES`: 47 files, about 9620 lines.
- `PP_PANOPAINTER_UI_SOURCES`: 52 files, about 9051 lines.
- `PP_LEGACY_PAINT_DOCUMENT_SOURCES`: 22 files, about 6277 lines.
- `PP_LEGACY_APP_SOURCES`: 26 files, about 4711 lines.
- `PP_LEGACY_UI_CORE_SOURCES`: 32 files, about 4304 lines.
- `App::I` still appears hundreds of times in retained app/canvas/UI/resource
code.
- `Canvas::I` still appears hundreds of times in retained canvas modes, panels,
and workflow bridges.
- Raw `Node*` and callback captures remain a dominant UI lifetime risk.
- Retained stroke-preview/runtime draw paths still depend on legacy
render/runtime helpers, but `RTT`, `Texture2D`, `Shape`, `Shader`,
`TextMesh`, and `CanvasLayer` no longer call `App::I` directly for queueing.
- `AppRuntime` now owns synchronized running flags plus explicit post/reject,
same-thread execution, and queue-drain behavior, but broader singleton reach
and app-shell ownership remain.
- Retained cloud upload/download, brush-package import, and timelapse-export
async paths now route through `AppRuntime::canvas_async_task`, but dialog and
execution ownership still remains in retained app/document/cloud bridges.
- `App::dialog_browse()` no longer owns browse-dialog button wiring inline; the
retained document-open bridge now owns that handoff in
`src/legacy_document_open_services.*`.
- `App::dialog_open()`, `App::dialog_browse()`, and `App::dialog_resize()` now
delegate retained dialog construction and overlay/button wiring through
`src/legacy_document_open_services.*` and
`src/legacy_document_session_services.*`, so
`src/app_dialogs_workflow.cpp` is thinner while the save-before-workflow
policy seam remains local.
- `App::init_toolbar_main()` now delegates retained main-toolbar button wiring
through `src/legacy_main_toolbar_binding_services.*`, so
`src/app_layout_main_toolbar.cpp` is down to a thin root lookup and adapter
call while retained toolbar execution still lives in
`src/legacy_app_shell_services.*`.
- `App::init_menu_file()` now delegates retained File-menu popup and export
submenu wiring through `src/legacy_file_menu_binding_services.*`, so
`src/app_layout_file_menu.cpp` is down to a thin trigger lookup and adapter
call while retained file/export execution still lives in
`src/legacy_app_shell_services.*`.
- `App::init_menu_about()` now delegates retained About-menu popup wiring
through `src/legacy_about_menu_binding_services.*`, so
`src/app_layout_about_layer_menu.cpp` no longer owns the About callback body
inline while retained About execution still lives in
`src/legacy_app_shell_services.*`.
- `App::init_menu_tools()` now delegates the retained Tools > Panels submenu
wiring through `src/legacy_tools_menu_binding_services.*`, so
`src/app_layout_tools_menu.cpp` no longer owns that floating-panel submenu
body inline while retained Tools execution and options wiring remain.
- `App::update()` now delegates retained app-frame layout update and
canvas-toolbar refresh execution through `src/legacy_app_frame_services.*`,
so `src/legacy_app_runtime_shell_services.cpp` is thinner at the frame
update seam while draw-time execution still remains there.
- `App::tick()` and `App::resize()` now delegate retained app-frame tick and
surface-resize execution through `src/legacy_app_frame_services.*`, so
`src/app_events.cpp` is thinner at the frame-execution seam while broader
input/platform dispatch still remains there.
- `App::ui_save()` and `App::ui_restore()` now delegate retained floating and
docked panel persistence through `src/legacy_app_ui_state_services.*`, so
`src/app_layout_ui_state.cpp` is down to thin preference adapters while RTL
direction execution stays local.
- `App::init_sidebar()` now delegates the retained color-popup open/close
wiring through `src/legacy_sidebar_color_popup_services.*` with explicit
`App&`, popup-root, trigger-button, and panel dependencies, so
`src/app_layout_sidebar.cpp` is thinner while the retained stroke/grid/layer
popup families still remain inline.
- `App::dialog_usermanual()`, `App::dialog_changelog()`, and
`App::dialog_about()` now delegate the retained info-dialog construction and
overlay close wiring through `src/legacy_info_dialog_services.*` with
explicit `App&` plus overlay-anchor dependencies, and the remaining
What's New plus shortcuts flows now route through the same seam, so
`src/app_dialogs_info_openers.cpp` is down to thin forwarding only.
- `App::dialog_newdoc()` and `App::dialog_save()` now delegate retained dialog
construction and button wiring through
`src/legacy_document_session_services.*`, so
`src/app_dialogs_workflow.cpp` is thinner at the document-session seam.
- `App::title_update()` now delegates retained document-title and DPI-label
rendering through `src/legacy_app_status_services.*`, so
`src/app_layout.cpp` no longer owns that app-status family inline.
- `App::init_toolbar_draw()` now delegates retained draw-toolbar button lookup,
click wiring, and default-tool application through
`src/legacy_draw_toolbar_binding_services.*`, so
`src/app_layout_draw_toolbar.cpp` is down to a thin adapter while retained
tool execution still flows through `src/legacy_canvas_tool_services.*`.
- `App::init_sidebar()` now delegates the retained stroke-popup open/anchor/tick
wiring through `src/legacy_sidebar_stroke_popup_services.*` with explicit
`App&`, popup-root, trigger-button, and panel dependencies, so
`src/app_layout_sidebar.cpp` is thinner while the retained layer popup family
still remains inline.
- `App::init_sidebar()` now delegates the retained grid-popup
open/anchor/tick/close wiring through
`src/legacy_sidebar_grid_popup_services.*`, so the `btn-grids-panel` path in
`src/app_layout_sidebar.cpp` is down to a thin adapter.
- `src/app_dialogs_export.cpp` is now a forwarding adapter; the retained
document export start/branching flows live in
`src/legacy_document_export_services.*`, and the PPBR dialog opener now lives
in `src/legacy_brush_package_export_services.*`.
## Parallel Assignment Rules
Coordinator packets for workers should include only:
- task id and one-paragraph goal
- exact write scope
- allowed read scope
- debt ids that matter
- required validation command
- specific `rg` or `clangd_nav.py` queries
- current behavior notes needed to avoid broad rediscovery
Safe parallel groups:
- One worker on canvas/render execution, one worker on generic UI controls, one
worker on platform CMake cleanup, and one worker on runtime queue contracts
can run in parallel if write scopes remain disjoint.
- Do not run two workers against `src/app_runtime.*`, `src/node.*`,
`src/legacy_canvas_document_io_services.cpp`, or
`src/legacy_node_stroke_preview_runtime_services.cpp` at the same time.
- Do not assign both a CMake source-list move and code edits touching the same
source files to separate workers unless the coordinator serializes the CMake
integration.
## P0 Queue
### ARC-RUN-010 - Harden `AppRuntime` Into An Explicit Runtime Service
Status: Ready
Why now:
Render/UI/background queues are central to memory and thread safety. The
current `AppRuntime` owns several `std::jthread` workers, but runtime state is
still mutable, partly unsynchronized, and app-specific. The working app still
uses `App::I` as the practical access path to render/UI queues.
Write scope:
- `src/app_runtime.h`
- `src/app_runtime.cpp`
- `src/app.h`
- `src/legacy_app_runtime_shell_services.cpp`
- `src/app_core/app_thread.h`
- `tests/app_core/app_thread_tests.cpp`
Read scope:
- `src/texture.cpp`
- `src/rtt.cpp`
- `src/shape.cpp`
- `src/shader.cpp`
- `src/platform_windows/windows_platform_services.cpp`
Required work:
- Make render/UI/prepared-file/canvas worker running state synchronized or
atomic, with a single shutdown path per worker.
- Add explicit runtime service methods for thread-affinity checks and
post/drain/shutdown semantics.
- Keep exceptions from escaping worker bodies.
- Stop exposing queue usage only through `App::I` wrappers for touched call
sites.
- Keep behavior identical for same-thread immediate execution and blocking
render/UI calls.
Done when:
- Touched queue state has no unsynchronized read/write ambiguity.
- Worker shutdown drains or rejects queued work according to documented
behavior.
- Touched app code can call an explicit runtime service instead of reaching
queues through singleton state.
- App-thread planner tests cover shutdown, stopped-worker enqueue, same-thread
execution, and queue-drain behavior that the live runtime implements.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pp_app_core_app_thread_tests -TestRegex "pp_app_core_app_thread"
python scripts/dev/check_component_boundaries.py
```
Mini-model packet:
Start by auditing `render_running_`, `ui_running_`,
`prepared_file_running_`, `canvas_async_running_`, thread ids, and worker stop
methods. Do not rewrite GL resources in this task; expose the runtime contract
needed for the next task.
### ARC-RND-010 - Move GL Resource Queueing Behind Renderer Runtime Contracts
Status: Ready
Why now:
`RTT`, `Texture2D`, `Shape`, `Shader`, `Font`, and `CanvasLayer` still use
`App::I->render_task*` directly. That blocks renderer backends and hides thread
affinity behind a global app singleton.
Write scope:
- `src/texture.cpp`
- `src/texture.h`
- `src/rtt.cpp`
- `src/rtt.h`
- `src/shape.cpp`
- `src/shape.h`
- `src/shader.cpp`
- `src/shader.h`
- `src/font.cpp`
- `src/font.h`
- `src/canvas_layer.cpp`
- `src/canvas_layer.h`
- narrow adapter files if introduced under `src/renderer_gl/`
Read scope:
- `src/app_runtime.*`
- `src/renderer_api/*`
- `src/renderer_gl/*`
- `src/paint_renderer/*`
Required work:
- Introduce a narrow render-dispatch interface or adapter consumed by retained
GL resource classes.
- Convert one coherent GL resource family per slice; do not edit every file in
one worker pass unless the abstraction is already integrated.
- Preserve blocking versus async semantics exactly.
- Do not move app policy into `pp_renderer_gl`.
- Do not add future backend implementation work.
Done when:
- The touched GL resource family no longer calls `App::I` directly for queueing
or thread checks.
- Render-thread assertions use an explicit runtime/render-dispatch contract.
- CMake ownership remains consistent and no pure renderer API target depends on
app headers.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pp_renderer_api_tests,pp_renderer_gl_capabilities_tests -TestRegex "pp_renderer|pp_paint_renderer"
python scripts/dev/check_renderer_api_contract.py
```
Mini-model packet:
Start with either `Texture2D`/`RTT` or `Shape`/`Shader`, not both. Use `rg -n
"App::I->render_task|is_render_thread" src/texture.* src/rtt.*` for the first
slice.
### ARC-RND-011 - Split Canvas Document I/O From Render Execution
Status: Ready
Why now:
`src/legacy_canvas_document_io_services.cpp` is still the largest working-app
document/export hotspot and has the highest `App::I` concentration found in the
review. It mixes license checks, worker dispatch, render readback, progress UI,
platform publish/flush, and retained `Canvas` mutation.
Write scope:
- `src/legacy_canvas_document_io_services.cpp`
- `src/legacy_canvas_document_io_services.h`
- `src/legacy_document_export_services.*`
- `src/app_core/document_export.h`
- `src/paint_renderer/*`
- focused tests under `tests/app_core` or `tests/paint_renderer`
Read scope:
- `src/canvas.*`
- `src/canvas_layer.*`
- `src/legacy_canvas_render_shell_services.*`
- `src/platform_api/platform_services.h`
Required work:
- Pick one export/import family first: equirectangular export, cube-face export,
layer/frame collection export, or project save/open async I/O.
- Move orchestration into an app-core or paint-renderer service request that
accepts explicit document/render/platform dependencies.
- Leave retained `Canvas` as a final adapter only for data that has not moved.
- Remove direct `App::I` calls from the touched path.
- Preserve progress and platform publish behavior.
Done when:
- One live document/export path is executable through an explicit service
request rather than by walking `App::I`/`Canvas::I` from the bridge.
- The retained bridge is visibly thinner and has fewer reasons to know about
UI, worker, platform, and renderer details at the same time.
- The touched path has focused validation that exercises the new request
contract and the retained adapter.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli,pp_app_core_document_export_tests,pp_paint_renderer_compositor_tests -TestRegex "document_export|paint_renderer"
```
Mini-model packet:
Do not broaden into all export types. Start with the path that already has the
strongest pure planning/readiness coverage, then remove only the corresponding
direct app/canvas singleton reach.
### ARC-RND-012 - Make Stroke Preview A Renderer-Owned Service
Status: Ready
Why now:
`NodeStrokePreview` has been thinned, but
`src/legacy_node_stroke_preview_runtime_services.cpp` still owns static worker
state, render-context handoff, preview texture lifetime, and direct app/canvas
access. This is a high-risk UI/render/thread boundary.
Write scope:
- `src/node_stroke_preview.*`
- `src/legacy_node_stroke_preview_runtime_services.*`
- `src/legacy_node_stroke_preview_draw_services.*`
- `src/legacy_node_stroke_preview_sample_services.*`
- `src/paint_renderer/*`
- `tests/paint_renderer/*`
Read scope:
- `src/app_runtime.*`
- `src/texture.*`
- `src/rtt.*`
- `src/canvas.*`
- `src/node_panel_stroke.*`
Required work:
- Move one preview execution phase behind a renderer-facing service contract.
- Replace static worker/resource state for the touched phase with owned service
state or explicit runtime dependency.
- Remove direct `App::I`/`Canvas::I` from the touched phase.
- Preserve preview output and cancellation/shutdown behavior.
Done when:
- The touched preview phase can be reasoned about without reading the full node
implementation.
- Preview worker lifetime is owned and cancellable for the touched phase.
- Renderer-facing tests cover the contract without linking app texture objects.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pp_paint_renderer_compositor_tests -TestRegex "paint_renderer|stroke_preview"
```
Mini-model packet:
Start with result copy, live pass request assembly, or worker lifecycle. Do not
combine all preview phases in one slice.
### ARC-UI-010 - Move Generic Controls Out Of `pp_legacy_ui_core`
Status: Ready
Why now:
Generic controls still live in `PP_LEGACY_UI_CORE_SOURCES`, keeping
`pp_panopainter_ui` tied to retained app/UI targets. This is working-app UI
architecture, not cosmetic cleanup.
Write scope:
- `src/node_button.*`
- `src/node_checkbox.*`
- `src/node_icon.*`
- `src/node_image.*`
- `src/node_scroll.*`
- `src/node_slider.*`
- `src/node_text.*`
- `src/node_text_input.*`
- `src/ui_core/*`
- `cmake/PanoPainterSources.cmake`
- `CMakeLists.txt`
Read scope:
- `src/node.*`
- `src/layout.*`
- app-specific `src/node_panel_*`
- app-specific `src/node_dialog_*`
Required work:
- Move one generic control family at a time to `pp_ui_core`.
- Split renderer-neutral state/event/layout logic from retained GL drawing
when a control still depends on GL classes.
- Keep app-specific panels and dialogs out of `pp_ui_core`.
- Update CMake ownership so the source-list change is real.
Done when:
- At least one generic control family is owned by `pp_ui_core` or has its
renderer-neutral core owned there with only a narrow retained draw adapter.
- `PP_LEGACY_UI_CORE_SOURCES` shrinks.
- Existing UI behavior is unchanged.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pp_ui_core_layout_xml_tests,pp_ui_core_node_lifetime_tests,pp_ui_core_overlay_lifetime_tests -TestRegex "pp_ui_core"
python scripts/dev/check_component_boundaries.py
```
Mini-model packet:
Start with the least app-specific control: checkbox, button, icon, image,
scroll, slider, text, or text input. Do not touch panels/dialogs in the same
slice.
### ARC-UI-011 - Convert UI Ownership To Checked Handles By Default
Status: Ready
Why now:
`pp_ui_core` has checked lifetime helpers, but base `Node` and app panels still
mix raw parent/manager pointers, shared child vectors, raw callback parameters,
and destroy-during-callback assumptions.
Write scope:
- `src/node.*`
- `src/layout.*`
- `src/legacy_ui_overlay_services.*`
- one dialog or panel family per slice under `src/node_dialog_*` or
`src/node_panel_*`
- `src/ui_core/node_lifetime.*`
- `src/ui_core/overlay_lifetime.*`
Read scope:
- call sites found with `rg -n "add_child|remove_child|destroy\\(|on_.*=|Node\\*" src/node_dialog_* src/node_panel_* src/legacy_ui_* src/node.*`
Required work:
- Convert one popup/dialog/panel family to checked handles or scoped
connections.
- Remove raw lifetime assumptions from callbacks in the touched family.
- Document any remaining raw `Node*` as non-owning views with owner proof.
- Keep visual behavior and event ordering unchanged.
Done when:
- The touched UI family can close during callback dispatch without relying on
dangling raw pointers.
- Overlay/popup lifetime flows through `pp_ui_core` lifetime primitives by
default.
- New touched callbacks are scoped or handle-checked.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pp_ui_core_node_lifetime_tests,pp_ui_core_overlay_lifetime_tests -TestRegex "ui_core_(node_lifetime|overlay_lifetime)"
```
Mini-model packet:
Pick one family only, such as open/browse dialogs, picker dialog, popup menu,
layer panel, or stroke panel. Avoid broad `Node` redesign unless the family
requires a small base helper.
### ARC-APP-010 - Reduce App Shells To Composition And Adapters
Status: Ready
Why now:
`PP_PANOPAINTER_APP_SOURCES` is still about 9620 lines. The app shell owns
workflow, dialogs, layout binding, runtime, VR, cloud, brush package, platform
hooks, and retained document/export adapters.
Write scope:
- one `src/app_*.cpp` family per slice
- matching `src/legacy_app_*` service files
- matching app-core planner/service headers only when needed
- `cmake/PanoPainterSources.cmake` if ownership moves
Read scope:
- `src/app.h`
- relevant app-core headers under `src/app_core`
- relevant UI node files for the touched workflow
Required work:
- Pick one shell family: layout menus, dialogs, startup/frame, cloud, brush
package, recording, VR, or document session.
- Move retained implementation into a named service with explicit dependencies.
- Make the app method a thin adapter or composition call.
- Do not add planner-only coverage unless the app method actually shrinks.
Done when:
- One app shell file loses real workflow/runtime ownership.
- The new service accepts explicit `App&`, runtime, platform, UI, document, or
renderer dependencies instead of reading global state internally.
- The touched path has focused validation or an app build gate.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan"
```
Mini-model packet:
Start with a single app shell family. Do not mix dialogs, layout, cloud, and VR
in one worker assignment.
### ARC-PLT-010 - Finish Platform Source Ownership In CMake
Status: Ready
Why now:
Platform implementation ownership improved, but CMake still leaks Web platform
sources into `PP_PANOPAINTER_APP_SOURCES`. Platform implementation files should
belong to concrete `pp_platform_*` targets, not the app source group.
Write scope:
- `cmake/PanoPainterSources.cmake`
- `CMakeLists.txt`
- `src/platform_web/*` only if build integration requires a narrow include or
factory adjustment
Read scope:
- `webgl/CMakeLists.txt`
- `src/platform_android/*`
- `src/platform_linux/*`
- `src/platform_apple/*`
- `src/platform_api/platform_services.h`
Required work:
- Remove `${PP_PLATFORM_WEB_SOURCES}` from `PP_PANOPAINTER_APP_SOURCES`.
- Ensure root app/platform targets link `pp_platform_web` only when needed.
- Keep WebGL retained package entrypoint behavior unchanged.
- Do not reintroduce `platform_legacy`.
Done when:
- Concrete Web platform files are not compiled as app sources in the root app
source group.
- The source ownership direction is visible in CMake.
- Platform API boundary checks still pass.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pp_platform_api_tests -TestRegex "pp_platform_api"
python scripts/dev/check_component_boundaries.py
```
Mini-model packet:
Keep this structural. Do not edit platform behavior unless CMake exposes a real
link or include problem.
## P1 Queue
### ARC-SAFE-010 - Remove Manual Allocation From Touched Ownership Paths
Status: Ready
Why now:
The review found manual `new`/`delete` pockets in node loading, canvas, layer
actions, Wacom/bootstrap helpers, and retained resource cleanup. Some are
non-owning or placement-new cases, but touched working-app ownership paths
should move to RAII containers and factories.
Write scope:
- one selected ownership path at a time, such as `src/legacy_ui_node_loader.*`,
`src/node.*`, `src/canvas.cpp`, `src/platform_windows/windows_bootstrap_helpers.cpp`,
or `src/wacom.cpp`
Read scope:
- immediate owner/caller files for the selected path
Required work:
- Replace owning raw allocation with `std::unique_ptr`, `std::shared_ptr`,
`std::vector`, `std::string`, or an explicit RAII wrapper.
- Preserve non-owning views only where ownership is proven.
- Avoid mixing this with UI or renderer redesign.
Done when:
- The touched path has no owning raw `new`/`delete`.
- Failure paths cannot leak.
- Lifetime remains clear at call sites.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter -TestRegex "pp_ui_core|pp_app_core"
```
Mini-model packet:
Start with a single obvious allocation family. `legacy_ui_node_loader` is a
good first target because it is UI ownership, not rendering behavior.
### ARC-SAFE-011 - Replace Remaining Ad Hoc Workers With Runtime-Owned Services
Status: Ready
Why now:
Most recent worker conversions use `std::jthread`, but retained worker pockets
still sit in UI/dialog/cloud/grid/preview services and `std::async` remains in
`Asset`. The end state requires service-owned cancellation and shutdown.
Write scope:
- one worker family per slice:
`src/node_dialog_cloud.*`, `src/legacy_cloud_services.*`,
`src/legacy_grid_ui_services.*`, `src/asset.*`, or
`src/legacy_node_stroke_preview_runtime_services.*`
Read scope:
- `src/app_runtime.*`
- corresponding app-core/cloud/grid/preview planner headers
- immediate UI caller files
Required work:
- Move worker ownership behind a runtime/service contract.
- Add cancellation or shutdown semantics for the touched worker.
- Avoid capturing raw nodes across worker completion without checked handles.
- Preserve progress and completion callbacks.
Done when:
- The touched worker cannot outlive its owner.
- Shutdown behavior is explicit and validated.
- UI completion handoff is handle-safe or owner-checked.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter -TestRegex "pp_app_core|pp_ui_core"
```
Mini-model packet:
Pick one worker family. Do not perform broad thread cleanup across unrelated
subsystems in one task.
### ARC-WKF-010 - Thin Document Session/Open/Save Bridges
Status: Ready
Why now:
The pure document/session planners are extensive, but live bridges still own
retained prompts, metadata mutation, title updates, history clearing, snapshot
handoff, and legacy `Canvas` execution.
Write scope:
- `src/legacy_document_open_services.*`
- `src/legacy_document_session_services.*`
- `src/legacy_history_services.*`
- focused app-core document/session headers only when needed
Read scope:
- `src/app_core/document_route.h`
- `src/app_core/document_session.h`
- `src/app_core/document_canvas.h`
- `src/app.h`
- `src/canvas.*`
Required work:
- Pick one bridge path: open-project confirmation, save-before-workflow,
save-version, new-document overwrite, history clear, or title update.
- Move decisions/mutations behind explicit service requests.
- Keep retained prompts as adapters only.
Done when:
- One document workflow path has a single obvious owner for decision,
execution, and metadata mutation.
- The retained bridge has less direct `App::I`/`Canvas::I` reach.
- Behavior is covered by existing or focused document-session validation.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli,pp_app_core_document_session_tests,pp_app_core_document_route_tests -TestRegex "document_(session|route)"
```
Mini-model packet:
Do not rewrite all document flows. Pick the narrowest path that removes live
bridge ownership.
### ARC-WKF-011 - Split Cloud And Brush Package Work Out Of UI Nodes
Status: Ready
Why now:
Cloud browse/download/upload and brush package import/export still mix UI node
lifetime, worker ownership, storage, network/asset behavior, and app singleton
reach.
Write scope:
- one family per slice:
`src/legacy_cloud_services.*`, `src/node_dialog_cloud.*`,
`src/legacy_brush_package_import_services.*`,
`src/legacy_brush_package_export_services.*`,
`src/legacy_brush_preset_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.*`
- relevant panel/dialog headers
Required work:
- Separate worker/network/asset execution from node lifetime.
- Use app-core requests and asset helpers where they already exist.
- Use checked handles for UI completion callbacks.
- Preserve current cloud and brush package UX.
Done when:
- One cloud or brush package path can be understood without reading panel or
dialog internals first.
- The touched UI node is a view/controller shell, not the workflow owner.
- The touched worker cannot outlive its service/UI owner.
Validation:
```powershell
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli,pp_app_core_document_cloud_tests,pp_app_core_brush_package_import_tests,pp_app_core_brush_package_export_tests,pp_assets_brush_package_tests -TestRegex "document_cloud|brush_package"
```
Mini-model packet:
Cloud and brush package work are separate packets. Do not assign both to one
small worker.
## Deferred On Purpose
- Vulkan, Metal, WebGPU, and broad future-backend implementation.
- OpenXR implementation beyond boundary cleanup needed to remove OpenVR debt.
- Package-only migration that does not affect root app architecture.
- CLI/planner-only expansion.
- Broad warning cleanup without ownership movement.
- Documentation-only progress claims.