Files
panopainter/docs/modernization/tasks.md

22 KiB

Modernization Task Tracker

Status: live Last updated: 2026-06-16

This file now tracks only active architecture work. Completed, blocked, and superseded task history moved to docs/modernization/tasks-done.md.

Operating Rules

  • 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

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

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.