diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index f7e1e66..d429812 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -653,6 +653,9 @@ Known local toolchain state: dialog metadata for equirectangular image, layer/frame collection, depth/cube, animation MP4, and timelapse success paths as JSON, including platform-style destinations and suppressed/no-message paths. +- `pano_cli plan-export-report` exposes `pp_app_core` export failure and + license-disabled dialog metadata as JSON; live export dialogs consume the + same metadata before retained legacy export execution or logging continues. - `pano_cli plan-export-start` exposes `pp_app_core` export availability planning for license-gated, demo-blocked, and missing-canvas states as JSON; the live image, layer, animation-frame, depth, and cube-face export dialogs diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index ccf683c..c994867 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -175,6 +175,14 @@ agent or engineer to remove them without reconstructing context from chat. `App::show_progress`, `App::message_box`, `App::input_box`, and `pano_cli plan-app-dialog`, but retained `Node*` dialog creation, layout insertion, callback wiring, and lifetime ownership remain in the legacy app. +- 2026-06-05: DEBT-0043 and DEBT-0044 were narrowed. Export success, + failure, and license-disabled dialog metadata plus export execution log + labels now live in tested `pp_app_core` planning consumed by + `src/legacy_document_export_services.*`, `App::dialog_export*`, + `pano_cli plan-export-message`, and `pano_cli plan-export-report`. + Retained canvas/video export calls, Web prepared-file handoff, picker + execution, export-directory creation, and desktop timelapse worker threading + remain open. - 2026-06-04: DEBT-0036 was narrowed again. Canvas stroke commit, thumbnail, and object-draw history paths now query saved blend state through tested `pp_renderer_gl` capability-state dispatch; CanvasLayer equirect @@ -193,7 +201,7 @@ agent or engineer to remove them without reconstructing context from chat. | --- | --- | --- | --- | --- | --- | --- | | DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets | | DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation | -| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `App::rec_loop`, `App::update_ui_observer`, `App::render_task*`, `App::ui_task*`, `App::render_thread_*`, `App::ui_thread_*`, file-menu save actions, `NodeCanvas` canvas hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/menu/target naming/path/message decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress/worker decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display and renderer diagnostic decisions, app dialog metadata decisions, app frame/UI-observer decisions, app thread/task orchestration decisions, document resize decisions, layer rename/menu decisions, Tools menu/panel decisions, About menu/diagnostic decisions, main toolbar/status decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-file-menu`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-menu`, `pano_cli plan-export-target`, `pano_cli plan-export-message`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-app-dialog`, `pano_cli plan-app-thread`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, `pano_cli plan-about-menu`, `pano_cli plan-main-toolbar`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-layer-menu`, `pano_cli plan-canvas-hotkey`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/dialog/thread/share/platform-I/O/display/keyboard/cloud/resize/layer/tools/about/toolbar/canvas-command contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, Tools panel creation/execution, About dialog/diagnostic execution, toolbar/status dialog/history/canvas execution, app dialog node creation, status/display UI rendering, renderer diagnostic capability adaptation, app task/thread execution, UI observer parent walking/callback execution, document resize execution, layer rename/menu execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4/PBO execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_file_menu_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_frame_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_app_dialog_tests`; `pp_app_core_app_thread_tests`; `pp_app_core_tools_menu_tests`; `pp_app_core_about_menu_tests`; `pp_app_core_main_toolbar_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pp_app_core_canvas_hotkey_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --running --no-encoder`; `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-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12 --framebuffer-fetch --float32 --float32-linear --float16`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-frame`; `pano_cli plan-app-thread --kind ui-loop --live-reload`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-canvas-hotkey --event key-up --key z --ctrl --undo-count 2`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries | +| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `App::rec_loop`, `App::update_ui_observer`, `App::render_task*`, `App::ui_task*`, `App::render_thread_*`, `App::ui_thread_*`, file-menu save actions, `NodeCanvas` canvas hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/menu/target naming/path/message/report decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress/worker decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display and renderer diagnostic decisions, app dialog metadata decisions, app frame/UI-observer decisions, app thread/task orchestration decisions, document resize decisions, layer rename/menu decisions, Tools menu/panel decisions, About menu/diagnostic decisions, main toolbar/status decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-file-menu`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-menu`, `pano_cli plan-export-target`, `pano_cli plan-export-message`, `pano_cli plan-export-report`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-app-dialog`, `pano_cli plan-app-thread`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, `pano_cli plan-about-menu`, `pano_cli plan-main-toolbar`, `pano_cli plan-document-resize`, `pano_cli plan-layer-rename`, `pano_cli plan-layer-menu`, `pano_cli plan-canvas-hotkey`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/dialog/thread/share/platform-I/O/display/keyboard/cloud/resize/layer/tools/about/toolbar/canvas-command contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, Tools panel creation/execution, About dialog/diagnostic execution, toolbar/status dialog/history/canvas execution, app dialog node creation, status/display UI rendering, renderer diagnostic capability adaptation, app task/thread execution, UI observer parent walking/callback execution, document resize execution, layer rename/menu execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4/PBO execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_file_menu_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_frame_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_app_dialog_tests`; `pp_app_core_app_thread_tests`; `pp_app_core_tools_menu_tests`; `pp_app_core_about_menu_tests`; `pp_app_core_main_toolbar_tests`; `pp_app_core_document_resize_tests`; `pp_app_core_document_layer_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pp_app_core_canvas_hotkey_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-file-menu --command save-as`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind animation-mp4 --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-export-message --kind timelapse --destination success`; `pano_cli plan-export-report --kind license-disabled`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --running --no-encoder`; `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-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12 --framebuffer-fetch --float32 --float32-linear --float16`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-frame`; `pano_cli plan-app-thread --kind ui-loop --live-reload`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-document-resize --current-resolution 2048 --selected-resolution-index 4`; `pano_cli plan-layer-rename --old-name Base --new-name Paint`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-canvas-hotkey --event key-up --key z --ctrl --undo-count 2`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries | | DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path | | DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated | | DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions | @@ -231,8 +239,8 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0040 | Open | Modernization | Close request, document save, save-before-workflow planning/execution dispatch, and close/save-before/save-error prompt metadata now consume pure `pp_app_core` through `App::request_close`, `App::save_document`, `App::continue_document_workflow_after_optional_save`, `pano_cli simulate-app-session`, `pano_cli plan-document-session-prompt`, `DocumentSaveServices`, `CloseRequestServices`, `DocumentWorkflowServices`, and `src/legacy_document_session_services.*`; Save dialog working-directory picker visibility/path formatting now dispatches through `PlatformServices`, but the bridge still opens retained `NodeMessageBox`/save dialogs, wires callbacks directly, calls `Canvas::I->project_save`, mutates the unsaved flag on close confirmation, invokes native app close, and routes save-version through the retained legacy dialog | Preserve current close/save/dirty-workflow behavior while document session execution moves toward app/document/UI/platform services | `pp_app_core_document_session_tests`; `pp_platform_api_tests`; `pano_cli plan-document-session-prompt --kind close-unsaved`; `pano_cli plan-document-session-prompt --kind save-before-workflow`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `ctest --preset desktop-fast --build-config Debug` | Close prompt execution, native close requests, dirty-workflow save prompts, existing-project saves, save dialogs, save-version execution, and unsaved-flag mutation are owned by injected app/document/UI/platform services with `App` methods acting only as adapters | | DEBT-0041 | Open | Modernization | Accepted new-document planning/execution dispatch and new-document overwrite prompt metadata now consume pure `pp_app_core` through `App::dialog_newdoc`, `pano_cli plan-new-document`, `pano_cli plan-document-session-prompt`, `NewDocumentServices`, and `src/legacy_document_session_services.*`; New Document dialog working-directory picker visibility/path formatting now dispatches through `PlatformServices`, but the bridge still mutates legacy app document fields, clears legacy layer UI, resizes legacy `Canvas`, clears legacy history, creates the default layer through legacy UI, mutates unsaved/new-document flags, updates the title, creates retained `NodeMessageBox` overwrite prompts, and handles keyboard/dialog cleanup directly | Preserve current New Document dialog behavior while document creation moves toward app/document/UI services | `pp_app_core_document_session_tests`; `pp_platform_api_tests`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-session-prompt --kind new-document-overwrite`; `pano_cli simulate-app-session --save-intent save`; `ctest --preset desktop-fast --build-config Debug` | New document creation, overwrite confirmation, canvas/document allocation, default layer creation, history clearing, title updates, dirty/new-document state, and keyboard/dialog cleanup are owned by injected app/document/UI services with `App::dialog_newdoc` acting only as a UI adapter | | DEBT-0042 | Open | Modernization | Accepted Save As and Save Version planning/execution dispatch plus Save As overwrite prompt metadata now consumes pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-document-session-prompt`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`, but the bridge still creates retained `NodeMessageBox` overwrite prompts, calls legacy `Canvas::project_save`, mutates app document name/path/directory fields, marks version saves dirty before saving, updates the title, and handles keyboard/dialog cleanup directly | Preserve current Save As and Save Version behavior while document persistence moves toward app/document/storage/UI services | `pp_app_core_document_session_tests`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-session-prompt --kind file-overwrite --name demo`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli simulate-app-session --save-intent save-as`; `pano_cli simulate-app-session --save-intent save-version`; `ctest --preset desktop-fast --build-config Debug` | Save As overwrite prompting, project-save execution, app document metadata updates, title updates, version-save dirty-state handling, and keyboard/dialog cleanup are owned by injected app/document/storage/UI services with `App::dialog_save` and `App::dialog_save_ver` acting only as UI adapters | -| DEBT-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`; layer/frame dialogs also consume `plan_document_export_collection_target` plus `PlatformServices::uses_work_directory_document_export_collections()` instead of spelling local iOS branches, and export success dialog metadata now comes from `plan_document_export_success_dialog`/`pano_cli plan-export-message`, but the bridge still calls legacy `Canvas` export methods, creates export directories, handles picker-selected stems, and performs Web prepared-file handoff directly | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pp_platform_api_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli plan-export-message --kind equirectangular --destination work --detail D:/Paint`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, Web file handoff, picker-selected stem handling, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters | -| 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`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, and success-message metadata now comes from `plan_document_export_success_dialog`, 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`; `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-0043 | Open | Modernization | Equirectangular, layer, animation-frame, depth, and cube-face export planning/execution dispatch now consumes pure `pp_app_core` through `App::dialog_export`, `App::dialog_export_layers`, `App::dialog_export_anim_frames`, `App::dialog_export_depth`, `App::dialog_export_cube_faces`, `pano_cli plan-export-*`, `DocumentExportServices`, and `src/legacy_document_export_services.*`; layer/frame dialogs also consume `plan_document_export_collection_target` plus `PlatformServices::uses_work_directory_document_export_collections()` instead of spelling local iOS branches, and export success/failure/license dialog metadata plus execution log labels now come from `pp_app_core`, but the bridge still calls legacy `Canvas` export methods, creates export directories, handles picker-selected stems, and performs Web prepared-file handoff directly | Preserve current image/collection/depth/cube export behavior while export execution moves toward document/renderer/platform/storage services | `pp_app_core_document_export_tests`; `pp_platform_api_tests`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-menu --kind layers`; `pano_cli plan-export-target --kind collection --work-dir D:/Paint --doc-name demo --suffix _layers`; `pano_cli plan-export-message --kind equirectangular --destination work --detail D:/Paint`; `pano_cli plan-export-report --kind license-disabled`; `pano_cli simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, Web file handoff, picker-selected stem handling, and legacy canvas export calls are owned by injected document/renderer/platform/storage services with export dialogs acting only as UI adapters | +| 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 | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index f1c598a..57aea7b 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -523,6 +523,9 @@ canvas/recording export execution. metadata now consumed by the live legacy export bridge for equirectangular, layer/frame, depth/cube, animation MP4, and timelapse success reporting, including platform-style destinations and no-message/suppressed branches. +`pano_cli plan-export-report` exposes app-core failure and license-disabled +dialog metadata now consumed by live export dialogs before retained legacy +export execution/logging continues. `pano_cli plan-recording-session` exposes the app-core recording start, stop, clear, platform recorded-file cleanup, frame reset, and export progress-total decisions used by the live recording controls. Recording lifecycle and MP4 @@ -662,6 +665,9 @@ before legacy export dialogs and renderer/video execution continue. Export success-message metadata now also comes from `pp_app_core` through `pano_cli plan-export-message` and the legacy document-export bridge, reducing the bridge to export execution, platform handoff, and retained threading. +Export failure/license dialog metadata now comes from `pp_app_core` through +`pano_cli plan-export-report`, with legacy `App::dialog_export*` only showing +the planned dialog and dispatching retained export calls. `pano_cli plan-grid-operation` exposes app-core planning for grid heightmap pick/load/reload/clear, lightmap render capability/limit checks, and heightmap commit used by the live grid panel. Grid execution now dispatches through diff --git a/src/app_core/document_export.h b/src/app_core/document_export.h index eae0c46..7805cf5 100644 --- a/src/app_core/document_export.h +++ b/src/app_core/document_export.h @@ -92,6 +92,18 @@ enum class DocumentExportSuccessDestination { generic_success, }; +enum class DocumentExportExecutionKind { + equirectangular_file, + layers_collection, + layers_stem, + animation_frames_collection, + animation_frames_stem, + depth, + cube_faces, + animation_mp4, + timelapse, +}; + struct DocumentExportMenuPlan { DocumentExportMenuKind kind = DocumentExportMenuKind::jpeg; DocumentExportMenuAction action = DocumentExportMenuAction::show_jpeg_dialog; @@ -271,6 +283,68 @@ public: return {}; } +[[nodiscard]] constexpr DocumentExportSuccessKind document_export_collection_success_kind( + DocumentExportCollectionKind kind) noexcept +{ + switch (kind) { + case DocumentExportCollectionKind::layers: + return DocumentExportSuccessKind::layers; + case DocumentExportCollectionKind::animation_frames: + return DocumentExportSuccessKind::animation_frames; + } + + return DocumentExportSuccessKind::layers; +} + +[[nodiscard]] constexpr const char* document_export_failure_dialog_title( + DocumentExportSuccessKind kind) noexcept +{ + switch (kind) { + case DocumentExportSuccessKind::equirectangular: + return "Export Equirectangular"; + case DocumentExportSuccessKind::layers: + case DocumentExportSuccessKind::animation_frames: + return "Export Layers"; + case DocumentExportSuccessKind::depth: + return "Export 3D View + Depth"; + case DocumentExportSuccessKind::cube_faces: + return "Export Cube Faces"; + case DocumentExportSuccessKind::animation_mp4: + return "Export Animation"; + case DocumentExportSuccessKind::timelapse: + return "Export Timelapse"; + } + + return "Export"; +} + +[[nodiscard]] constexpr const char* document_export_execution_log_message( + DocumentExportExecutionKind kind) noexcept +{ + switch (kind) { + case DocumentExportExecutionKind::equirectangular_file: + return "Document export file action failed"; + case DocumentExportExecutionKind::layers_collection: + return "Document layer collection export failed"; + case DocumentExportExecutionKind::layers_stem: + return "Document layer stem export failed"; + case DocumentExportExecutionKind::animation_frames_collection: + return "Document animation frame collection export failed"; + case DocumentExportExecutionKind::animation_frames_stem: + return "Document animation frame stem export failed"; + case DocumentExportExecutionKind::depth: + return "Document depth export failed"; + case DocumentExportExecutionKind::cube_faces: + return "Document cube-face export failed"; + case DocumentExportExecutionKind::animation_mp4: + return "Document animation export failed"; + case DocumentExportExecutionKind::timelapse: + return "Document timelapse export failed"; + } + + return "Document export failed"; +} + [[nodiscard]] constexpr DocumentExportCollectionTargetPlan plan_document_export_collection_target( DocumentExportCollectionKind kind, bool use_work_directory_collection) noexcept @@ -284,6 +358,24 @@ public: }; } +[[nodiscard]] inline AppMessageDialogPlan plan_document_export_failure_dialog( + DocumentExportSuccessKind kind, + std::string_view status_message) +{ + return plan_app_message_dialog( + document_export_failure_dialog_title(kind), + status_message, + false); +} + +[[nodiscard]] inline AppMessageDialogPlan plan_document_export_license_disabled_dialog() +{ + return plan_app_message_dialog( + "License", + "This function is disabled in demo mode.", + false); +} + [[nodiscard]] inline DocumentExportSuccessDialogPlan plan_document_export_success_dialog( DocumentExportSuccessKind kind, DocumentExportSuccessDestination destination, diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp index aff990d..f791456 100644 --- a/src/app_dialogs.cpp +++ b/src/app_dialogs.cpp @@ -45,8 +45,11 @@ namespace { case pp::app::DocumentExportStartDecision::start_now: return true; case pp::app::DocumentExportStartDecision::show_license_disabled: - app.message_box("License", "This function is disabled in demo mode."); + { + const auto plan = pp::app::plan_document_export_license_disabled_dialog(); + app.message_box(plan.title, plan.message, plan.show_cancel); return false; + } case pp::app::DocumentExportStartDecision::unavailable_no_canvas: return false; } @@ -56,14 +59,12 @@ namespace { void start_document_export_collection( App& app, - pp::app::DocumentExportCollectionKind kind, - const char* message_title, - const char* collection_log_message, - const char* stem_log_message) + pp::app::DocumentExportCollectionKind kind) { const auto plan = pp::app::plan_document_export_collection_target( kind, app.uses_work_directory_document_export_collections()); + const auto success_kind = pp::app::document_export_collection_success_kind(kind); if (plan.destination == pp::app::DocumentExportCollectionDestination::work_directory_collection) { const auto target = pp::app::make_document_export_collection_target( @@ -71,7 +72,8 @@ void start_document_export_collection( app.doc_name, plan.suffix); if (!target) { - app.message_box(message_title, target.status().message); + const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message); + app.message_box(dialog.title, dialog.message, dialog.show_cancel); return; } @@ -80,19 +82,25 @@ void start_document_export_collection( plan.kind, target.value()); if (!status.ok()) - LOG("%s: %s", collection_log_message, status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + kind == pp::app::DocumentExportCollectionKind::layers + ? pp::app::DocumentExportExecutionKind::layers_collection + : pp::app::DocumentExportExecutionKind::animation_frames_collection), + status.message); return; } app.pick_dir([ &app, kind = plan.kind, - title = std::string(message_title), - log_message = std::string(stem_log_message) + success_kind ](std::string path) { const auto target = pp::app::make_document_export_stem_target(path, app.doc_name); if (!target) { - app.message_box(title, target.status().message); + const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message); + app.message_box(dialog.title, dialog.message, dialog.show_cancel); return; } @@ -101,7 +109,13 @@ void start_document_export_collection( kind, target.value()); if (!status.ok()) - LOG("%s: %s", log_message.c_str(), status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + kind == pp::app::DocumentExportCollectionKind::layers + ? pp::app::DocumentExportExecutionKind::layers_stem + : pp::app::DocumentExportExecutionKind::animation_frames_stem), + status.message); }); } @@ -409,13 +423,20 @@ void App::dialog_export(std::string ext) // TODO: use picker const auto target = pp::app::make_document_export_file_target(work_path, doc_name, ext); if (!target) { - message_box("Export Equirectangular", target.status().message); + const auto dialog = pp::app::plan_document_export_failure_dialog( + pp::app::DocumentExportSuccessKind::equirectangular, + target.status().message); + message_box(dialog.title, dialog.message, dialog.show_cancel); return; } const auto status = pp::panopainter::execute_legacy_document_export_file(*this, target.value()); if (!status.ok()) - LOG("Document export file action failed: %s", status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::equirectangular_file), + status.message); } void App::dialog_export_layers() @@ -425,10 +446,7 @@ void App::dialog_export_layers() start_document_export_collection( *this, - pp::app::DocumentExportCollectionKind::layers, - "Export Layers", - "Document layer collection export failed", - "Document layer stem export failed"); + pp::app::DocumentExportCollectionKind::layers); } void App::dialog_export_anim_frames() @@ -438,10 +456,7 @@ void App::dialog_export_anim_frames() start_document_export_collection( *this, - pp::app::DocumentExportCollectionKind::animation_frames, - "Export Layers", - "Document animation frame collection export failed", - "Document animation frame stem export failed"); + pp::app::DocumentExportCollectionKind::animation_frames); } void App::dialog_export_depth() @@ -451,7 +466,10 @@ void App::dialog_export_depth() const auto status = pp::panopainter::execute_legacy_document_export_depth(*this, doc_name); if (!status.ok()) - LOG("Document depth export failed: %s", status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::depth), + status.message); } void App::dialog_resize() @@ -487,7 +505,10 @@ void App::dialog_export_cube_faces() const auto status = pp::panopainter::execute_legacy_document_export_cube_faces(*this, doc_name); if (!status.ok()) - LOG("Document cube-face export failed: %s", status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::cube_faces), + status.message); } void App::dialog_layer_rename() @@ -575,7 +596,10 @@ void App::dialog_timelapse_export() { const auto target = pp::app::make_document_export_suggested_name(doc_name, "-timelapse"); if (!target) { - message_box("Export Timelapse", target.status().message); + const auto dialog = pp::app::plan_document_export_failure_dialog( + pp::app::DocumentExportSuccessKind::timelapse, + target.status().message); + message_box(dialog.title, dialog.message, dialog.show_cancel); return; } @@ -587,7 +611,11 @@ void App::dialog_timelapse_export() path, false); if (!status.ok()) - LOG("Document timelapse export failed: %s", status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::timelapse), + status.message); }, [](const std::string& path, bool saved) { (void)path; @@ -604,7 +632,10 @@ void App::dialog_timelapse_export() path, true); if (!status.ok()) - LOG("Document timelapse export failed: %s", status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::timelapse), + status.message); }); } @@ -617,7 +648,10 @@ void App::dialog_export_mp4() { const auto target = pp::app::make_document_export_suggested_name(doc_name, "-animation"); if (!target) { - message_box("Export Animation", target.status().message); + const auto dialog = pp::app::plan_document_export_failure_dialog( + pp::app::DocumentExportSuccessKind::animation_mp4, + target.status().message); + message_box(dialog.title, dialog.message, dialog.show_cancel); return; } @@ -629,7 +663,11 @@ void App::dialog_export_mp4() path, false); if (!status.ok()) - LOG("Document animation export failed: %s", status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::animation_mp4), + status.message); }, [](const std::string& path, bool saved) { (void)path; @@ -646,7 +684,10 @@ void App::dialog_export_mp4() path, true); if (!status.ok()) - LOG("Document animation export failed: %s", status.message); + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::animation_mp4), + status.message); }); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b4a4092..41f7811 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -930,6 +930,25 @@ if(TARGET pano_cli) WILL_FAIL TRUE PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-message\".*\"message\":\"unknown export message kind\"") + add_test(NAME pano_cli_plan_export_report_failure_smoke + COMMAND pano_cli plan-export-report --kind animation-mp4 --message "video export path must not be empty") + set_tests_properties(pano_cli_plan_export_report_failure_smoke PROPERTIES + LABELS "app;integration;desktop-fast;fuzz" + PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-report\".*\"kind\":\"animation-mp4\".*\"title\":\"Export Animation\".*\"message\":\"video export path must not be empty\".*\"showCancel\":false") + + add_test(NAME pano_cli_plan_export_report_license_smoke + COMMAND pano_cli plan-export-report --kind license-disabled) + set_tests_properties(pano_cli_plan_export_report_license_smoke PROPERTIES + LABELS "app;integration;desktop-fast" + PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-report\".*\"kind\":\"license-disabled\".*\"title\":\"License\".*\"message\":\"This function is disabled in demo mode\\.\".*\"showCancel\":false") + + add_test(NAME pano_cli_plan_export_report_rejects_unknown + COMMAND pano_cli plan-export-report --kind nope) + set_tests_properties(pano_cli_plan_export_report_rejects_unknown PROPERTIES + LABELS "app;integration;desktop-fast;fuzz" + WILL_FAIL TRUE + PASS_REGULAR_EXPRESSION "\"command\":\"plan-export-report\".*\"message\":\"unknown export message kind\"") + add_test(NAME pano_cli_plan_cloud_upload_clean_smoke COMMAND pano_cli plan-cloud-upload) set_tests_properties(pano_cli_plan_cloud_upload_clean_smoke PROPERTIES diff --git a/tests/app_core/document_export_tests.cpp b/tests/app_core/document_export_tests.cpp index cae9952..da68047 100644 --- a/tests/app_core/document_export_tests.cpp +++ b/tests/app_core/document_export_tests.cpp @@ -327,6 +327,64 @@ void export_success_dialog_suppresses_unsupported_destinations(pp::tests::Harnes PP_EXPECT(harness, invalid_combo.dialog.message.empty()); } +void export_failure_dialog_plans_titles_and_messages(pp::tests::Harness& harness) +{ + const auto equirect = pp::app::plan_document_export_failure_dialog( + pp::app::DocumentExportSuccessKind::equirectangular, + "document name must not be empty"); + const auto frames = pp::app::plan_document_export_failure_dialog( + pp::app::DocumentExportSuccessKind::animation_frames, + "document name must not be empty"); + const auto animation = pp::app::plan_document_export_failure_dialog( + pp::app::DocumentExportSuccessKind::animation_mp4, + "video export path must not be empty"); + + PP_EXPECT(harness, equirect.title == "Export Equirectangular"); + PP_EXPECT(harness, equirect.message == "document name must not be empty"); + PP_EXPECT(harness, !equirect.show_cancel); + PP_EXPECT(harness, frames.title == "Export Layers"); + PP_EXPECT(harness, animation.title == "Export Animation"); + PP_EXPECT(harness, animation.message == "video export path must not be empty"); +} + +void export_license_disabled_dialog_preserves_legacy_warning(pp::tests::Harness& harness) +{ + const auto plan = pp::app::plan_document_export_license_disabled_dialog(); + + PP_EXPECT(harness, plan.title == "License"); + PP_EXPECT(harness, plan.message == "This function is disabled in demo mode."); + PP_EXPECT(harness, !plan.show_cancel); +} + +void export_execution_log_messages_cover_legacy_paths(pp::tests::Harness& harness) +{ + PP_EXPECT( + harness, + std::string_view(pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::equirectangular_file)) + == "Document export file action failed"); + PP_EXPECT( + harness, + std::string_view(pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::layers_collection)) + == "Document layer collection export failed"); + PP_EXPECT( + harness, + std::string_view(pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::animation_frames_stem)) + == "Document animation frame stem export failed"); + PP_EXPECT( + harness, + std::string_view(pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::cube_faces)) + == "Document cube-face export failed"); + PP_EXPECT( + harness, + std::string_view(pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::timelapse)) + == "Document timelapse export failed"); +} + void export_start_allows_valid_canvas_state(pp::tests::Harness& harness) { PP_EXPECT( @@ -666,6 +724,9 @@ int main() harness.run("export success dialog plans depth and cube destinations", export_success_dialog_plans_depth_and_cube_destinations); harness.run("export success dialog plans video destinations", export_success_dialog_plans_video_destinations); harness.run("export success dialog suppresses unsupported destinations", export_success_dialog_suppresses_unsupported_destinations); + harness.run("export failure dialog plans titles and messages", export_failure_dialog_plans_titles_and_messages); + harness.run("export license disabled dialog preserves legacy warning", export_license_disabled_dialog_preserves_legacy_warning); + harness.run("export execution log messages cover legacy paths", export_execution_log_messages_cover_legacy_paths); harness.run("export start allows valid canvas state", export_start_allows_valid_canvas_state); harness.run("export start blocks demo only when license required", export_start_blocks_demo_only_when_license_required); harness.run("export start reports missing canvas after license gate", export_start_reports_missing_canvas_after_license_gate); diff --git a/tools/pano_cli/main.cpp b/tools/pano_cli/main.cpp index 863f103..3664ed3 100644 --- a/tools/pano_cli/main.cpp +++ b/tools/pano_cli/main.cpp @@ -174,6 +174,11 @@ struct PlanExportMessageArgs { std::string detail = "D:/Paint"; }; +struct PlanExportReportArgs { + std::string kind = "equirectangular"; + std::string message = "document name must not be empty"; +}; + struct PlanExportStartArgs { bool requires_license = false; bool license_valid = true; @@ -2358,6 +2363,7 @@ void print_help() << " plan-export-menu --kind jpeg|png|layers|cube-faces|depth|animation-frames|animation-mp4|timelapse [--demo] [--no-canvas]\n" << " plan-export-target --kind file|collection|stem|name --doc-name NAME [--work-dir DIR] [--directory DIR] [--extension EXT] [--suffix SUFFIX]\n" << " plan-export-message --kind equirectangular|layers|animation-frames|depth|cube-faces|animation-mp4|timelapse --destination photos|pictures|files|work|path|success|suppressed [--detail TEXT]\n" + << " plan-export-report --kind license-disabled|equirectangular|layers|animation-frames|depth|cube-faces|animation-mp4|timelapse [--message TEXT]\n" << " plan-cloud-upload [--no-canvas] [--new-document] [--unsaved]\n" << " plan-cloud-browse [--no-canvas] [--selected-file FILE]\n" << " plan-cloud-upload-all [--file-count N] [--no-progress-ui]\n" @@ -3780,6 +3786,62 @@ int plan_export_message(int argc, char** argv) return 0; } +pp::foundation::Status parse_plan_export_report_args( + int argc, + char** argv, + PlanExportReportArgs& args) +{ + for (int i = 2; i < argc; ++i) { + const std::string_view key(argv[i]); + if (key == "--kind") { + if (i + 1 >= argc) { + return pp::foundation::Status::invalid_argument("missing value for option"); + } + args.kind = argv[++i]; + } else if (key == "--message") { + if (i + 1 >= argc) { + return pp::foundation::Status::invalid_argument("missing value for option"); + } + args.message = argv[++i]; + } else { + return pp::foundation::Status::invalid_argument("unknown option"); + } + } + + return pp::foundation::Status::success(); +} + +int plan_export_report(int argc, char** argv) +{ + PlanExportReportArgs args; + const auto status = parse_plan_export_report_args(argc, argv, args); + if (!status.ok()) { + print_error("plan-export-report", status.message); + return 2; + } + + pp::app::AppMessageDialogPlan dialog; + if (args.kind == "license-disabled") { + dialog = pp::app::plan_document_export_license_disabled_dialog(); + } else { + const auto kind = parse_document_export_success_kind(args.kind); + if (!kind) { + print_error("plan-export-report", kind.status().message); + return 2; + } + dialog = pp::app::plan_document_export_failure_dialog(kind.value(), args.message); + } + + std::cout << "{\"ok\":true,\"command\":\"plan-export-report\"" + << ",\"state\":{\"kind\":\"" << json_escape(args.kind) + << "\",\"message\":\"" << json_escape(args.message) + << "\"},\"dialog\":{\"title\":\"" << json_escape(dialog.title) + << "\",\"message\":\"" << json_escape(dialog.message) + << "\",\"showCancel\":" << json_bool(dialog.show_cancel) + << "}}\n"; + return 0; +} + pp::foundation::Status parse_plan_cloud_upload_args( int argc, char** argv, @@ -11383,6 +11445,10 @@ int main(int argc, char** argv) return plan_export_message(argc, argv); } + if (command == "plan-export-report") { + return plan_export_report(argc, argv); + } + if (command == "plan-cloud-upload") { return plan_cloud_upload(argc, argv); }