328 lines
12 KiB
Markdown
328 lines
12 KiB
Markdown
# PanoPainter Modernization Roadmap
|
|
|
|
Status: live
|
|
Last updated: 2026-06-16
|
|
|
|
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`.
|
|
|
|
## Objective
|
|
|
|
Turn PanoPainter into a thin composition-root application over separable C++23
|
|
components while preserving current behavior.
|
|
|
|
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:
|
|
|
|
- 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
|
|
|
|
## What This Roadmap Covers
|
|
|
|
- app architecture
|
|
- component boundaries
|
|
- platform boundaries
|
|
- renderer/app ownership boundaries
|
|
- the order of work needed to finish the split
|
|
|
|
It does not try to be the full build, test, or automation reference.
|
|
Those details live in the other modernization docs when needed.
|
|
|
|
## What Does Not Count As Top-Priority Progress
|
|
|
|
These can still be useful, but they are not first-order modernization work
|
|
while the app shell still mostly looks like the old codebase:
|
|
|
|
- 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
|
|
|
|
## Reality Check
|
|
|
|
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` no longer reaches `App::I`
|
|
directly, and Linux FPS title reporting now uses an injected callback, but
|
|
retained Apple bridging in `platform_legacy` and other platform/app coupling
|
|
remain, even though iOS keyboard visibility and prepared-file save handoff
|
|
now also route through explicit Apple bridge callbacks and Apple render-
|
|
context hooks plus iOS main-render-target binding now route through the same
|
|
bridge style, as do Apple crash-test, app-close, and iOS SonarPen hooks,
|
|
while Linux/Web GLFW render-context acquire/present and Linux app-close now
|
|
route through retained local GLFW callback hooks.
|
|
- `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, even though `NodeCanvas`
|
|
display resolve, cache-to-screen composite, post-draw mask/grid/current-mode
|
|
sequencing, per-layer/per-plane retained draw execution, and shared
|
|
checkerboard background setup now route through retained draw-merge helpers,
|
|
with the cache-to-screen checkerboard-plane callback setup also reduced and
|
|
the merged-path per-plane merged-texture draw now routed through the same
|
|
retained helper family.
|
|
- `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, retained static worker
|
|
ownership in several app families, and ad hoc mutex/condition-variable
|
|
ownership, even though most previously detached or raw app-facing worker
|
|
launches now use owned `std::jthread` or service-owned worker queues and
|
|
`AppRuntime` now owns render/UI workers with explicit `std::jthread`
|
|
shutdown semantics while the Windows splash-dialog and HMD renderer workers
|
|
also use owned `std::jthread` lifecycle, `LogRemote` now uses the same
|
|
ownership model, and the Windows VR device now has explicit `std::unique_ptr`
|
|
ownership instead of raw global lifetime.
|
|
- 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_app_core + pp_panopainter_ui + pp_platform_*
|
|
-> panopainter_app
|
|
```
|
|
|
|
Key ownership rules:
|
|
|
|
- `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.
|
|
|
|
## Workstreams
|
|
|
|
### 1. Break The Canvas And Preview Hotspots First
|
|
|
|
This is the highest-value work because it moves the largest concentration of
|
|
real app behavior out of the old shell.
|
|
|
|
Required outcomes:
|
|
|
|
- `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
|
|
|
|
`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 and
|
|
no longer depends on detached worker launch sites
|
|
|
|
### 7. Only Then Resume Future Backend Work
|
|
|
|
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:
|
|
|
|
- `platform_legacy`
|
|
- `pp_legacy_app`
|
|
- `pp_legacy_ui_core`
|
|
- `pp_legacy_paint_document`
|
|
- large GL-heavy node and canvas files
|
|
|
|
## Exit Criteria
|
|
|
|
The modernization is not done until these are all true:
|
|
|
|
- `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
|
|
|
|
## Active Queue
|
|
|
|
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.
|