235 lines
9.2 KiB
Markdown
235 lines
9.2 KiB
Markdown
# PanoPainter Modernization Roadmap
|
|
|
|
Status: live
|
|
Last updated: 2026-06-17
|
|
|
|
This roadmap is the architecture guide for finishing the modernization. The
|
|
execution queue lives in `docs/modernization/tasks.md`; completed history lives
|
|
in `docs/modernization/tasks-done.md`; shortcuts and temporary adapters live in
|
|
`docs/modernization/debt.md`.
|
|
|
|
## Objective
|
|
|
|
Turn PanoPainter into a thin C++23 composition-root application over separable,
|
|
testable components while preserving the working app.
|
|
|
|
The goal is not more planners, CLI commands, or tests around the old shell. The
|
|
goal is ownership transfer out of retained app, UI, canvas, renderer, and
|
|
platform hotspots into explicit components with safe lifetime and thread
|
|
contracts.
|
|
|
|
## 2026-06-17 Architecture Review
|
|
|
|
The extracted components are real and currently clean at their boundaries:
|
|
|
|
- `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`
|
|
|
|
Review validation:
|
|
|
|
- `python scripts/dev/check_component_boundaries.py` passed with zero source or
|
|
link violations.
|
|
- `python scripts/dev/check_renderer_api_contract.py` passed with zero renderer
|
|
API violations.
|
|
|
|
The risk is therefore not component pollution. The risk is that the working app
|
|
still bypasses those clean components through retained shells, singleton state,
|
|
raw node ownership, direct GL resource calls, and ad hoc thread access.
|
|
|
|
Measured source pressure from `cmake/PanoPainterSources.cmake`:
|
|
|
|
| Source group | Files | Approx. lines | Review read |
|
|
| --- | ---: | ---: | --- |
|
|
| `PP_PANOPAINTER_APP_SOURCES` | 47 | 9620 | Still a workflow/runtime shell, not composition only |
|
|
| `PP_PANOPAINTER_UI_SOURCES` | 52 | 9051 | Still owns app-specific UI plus canvas/preview rendering |
|
|
| `PP_LEGACY_PAINT_DOCUMENT_SOURCES` | 22 | 6277 | Canvas/document/render behavior still concentrated here |
|
|
| `PP_LEGACY_APP_SOURCES` | 26 | 4711 | Canvas modes, preferences, history, recording, overlays |
|
|
| `PP_LEGACY_UI_CORE_SOURCES` | 32 | 4304 | Generic controls and base node still retained |
|
|
| `PP_LEGACY_RENDERER_GL_SOURCES` | 5 | 2854 | Direct GL classes still consumed by app/UI/canvas |
|
|
|
|
Measured safety pressure in `src/`:
|
|
|
|
| Signal | Count | Main hotspots |
|
|
| --- | ---: | --- |
|
|
| `App::I` references | 383 | canvas document I/O, GL resource classes, brush/panel services, UI tree services |
|
|
| `Canvas::I` references | 364 | canvas modes, layer/stroke panels, brush UI, app shell services |
|
|
| Raw `Node*`-style references | 624 | layout/menu bindings, node tree services, panel headers, dialog code |
|
|
| `new` tokens | 198 | node loader, `canvas.cpp`, layer/panel actions, bootstrap helpers |
|
|
| `delete` tokens | 61 | retained GL/resources, asset/bootstrap/manual cleanup pockets |
|
|
| Render queue call sites | 105 | `RTT`, `Texture2D`, `Shape`, `Shader`, `CanvasLayer`, canvas I/O |
|
|
| UI queue call sites | 69 | window shell, document I/O, UI tree services, app runtime |
|
|
|
|
Current conclusion:
|
|
|
|
- The pure component graph is defensible.
|
|
- The live app is not yet modern C++23 in ownership, lifetime, or thread
|
|
safety.
|
|
- Raw pointers and singletons are still architectural coupling, not just style
|
|
debt.
|
|
- Renderer API contracts exist, but retained OpenGL resource classes still leak
|
|
into app/UI/document code.
|
|
- `AppRuntime` now owns synchronized running flags and explicit
|
|
same-thread/post-reject queue behavior, but broader app/runtime singleton
|
|
reach and retained shell ownership still remain.
|
|
- Platform extraction improved substantially and the root app source group no
|
|
longer compiles Web platform sources directly, but broader CMake and
|
|
entrypoint cleanup are not complete.
|
|
|
|
## Target Architecture
|
|
|
|
The final shape is a layered DAG with one 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_app_core + pp_panopainter_ui + pp_platform_*
|
|
-> panopainter_app
|
|
```
|
|
|
|
Ownership rules:
|
|
|
|
- `panopainter_app` composes components and platform services. It does not own
|
|
document workflow, renderer execution, dialog logic, or platform state.
|
|
- `pp_app_core` owns UI-free workflow policy and service contracts. It does not
|
|
own nodes, GL resources, OS handles, or background threads.
|
|
- `pp_ui_core` owns generic scene graph, layout, controls, checked handles,
|
|
scoped connections, mutation-safe dispatch, and UI-thread affinity rules.
|
|
- `pp_panopainter_ui` owns PanoPainter-specific panels, dialogs, canvas views,
|
|
and UI-to-app bindings.
|
|
- `pp_renderer_api` owns backend-neutral resource and command contracts.
|
|
- `pp_renderer_gl` owns OpenGL implementation details while OpenGL remains the
|
|
production backend.
|
|
- `pp_paint_renderer` owns paint, export, preview, and document-frame render
|
|
contracts over `pp_renderer_api`.
|
|
- `pp_platform_api` owns platform-neutral interfaces and policy only.
|
|
- `pp_platform_*` targets own SDK handles, event loops, windows, native pickers,
|
|
render-context binding, VR/device bridges, and platform-specific state.
|
|
|
|
Safety rules:
|
|
|
|
- Owning raw pointers are not allowed in new component code.
|
|
- Raw pointers in retained code must be treated as non-owning views and removed
|
|
from touched ownership boundaries unless the lifetime is proven by checked
|
|
handles, scoped connections, or owner objects.
|
|
- UI and render access must go through explicit runtime services, not `App::I`
|
|
or `Canvas::I` reach.
|
|
- Worker threads must be joinable, cancellable, and owned by a service with
|
|
shutdown semantics. Detached workers and static worker state are not
|
|
acceptable end-state architecture.
|
|
- Renderer-facing app and UI code must depend on `pp_renderer_api` contracts,
|
|
not `Texture2D`, `RTT`, direct GL calls, or retained render-thread helpers.
|
|
- Public modernization APIs keep exceptions out of app code and return explicit
|
|
`Status`/`Result` objects.
|
|
|
|
## Priority Order
|
|
|
|
### P0: Move Working-App Ownership
|
|
|
|
The next phase must reduce the working app, not expand headless planner
|
|
coverage. Priority work moves real ownership out of:
|
|
|
|
- `src/legacy_canvas_document_io_services.cpp`
|
|
- `src/legacy_canvas_render_shell_services.cpp`
|
|
- `src/legacy_node_canvas_draw_services.cpp`
|
|
- `src/legacy_node_stroke_preview_runtime_services.cpp`
|
|
- `src/app_runtime.*`
|
|
- `src/app_dialogs*.cpp`
|
|
- `src/app_layout*.cpp`
|
|
- `src/legacy_ui_node_*`
|
|
- generic `src/node_*` controls still in `pp_legacy_ui_core`
|
|
|
|
### P1: Make Safety Boundaries Enforceable
|
|
|
|
The working app should increasingly fail compile or validation when new code
|
|
uses unsafe ownership or the wrong component direction. This includes:
|
|
|
|
- no new `App::I` or `Canvas::I` in moved code
|
|
- no new owning `Node*`
|
|
- no direct GL resource use from future-backend-facing UI/workflow code
|
|
- explicit runtime service contracts for render/UI/background queues
|
|
- explicit platform target ownership in CMake
|
|
|
|
### P2: Retire Compatibility Bridges
|
|
|
|
After the biggest working-app hotspots move, thin the remaining retained
|
|
workflow bridges:
|
|
|
|
- document open/save/export/session bridges
|
|
- cloud transfer and browser bridges
|
|
- brush package import/export bridges
|
|
- retained platform/package compatibility paths
|
|
|
|
### Deferred
|
|
|
|
Do not restart these as top-priority work until the P0/P1 conditions are
|
|
materially improved:
|
|
|
|
- Vulkan, Metal, WebGPU, or OpenXR expansion beyond necessary boundary design
|
|
- package-only cleanup that does not change root app architecture
|
|
- CLI-only planner work
|
|
- test-only expansion that does not guard an ownership transfer
|
|
- scorekeeping or documentation churn without code ownership implications
|
|
|
|
## Exit Criteria
|
|
|
|
Modernization is not complete until all are true:
|
|
|
|
- `panopainter_app` is composition only.
|
|
- `pp_panopainter_ui` no longer depends on `pp_legacy_app`.
|
|
- `pp_legacy_ui_core`, `pp_legacy_app`, and `pp_legacy_paint_document` are
|
|
deleted or reduced to narrow debt-tracked adapters.
|
|
- `App::I` and `Canvas::I` are no longer cross-component access paths.
|
|
- Platform SDK handles and event loops live only in `pp_platform_*` targets.
|
|
- `pp_platform_api` is SDK-free and implementation-free.
|
|
- `canvas.cpp`, `node_canvas.cpp`, and `node_stroke_preview.cpp` no longer own
|
|
large renderer orchestration bodies.
|
|
- UI ownership uses checked handles and scoped callback connections by default.
|
|
- Render/UI/background queues are owned by explicit runtime services with
|
|
cancellation, shutdown, and thread-affinity contracts.
|
|
- Future-backend-facing app/UI/document code uses `pp_renderer_api` and
|
|
`pp_paint_renderer`, not retained OpenGL resource classes.
|
|
- The working app builds and passes focused validation for each migrated slice.
|
|
|
|
## How To Execute
|
|
|
|
Use `docs/modernization/tasks.md` as the active work queue. It is written as
|
|
coordinator-ready packets for smaller parallel workers. Pick disjoint write
|
|
scopes, pass only the packet context to each worker, integrate locally, run the
|
|
listed validation, update debt/tasks if the slice moves ownership, then commit
|
|
and push.
|