diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index d429812..197de0b 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -942,10 +942,10 @@ Known local toolchain state: between `pp_app_core` PPBR brush package export requests and live `NodePanelBrushPreset::export_ppbr` execution. It preserves dialog metadata, the retained legacy `Image` header object, desktop worker-thread export, - mobile/Web save completion, dialog lifetime, and success messages while the - PPBR preview data-directory override now comes from `PlatformServices`; - remaining brush asset/storage/UI/platform ownership is tracked by - `DEBT-0047`. + mobile/Web save completion, and dialog lifetime while the PPBR preview + data-directory override and export success-dialog metadata now come from + `PlatformServices`/`pp_app_core`; remaining brush asset/storage/UI/platform + ownership is tracked by `DEBT-0047`. - `src/assets/brush_package.*` owns the first headless PPBR package helpers: header validation, legacy-compatible version acceptance, export path normalization, preview-data-directory planning, and imported brush @@ -961,8 +961,10 @@ Known local toolchain state: tip/pattern image target planning, and invalid imported image target inputs. - `pp_app_core_brush_package_export_tests` covers PPBR export request path validation, metadata preservation, legacy-flexible destination/export-data - combinations, service dispatch, and malformed request rejection without - requiring a window, brush preset panel, or filesystem write. + combinations, service dispatch, malformed request rejection, and legacy + success-dialog metadata without requiring a window, brush preset panel, or + filesystem write. `pano_cli plan-brush-package-export` emits the same + success-dialog plan for automation. - `src/legacy_history_services.*` is the current app-shell bridge between `pp_app_core` history plans and legacy `ActionManager`; toolbar and `NodeCanvas` hotkeys share it while document-history extraction remains diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index c994867..e1a4591 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -45,6 +45,13 @@ agent or engineer to remove them without reconstructing context from chat. projection now also uses a tested `pp_app_core` view model exposed by `pano_cli plan-animation-panel-view`, including stale-selection behavior. Legacy canvas/layer/UI execution remains open under DEBT-0022. +- 2026-06-05: DEBT-0047 was narrowed. PPBR export success-dialog metadata now + lives in tested `pp_app_core`, is exposed through + `pano_cli plan-brush-package-export`, and is consumed by + `src/legacy_brush_package_export_services.*`. Retained `NodeDialogExportPPBR` + reads, legacy `Image` header ownership, `PPBRInfo` conversion, + `NodePanelBrushPreset::export_ppbr`, desktop worker threading, dialog + lifetime, mobile/Web completion, and PPBR serialization remain open. - 2026-06-05: DEBT-0021 was narrowed again. Layer panel selected-control and visibility view projection now goes through tested `pp_app_core` planning, `NodePanelLayer::update_attributes()` consumes that view model in the live @@ -243,7 +250,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0044 | Open | Modernization | Timelapse and animation MP4 export execution dispatch now consumes pure `pp_app_core` through `App::dialog_timelapse_export`, `App::dialog_export_mp4`, `pano_cli plan-export-menu`, `pano_cli plan-export-target --kind name`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, and success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, and owns mobile/Web save callbacks | Preserve current MP4/timelapse export behavior while video export moves toward app/document/renderer/video/platform/storage services | `pp_app_core_document_export_tests`; `pano_cli plan-export-menu --kind animation-mp4`; `pano_cli plan-export-menu --kind timelapse`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -animation`; `pano_cli plan-export-target --kind name --doc-name demo --suffix -timelapse`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind animation-mp4 --message "video export path must not be empty"`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, and mobile/Web save callbacks are owned by injected app/document/renderer/video/platform/storage services with export dialogs acting only as UI adapters | | DEBT-0045 | Open | Modernization | Options-menu preference execution now consumes pure `pp_app_core` through UI scale, viewport scale, RTL direction, VR mode, VR-controller, auto-timelapse, and canvas cursor-mode callbacks plus `AppPreferenceServices` and `src/legacy_app_preference_services.*`; viewport-density and cursor-mode execution now delegate to `src/legacy_canvas_view_services.*`, but the bridges still call legacy `App::set_ui_scale`, `App::set_ui_rtl`, `App::rec_start`, `App::rec_stop`, retained canvas view mutation, and `Settings::save` directly; VR mode callbacks now call `App` VR wrappers that dispatch to `PlatformServices`, while the actual Windows OpenVR SDK bridge still lives in `WindowsPlatformServices` | Preserve current options-menu behavior while preferences move toward app/UI/platform/storage services | `pp_app_core_app_preferences_tests`; `pp_app_core_canvas_view_tests`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-canvas-view-density --density 1.5`; `pano_cli plan-canvas-view-cursor-mode --mode 3`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Preference persistence, UI/layout direction, viewport density, cursor mode, VR mode start/stop/failure handling, VR-controller state, and auto-timelapse recording side effects are owned by injected app/UI/platform/storage services with options-menu callbacks acting only as UI adapters | | DEBT-0046 | Open | Modernization | Startup preference/runtime execution and startup resource sequencing now consume pure `pp_app_core` through `App::init`, `pano_cli plan-app-startup`, `pano_cli plan-app-startup-resources`, `AppStartupServices`, `AppStartupResourceServices`, and `src/legacy_app_startup_services.*`, but the bridge still calls legacy `Settings::set`, `Settings::save`, `App::rec_start`, app VR-controller state mutation, message-box license warning execution, shader loading, asset initialization, layout creation, title updates, and UI render-target creation directly | Preserve current startup behavior while app startup moves toward app/preferences/storage/recording/UI/renderer services | `pp_app_core_app_startup_tests`; `pano_cli plan-app-startup --run-counter 7 --vr-controllers-disabled --license-invalid`; `pano_cli plan-app-startup --run-counter -1`; `pano_cli plan-app-startup-resources --width 1280 --height 720`; `pano_cli plan-app-startup-resources --bad-size`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Startup preference persistence, auto-timelapse startup, stored VR-controller state, license validation/warning, startup resource initialization, title updates, and UI render-target allocation are owned by injected app/preferences/storage/recording/UI/renderer services with `App::init` acting only as orchestration | -| DEBT-0047 | Open | Modernization | PPBR brush package export request validation and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, and the macOS data-directory override now routes through `PlatformServices`, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, owns desktop worker-thread dispatch, dialog destruction, mobile/Web completion, and success-message behavior directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and success UI are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter | +| DEBT-0047 | Open | Modernization | PPBR brush package export request validation, success-dialog metadata, and execution dispatch now consume pure `pp_app_core` through `App::dialog_ppbr_export`, `pano_cli plan-brush-package-export`, `BrushPackageExportServices`, and `src/legacy_brush_package_export_services.*`; PPBR header/path planning now consumes `pp_assets::brush_package`, and the macOS data-directory override now routes through `PlatformServices`, but the bridge still reads `NodeDialogExportPPBR`, carries the legacy `Image` header object outside the pure request, converts to `NodePanelBrushPreset::PPBRInfo`, calls `NodePanelBrushPreset::export_ppbr`, owns desktop worker-thread dispatch, dialog destruction, and mobile/Web completion directly | Preserve current PPBR export behavior while brush assets, PPBR serialization, picker completion, and UI lifetime move toward asset/storage/UI/platform services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_export_tests`; `pp_platform_api_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --author Artist --dest-path D:/Paint/BrushPreviews --export-data --header-image`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `pano_cli plan-brush-package-export`; `pano_cli plan-brush-package-export --path clouds`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr --dest-path D:/Paint/BrushPreviews --no-export-data`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | PPBR metadata collection, header-image ownership, serialization, picker-selected path execution, desktop threading, dialog lifetime, and mobile/Web completion are owned by injected brush asset/storage/UI/platform services with `App::dialog_ppbr_export` acting only as a UI adapter | | DEBT-0048 | Open | Modernization | ABR/PPBR brush package import execution now consumes pure `pp_app_core` through document-open confirmation callbacks, `pano_cli plan-brush-package-import`, `BrushPackageImportServices`, and `src/legacy_brush_package_import_services.*`; imported brush tip/pattern target paths now consume `pp_assets::brush_package`, but the bridge still launches detached legacy `NodePanelBrushPreset::import_abr`/`import_ppbr` worker threads and depends on the legacy preset panel as the importer/storage owner | Preserve current brush import behavior while brush package parsing, preset storage, progress/error reporting, and UI refresh move toward asset/paint/UI services | `pp_assets_brush_package_tests`; `pp_app_core_brush_package_import_tests`; `pano_cli plan-brush-package-import --kind ppbr --path D:/Paint/Brushes/clouds.ppbr`; `pano_cli plan-brush-package-import --kind abr --path D:/Paint/Brushes/clouds.abr`; `pano_cli plan-brush-package-import --kind ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | ABR/PPBR parsing, preset creation/storage, import threading/progress, duplicate asset policy, and UI refresh are owned by injected brush asset/paint/UI services with document-open callbacks only confirming user intent | | DEBT-0049 | Open | Modernization | `pp_assets::validate_ppbr_header` intentionally preserves the legacy PPBR version check from `NodePanelBrushPreset::import_ppbr`, which accepts files when either major is `0` or minor is `1` instead of requiring exactly version `0.1` | Avoid rejecting existing brush packages before compatibility fixtures prove the stricter rule is safe | `pp_assets_brush_package_tests`; `pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Add PPBR compatibility fixtures for accepted/rejected historical package versions, then require canonical `0.1` or an explicit supported-version matrix and update live import accordingly | | DEBT-0050 | Open | Modernization | iOS exported-image photo-library publishing and WebGL persistent-storage flushing now dispatch through `PlatformServices`, but non-Windows execution still lives in `src/platform_legacy/legacy_platform_services.*` and forwards to retained `save_image_library`/`webgl_sync` bridges | Preserve current iOS/Web export and save behavior while the Apple/Web platform shells are extracted incrementally | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug`; platform package smoke once Apple/Web root builds exist | Exported-image publishing and persistent-storage flushing are owned by injected Apple/Web `pp_platform_*` services with no legacy adapter branch | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 57aea7b..a50ef3f 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -1039,8 +1039,10 @@ success messages while retained execution remains tracked under `DEBT-0044`. 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, `NodePanelBrushPreset::export_ppbr`, and existing -success messages while retained execution remains tracked under `DEBT-0047`. +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`. diff --git a/src/app_core/brush_package_export.h b/src/app_core/brush_package_export.h index 7df8c47..903c6f5 100644 --- a/src/app_core/brush_package_export.h +++ b/src/app_core/brush_package_export.h @@ -1,5 +1,6 @@ #pragma once +#include "app_core/app_dialog.h" #include "foundation/result.h" #include @@ -33,6 +34,13 @@ public: return pp::foundation::Status::success(); } +[[nodiscard]] inline AppMessageDialogPlan plan_brush_package_export_success_dialog(std::string_view path) +{ + std::string message = "Brushes exported to:\n"; + message += path; + return plan_app_message_dialog("Export PPBR", message, false); +} + [[nodiscard]] inline pp::foundation::Status validate_brush_package_export_request( std::string_view path, const BrushPackageExportRequest& request) noexcept diff --git a/src/legacy_brush_package_export_services.cpp b/src/legacy_brush_package_export_services.cpp index dafe3d8..e6bc2d6 100644 --- a/src/legacy_brush_package_export_services.cpp +++ b/src/legacy_brush_package_export_services.cpp @@ -50,7 +50,8 @@ public: BT_SetTerminate(); app->presets->export_ppbr(path_string, info); dialog->destroy(); - app->message_box("Export PPBR", "Brushes exported to:\n" + path_string); + const auto plan = pp::app::plan_brush_package_export_success_dialog(path_string); + app->message_box(plan.title, plan.message, plan.show_cancel); }).detach(); return; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 41f7811..c25d698 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1324,6 +1324,12 @@ if(TARGET pano_cli) LABELS "app;paint;assets;integration;desktop-fast" PASS_REGULAR_EXPRESSION "\"command\":\"plan-brush-package-export\".*\"path\":\"D:/Paint/clouds.ppbr\".*\"author\":\"Artist\".*\"destPath\":\"D:/Paint/BrushPreviews\".*\"exportData\":true.*\"hasHeaderImage\":true.*\"paths\":\\{\"package\":\"D:/Paint/clouds.ppbr\".*\"dataDirectory\":\"D:/Paint/BrushPreviews/clouds_data\".*\"dataDirectoryEnabled\":true.*\"dispatches\":1") + add_test(NAME pano_cli_plan_brush_package_export_success_dialog_smoke + COMMAND pano_cli plan-brush-package-export --path D:/Paint/clouds.ppbr) + set_tests_properties(pano_cli_plan_brush_package_export_success_dialog_smoke PROPERTIES + LABELS "app;paint;assets;integration;desktop-fast" + PASS_REGULAR_EXPRESSION "\"command\":\"plan-brush-package-export\".*\"successDialog\":\\{\"title\":\"Export PPBR\",\"message\":\"Brushes exported to:\\\\nD:/Paint/clouds.ppbr\",\"showCancel\":false\\}") + add_test(NAME pano_cli_plan_brush_package_export_rejects_empty_path COMMAND "${CMAKE_COMMAND}" -DPANO_CLI=$ diff --git a/tests/app_core/brush_package_export_tests.cpp b/tests/app_core/brush_package_export_tests.cpp index b9c9462..bf1b61d 100644 --- a/tests/app_core/brush_package_export_tests.cpp +++ b/tests/app_core/brush_package_export_tests.cpp @@ -82,6 +82,15 @@ void executor_rejects_malformed_requests_before_dispatch(pp::tests::Harness& har PP_EXPECT(harness, services.exports == 1); } +void success_dialog_preserves_legacy_message(pp::tests::Harness& harness) +{ + const auto plan = pp::app::plan_brush_package_export_success_dialog("D:/Paint/clouds.ppbr"); + + PP_EXPECT(harness, plan.title == "Export PPBR"); + PP_EXPECT(harness, plan.message == "Brushes exported to:\nD:/Paint/clouds.ppbr"); + PP_EXPECT(harness, !plan.show_cancel); +} + } // namespace int main() @@ -90,5 +99,6 @@ int main() harness.run("validates path and preserves metadata edges", validates_path_and_preserves_metadata_edges); harness.run("executor dispatches export request", executor_dispatches_export_request); harness.run("executor rejects malformed requests before dispatch", executor_rejects_malformed_requests_before_dispatch); + harness.run("success dialog preserves legacy message", success_dialog_preserves_legacy_message); return harness.finish(); } diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 3664ed3..6c4ec4a 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -5363,6 +5363,7 @@ int plan_brush_package_export(int argc, char** argv) print_error("plan-brush-package-export", paths.status().message); return 2; } + const auto success_dialog = pp::app::plan_brush_package_export_success_dialog(services.last_path); std::cout << "{\"ok\":true,\"command\":\"plan-brush-package-export\"" << ",\"request\":{\"path\":\"" << json_escape(services.last_path) @@ -5379,6 +5380,9 @@ int plan_brush_package_export(int argc, char** argv) << "\",\"extension\":\"" << json_escape(paths.value().extension) << "\",\"dataDirectory\":\"" << json_escape(paths.value().data_directory) << "\",\"dataDirectoryEnabled\":" << json_bool(paths.value().data_directory_enabled) + << "},\"successDialog\":{\"title\":\"" << json_escape(success_dialog.title) + << "\",\"message\":\"" << json_escape(success_dialog.message) + << "\",\"showCancel\":" << json_bool(success_dialog.show_cancel) << "},\"dispatches\":" << services.exports << "}\n"; return 0;