Plan document session prompts

This commit is contained in:
2026-06-05 08:07:54 +02:00
parent 5def47cdcc
commit e5526c6d0a
11 changed files with 316 additions and 26 deletions

View File

@@ -837,7 +837,7 @@ Known local toolchain state:
options-menu side effects. options-menu side effects.
- `pp_app_core_app_dialog_tests` covers app-level progress/message/input dialog - `pp_app_core_app_dialog_tests` covers app-level progress/message/input dialog
metadata planning, progress initialization, negative progress-total clamping, metadata planning, progress initialization, negative progress-total clamping,
message cancel-button policy, input OK-caption propagation, and malformed message cancel-button/caption policy, input OK-caption propagation, and malformed
empty OK-caption rejection without requiring legacy `Node*` dialogs. empty OK-caption rejection without requiring legacy `Node*` dialogs.
- `pp_app_core_app_startup_tests` covers startup run-counter increment - `pp_app_core_app_startup_tests` covers startup run-counter increment
planning, optional auto-timelapse/license/VR-controller decisions, negative planning, optional auto-timelapse/license/VR-controller decisions, negative
@@ -881,7 +881,9 @@ Known local toolchain state:
- `pp_app_core_document_session_tests` covers clean and dirty app session, - `pp_app_core_document_session_tests` covers clean and dirty app session,
document-open action planning and executor dispatch/rejection, save-request, document-open action planning and executor dispatch/rejection, save-request,
close-request executor dispatch/no-op preservation, document-save executor close-request executor dispatch/no-op preservation, document-save executor
dispatch/no-op preservation, save-before-workflow executor dispatch, dispatch/no-op preservation, document-session prompt metadata for close,
save-before-workflow, overwrite, and save-error dialogs,
save-before-workflow executor dispatch,
new-document target/resolution/overwrite planning and executor dispatch, new-document target/resolution/overwrite planning and executor dispatch,
document file target, combined save-file overwrite planning and executor document file target, combined save-file overwrite planning and executor
dispatch, plus save-version target decisions and executor validation without dispatch, plus save-version target decisions and executor validation without
@@ -903,9 +905,13 @@ Known local toolchain state:
- `src/legacy_document_session_services.*` is the current app-shell bridge - `src/legacy_document_session_services.*` is the current app-shell bridge
between `pp_app_core` document-session decisions and live close prompts, between `pp_app_core` document-session decisions and live close prompts,
save dialogs, save-version routing, existing-project saves, and save dialogs, save-version routing, existing-project saves, and
save-before-workflow prompts. It also bridges accepted new-document plans to save-before-workflow prompts. Close, save-before-workflow, new-document
legacy canvas resize/layer setup, overwrite prompts, title updates, and overwrite, Save As overwrite, and save-error prompt text/captions now come
keyboard/dialog cleanup. Accepted Save As and Save Version plans now also from the pure document-session prompt catalog exposed through
`pano_cli plan-document-session-prompt` before retained `NodeMessageBox`
creation. It also bridges accepted new-document plans to legacy canvas
resize/layer setup, overwrite prompts, title updates, and keyboard/dialog
cleanup. Accepted Save As and Save Version plans now also
route through this bridge before reaching legacy project-save execution, route through this bridge before reaching legacy project-save execution,
overwrite prompts, document field updates, title updates, and keyboard/dialog overwrite prompts, document field updates, title updates, and keyboard/dialog
cleanup. Retained legacy UI/canvas execution remains tracked by `DEBT-0040`, cleanup. Retained legacy UI/canvas execution remains tracked by `DEBT-0040`,

View File

@@ -99,6 +99,14 @@ agent or engineer to remove them without reconstructing context from chat.
the retained OpenGL startup task in place, then delegates startup resources the retained OpenGL startup task in place, then delegates startup resources
and runtime side effects through the startup bridge. `pano_cli and runtime side effects through the startup bridge. `pano_cli
plan-app-startup-resources` exposes the resource path for automation. plan-app-startup-resources` exposes the resource path for automation.
- 2026-06-05: DEBT-0040/0041/0042 were narrowed. Close-unsaved,
save-before-workflow, new-document overwrite, Save As overwrite, and
save-error prompt metadata now comes from a tested pure document-session
prompt catalog consumed by `src/legacy_document_session_services.*`, and
`pano_cli plan-document-session-prompt` exposes the titles, messages,
captions, and cancel visibility for automation. Retained `NodeMessageBox`
creation, callback wiring, project-save execution, and canvas/document
mutation remain open under the same debts.
- 2026-06-05: DEBT-0003 was narrowed. Initial surface sizing, redraw/animation - 2026-06-05: DEBT-0003 was narrowed. Initial surface sizing, redraw/animation
update gating, layout tick selection, resize render-target recreation, update gating, layout tick selection, resize render-target recreation,
canvas-stroke draw eligibility, VR UI pass selection, main UI pass selection, canvas-stroke draw eligibility, VR UI pass selection, main UI pass selection,
@@ -211,9 +219,9 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, and retained `PBO` allocation/readback/map/unmap/delete operations now route through tested `pp_renderer_gl` dispatch, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, retained `App::rec_loop` readback call sites, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pp_renderer_gl_capabilities_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback scheduling, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters | | DEBT-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, and retained `PBO` allocation/readback/map/unmap/delete operations now route through tested `pp_renderer_gl` dispatch, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, retained `App::rec_loop` readback call sites, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pp_renderer_gl_capabilities_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback scheduling, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters |
| DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, the app-owned `upload`/`download` CURL helpers now consume `pp_app_core` cloud transfer request/progress planning and the platform TLS-verification bypass policy before retained CURL setup, `pano_cli plan-cloud-transfer` exposes the same missing endpoint, TLS policy, progress-callback, and progress fraction guards, and retained `Asset::open_url`, `LogRemote::net_init`, and `NodeDialogCloud::load_thumbs_thread` curl sites consume the `pp_platform_api` default TLS policy helper instead of spelling Android branches locally, but the bridge still uses legacy save-before-upload, app-owned curl helpers instead of an injected network service, upload form construction, response/error handling, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pp_platform_api_tests`; `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 plan-cloud-transfer --direction download --progress --disable-tls-verification`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, TLS policy, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters | | DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, the app-owned `upload`/`download` CURL helpers now consume `pp_app_core` cloud transfer request/progress planning and the platform TLS-verification bypass policy before retained CURL setup, `pano_cli plan-cloud-transfer` exposes the same missing endpoint, TLS policy, progress-callback, and progress fraction guards, and retained `Asset::open_url`, `LogRemote::net_init`, and `NodeDialogCloud::load_thumbs_thread` curl sites consume the `pp_platform_api` default TLS policy helper instead of spelling Android branches locally, but the bridge still uses legacy save-before-upload, app-owned curl helpers instead of an injected network service, upload form construction, response/error handling, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pp_platform_api_tests`; `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 plan-cloud-transfer --direction download --progress --disable-tls-verification`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, TLS policy, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters |
| DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`, but the bridge still opens ABR/PPBR import prompts before delegating import execution to `src/legacy_brush_package_import_services.*`, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and clears legacy history directly | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter | | DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`, but the bridge still opens ABR/PPBR import prompts before delegating import execution to `src/legacy_brush_package_import_services.*`, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and clears legacy history directly | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter |
| DEBT-0040 | Open | Modernization | Close request, document save, and save-before-workflow planning/execution dispatch 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`, `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 legacy message boxes/save dialogs, 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 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-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 now consumes pure `pp_app_core` through `App::dialog_newdoc`, `pano_cli plan-new-document`, `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, 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 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-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 now consumes pure `pp_app_core` through `App::dialog_save`, `App::dialog_save_ver`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `DocumentFileSaveServices`, `DocumentVersionSaveServices`, and `src/legacy_document_session_services.*`, but the bridge still opens legacy 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-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-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, but the bridge still calls legacy `Canvas` export methods, owns platform-specific export success messages, 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 simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, platform success reporting, 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-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, but the bridge still calls legacy `Canvas` export methods, owns platform-specific export success messages, 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 simulate-document-export`; `ctest --preset desktop-fast --build-config Debug` | File, collection, stem, depth, and cube export execution, export-directory creation, platform success reporting, 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`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, owns mobile/Web save callbacks, and emits success messages directly | 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`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, mobile/Web save callbacks, and success reporting are owned by injected app/document/renderer/video/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`, `DocumentVideoExportServices`, and `src/legacy_document_export_services.*`, but the bridge still launches legacy desktop timelapse worker threads, calls `App::rec_export`, calls `Canvas::export_anim_mp4`, owns mobile/Web save callbacks, and emits success messages directly | 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`; `ctest --preset desktop-fast --build-config Debug` | Timelapse and animation MP4 execution, desktop worker threading, frame readback/video encoding handoff, mobile/Web save callbacks, and success reporting 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-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 |
@@ -229,7 +237,7 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0055 | Open | Modernization | `src/app.h` now forward-declares retained iOS/macOS/Android/Linux/Web platform handles instead of including platform SDK headers, and full SDK includes are isolated in `src/platform_legacy/legacy_platform_services.cpp`, but the `App` singleton still stores those platform handles directly | Reduce central header platform coupling incrementally without rewriting non-Windows platform entrypoints before Phase 6 | Windows app build; Apple/Android/Linux/Web package smoke once platform root builds are active | Platform handles are owned by injected `pp_platform_*` shell state or services, and `App` has no platform SDK handle fields or platform conditional members | | DEBT-0055 | Open | Modernization | `src/app.h` now forward-declares retained iOS/macOS/Android/Linux/Web platform handles instead of including platform SDK headers, and full SDK includes are isolated in `src/platform_legacy/legacy_platform_services.cpp`, but the `App` singleton still stores those platform handles directly | Reduce central header platform coupling incrementally without rewriting non-Windows platform entrypoints before Phase 6 | Windows app build; Apple/Android/Linux/Web package smoke once platform root builds are active | Platform handles are owned by injected `pp_platform_*` shell state or services, and `App` has no platform SDK handle fields or platform conditional members |
| DEBT-0056 | Open | Modernization | `src/asset.h` now forward-declares Android asset-manager types and uses `Asset::set_android_asset_manager` instead of public mutable manager state, but retained `Asset` still stores Android asset handles and `src/asset.cpp` still performs Android `AAssetManager` reads directly; the current `android-arm64` root preset is headless and does not expose `pp_legacy_assets_io` | Reduce legacy asset I/O header coupling without rewriting Android asset loading before the asset manager/storage boundary exists | Windows app build; `cmake --build --preset android-arm64 --target pp_assets`; Android package smoke once package builds consume shared targets | Android asset loading is owned by injected asset storage/platform services or `pp_assets` file providers, with no static Android asset manager on `Asset` | | DEBT-0056 | Open | Modernization | `src/asset.h` now forward-declares Android asset-manager types and uses `Asset::set_android_asset_manager` instead of public mutable manager state, but retained `Asset` still stores Android asset handles and `src/asset.cpp` still performs Android `AAssetManager` reads directly; the current `android-arm64` root preset is headless and does not expose `pp_legacy_assets_io` | Reduce legacy asset I/O header coupling without rewriting Android asset loading before the asset manager/storage boundary exists | Windows app build; `cmake --build --preset android-arm64 --target pp_assets`; Android package smoke once package builds consume shared targets | Android asset loading is owned by injected asset storage/platform services or `pp_assets` file providers, with no static Android asset manager on `Asset` |
| DEBT-0057 | Open | Modernization | Default canvas allocation size now dispatches through `PlatformServices::default_canvas_resolution`, removing the `CANVAS_RES` platform macro from `src/canvas.h`, but WebGL's retained 512 default still lives in `src/platform_legacy/legacy_platform_services.cpp` until the Web shell owns injected services | Preserve WebGL memory behavior while moving canvas creation policy out of shared canvas headers and into the platform boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build; WebGL package smoke once root Web build exists | Default canvas resolution is owned by injected `pp_platform_*` services for every supported platform, with no WebGL branch in the legacy fallback | | DEBT-0057 | Open | Modernization | Default canvas allocation size now dispatches through `PlatformServices::default_canvas_resolution`, removing the `CANVAS_RES` platform macro from `src/canvas.h`, but WebGL's retained 512 default still lives in `src/platform_legacy/legacy_platform_services.cpp` until the Web shell owns injected services | Preserve WebGL memory behavior while moving canvas creation policy out of shared canvas headers and into the platform boundary | `pp_platform_api_tests`; `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests`; Windows app build; WebGL package smoke once root Web build exists | Default canvas resolution is owned by injected `pp_platform_*` services for every supported platform, with no WebGL branch in the legacy fallback |
| DEBT-0058 | Open | Modernization | App-level progress/message/input dialog metadata now consumes pure `pp_app_core` through `App::show_progress`, `App::message_box`, `App::input_box`, `pano_cli plan-app-dialog`, and `pp_app_core_app_dialog_tests`, but live execution still creates retained `NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` instances directly in `src/app_dialogs.cpp` and inserts them into the legacy layout tree | Preserve current app-shell dialog behavior while moving shared dialog policy toward UI/app services | `pp_app_core_app_dialog_tests`; `pano_cli plan-app-dialog --kind progress --total -4`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-dialog --kind input --ok-caption Save`; `ctest --preset desktop-fast --build-config Debug`; Windows app build | Progress/message/input dialog creation, callback wiring, layout insertion, lifetime ownership, and headless automation are owned by injected app/UI services with `App` methods acting only as adapters | | DEBT-0058 | Open | Modernization | App-level progress/message/input dialog metadata, including message-dialog OK/cancel captions, now consumes pure `pp_app_core` through `App::show_progress`, `App::message_box`, `App::input_box`, `pano_cli plan-app-dialog`, and `pp_app_core_app_dialog_tests`, but live execution still creates retained `NodeProgressBar`, `NodeMessageBox`, and `NodeInputBox` instances directly in `src/app_dialogs.cpp` and inserts them into the legacy layout tree | Preserve current app-shell dialog behavior while moving shared dialog policy toward UI/app services | `pp_app_core_app_dialog_tests`; `pano_cli plan-app-dialog --kind progress --total -4`; `pano_cli plan-app-dialog --kind message --cancel`; `pano_cli plan-app-dialog --kind input --ok-caption Save`; `ctest --preset desktop-fast --build-config Debug`; Windows app build | Progress/message/input dialog creation, callback wiring, layout insertion, lifetime ownership, and headless automation are owned by injected app/UI services with `App` methods acting only as adapters |
## Closed Debt ## Closed Debt

View File

@@ -992,6 +992,10 @@ app-core document-session executors and `src/legacy_document_session_services.*`
preserving close prompts, save dialogs, save-version routing, existing-project preserving close prompts, save dialogs, save-version routing, existing-project
save execution, and dirty-workflow save-before-continue prompts while retained save execution, and dirty-workflow save-before-continue prompts while retained
legacy UI/canvas behavior remains tracked under `DEBT-0040`. legacy UI/canvas behavior remains tracked under `DEBT-0040`.
The retained document-session prompt boxes now consume a pure prompt catalog for
close-unsaved, save-before-workflow, new-document overwrite, Save As overwrite,
and save-error metadata; `pano_cli plan-document-session-prompt` exposes the
same titles, messages, button captions, and cancel visibility for automation.
`App::dialog_newdoc` now routes accepted new-document plans through the `App::dialog_newdoc` now routes accepted new-document plans through the
app-core new-document executor and `src/legacy_document_session_services.*`, app-core new-document executor and `src/legacy_document_session_services.*`,
preserving target overwrite prompts, legacy canvas resize/layer setup, history preserving target overwrite prompts, legacy canvas resize/layer setup, history
@@ -1682,6 +1686,20 @@ Results:
`pp_app_core_document_session_tests`, `pano_cli_plan_document_file_*`, `pp_app_core_document_session_tests`, `pano_cli_plan_document_file_*`,
`pano_cli_plan_document_version_*`, and `pano_cli_simulate_app_session_*` `pano_cli_plan_document_version_*`, and `pano_cli_simulate_app_session_*`
smoke tests after the live bridge split. smoke tests after the live bridge split.
- `PanoPainter`, `pano_cli`, `pp_app_core_app_dialog_tests`, and
`pp_app_core_document_session_tests` built after close/save/workflow,
new-document overwrite, Save As overwrite, and save-error prompt metadata
moved into the pure document-session prompt catalog. A clean rebuild was
required once because MSVC reported the known Debug PDB `LNK1103`
corruption, after which the build passed.
- Focused document-session prompt CTest coverage passed for
`pp_app_core_app_dialog_tests`, `pp_app_core_document_session_tests`,
`pano_cli_plan_document_session_prompt_*`,
`pano_cli_plan_document_file_*`, `pano_cli_plan_new_document_*`, and
representative `pano_cli_simulate_app_session_*` smoke tests.
- Android arm64 headless `pp_app_core`, `pano_cli`,
`pp_app_core_app_dialog_tests`, and `pp_app_core_document_session_tests`
built after the same prompt-catalog change.
- `PanoPainter`, `pp_app_core_document_export_tests`, and `pano_cli` built - `PanoPainter`, `pp_app_core_document_export_tests`, and `pano_cli` built
after equirectangular, layers, animation-frame, depth, and cube-face export after equirectangular, layers, animation-frame, depth, and cube-face export
execution moved behind document export services. A clean rebuild was required execution moved behind document export services. A clean rebuild was required

View File

@@ -24,6 +24,7 @@ struct AppMessageDialogPlan {
std::string title; std::string title;
std::string message; std::string message;
std::string ok_caption = "Ok"; std::string ok_caption = "Ok";
std::string cancel_caption = "Cancel";
bool show_cancel = false; bool show_cancel = false;
}; };
@@ -48,12 +49,15 @@ struct AppInputDialogPlan {
[[nodiscard]] inline AppMessageDialogPlan plan_app_message_dialog( [[nodiscard]] inline AppMessageDialogPlan plan_app_message_dialog(
std::string_view title, std::string_view title,
std::string_view message, std::string_view message,
bool show_cancel) bool show_cancel,
std::string_view ok_caption = "Ok",
std::string_view cancel_caption = "Cancel")
{ {
return { return {
std::string(title), std::string(title),
std::string(message), std::string(message),
"Ok", std::string(ok_caption),
std::string(cancel_caption),
show_cancel, show_cancel,
}; };
} }

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "app_core/app_dialog.h"
#include "app_core/document_route.h" #include "app_core/document_route.h"
#include "foundation/result.h" #include "foundation/result.h"
@@ -55,6 +56,14 @@ enum class DocumentOpenPlanAction {
prompt_import_ppbr, prompt_import_ppbr,
}; };
enum class DocumentSessionPromptKind {
close_unsaved_document,
save_before_workflow_continue,
new_document_overwrite,
document_file_overwrite,
document_save_error,
};
class DocumentOpenServices { class DocumentOpenServices {
public: public:
virtual ~DocumentOpenServices() = default; virtual ~DocumentOpenServices() = default;
@@ -135,6 +144,47 @@ public:
virtual void prompt_overwrite_new_document(const NewDocumentPlan& plan) = 0; virtual void prompt_overwrite_new_document(const NewDocumentPlan& plan) = 0;
}; };
[[nodiscard]] inline AppMessageDialogPlan plan_document_session_prompt(
DocumentSessionPromptKind kind,
std::string_view document_name = {})
{
switch (kind) {
case DocumentSessionPromptKind::close_unsaved_document:
return plan_app_message_dialog(
"Unsaved document",
"Do you want to close without saving?",
true,
"Yes",
"No");
case DocumentSessionPromptKind::save_before_workflow_continue:
return plan_app_message_dialog(
"Unsaved document",
"Would you like to save this document before closing?",
true,
"Yes",
"No");
case DocumentSessionPromptKind::new_document_overwrite:
return plan_app_message_dialog(
"Warning",
"A document with this name already exists, continue?",
true);
case DocumentSessionPromptKind::document_file_overwrite:
{
std::string message = "Are you sure you want to overwrite ";
message += document_name;
message += "?";
return plan_app_message_dialog("Warning", message, true);
}
case DocumentSessionPromptKind::document_save_error:
return plan_app_message_dialog(
"Saving Error",
"There was a problem saving the document",
false);
}
return plan_app_message_dialog("Warning", "", false);
}
[[nodiscard]] constexpr ProjectOpenDecision plan_project_open(bool has_unsaved_changes) noexcept [[nodiscard]] constexpr ProjectOpenDecision plan_project_open(bool has_unsaved_changes) noexcept
{ {
return has_unsaved_changes return has_unsaved_changes

View File

@@ -134,7 +134,9 @@ std::shared_ptr<NodeMessageBox> App::message_box(const std::string &title, const
m->m_title->set_text(plan.title.c_str()); m->m_title->set_text(plan.title.c_str());
m->m_message->set_text(plan.message.c_str()); m->m_message->set_text(plan.message.c_str());
m->btn_ok->m_text->set_text(plan.ok_caption.c_str()); m->btn_ok->m_text->set_text(plan.ok_caption.c_str());
if (!plan.show_cancel) if (plan.show_cancel)
m->btn_cancel->m_text->set_text(plan.cancel_caption.c_str());
else
m->btn_cancel->destroy(); m->btn_cancel->destroy();
layout[main_id]->add_child(m); layout[main_id]->add_child(m);
return m; return m;

View File

@@ -12,6 +12,20 @@
namespace pp::panopainter { namespace pp::panopainter {
namespace { namespace {
void apply_legacy_message_box_plan(
NodeMessageBox& msgbox,
const pp::app::AppMessageDialogPlan& plan)
{
msgbox.m_title->set_text(plan.title.c_str());
msgbox.m_message->set_text(plan.message.c_str());
msgbox.btn_ok->m_text->set_text(plan.ok_caption.c_str());
if (plan.show_cancel) {
msgbox.btn_cancel->m_text->set_text(plan.cancel_caption.c_str());
} else {
msgbox.btn_cancel->destroy();
}
}
void create_legacy_new_document( void create_legacy_new_document(
App& app, App& app,
const pp::app::NewDocumentPlan& plan, const pp::app::NewDocumentPlan& plan,
@@ -58,8 +72,10 @@ public:
auto msgbox = new NodeMessageBox(); auto msgbox = new NodeMessageBox();
msgbox->set_manager(&app_.layout); msgbox->set_manager(&app_.layout);
msgbox->init(); msgbox->init();
msgbox->m_title->set_text("Warning"); apply_legacy_message_box_plan(
msgbox->m_message->set_text("A document with this name already exists, continue?"); *msgbox,
pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::new_document_overwrite));
auto* app = &app_; auto* app = &app_;
auto dialog = dialog_; auto dialog = dialog_;
msgbox->btn_ok->on_click = [app, msgbox, dialog, plan](Node*) { msgbox->btn_ok->on_click = [app, msgbox, dialog, plan](Node*) {
@@ -106,8 +122,11 @@ public:
auto msgbox = new NodeMessageBox(); auto msgbox = new NodeMessageBox();
msgbox->set_manager(&app_.layout); msgbox->set_manager(&app_.layout);
msgbox->init(); msgbox->init();
msgbox->m_title->set_text("Warning"); apply_legacy_message_box_plan(
msgbox->m_message->set_text(("Are you sure you want to overwrite " + plan.target.name + "?").c_str()); *msgbox,
pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::document_file_overwrite,
plan.target.name));
auto* app = &app_; auto* app = &app_;
auto dialog = dialog_; auto dialog = dialog_;
msgbox->btn_ok->on_click = [app, msgbox, dialog, plan](Node*) { msgbox->btn_ok->on_click = [app, msgbox, dialog, plan](Node*) {
@@ -159,14 +178,14 @@ public:
auto* app = &app_; auto* app = &app_;
auto* dialog_already_opened = &dialog_already_opened_; auto* dialog_already_opened = &dialog_already_opened_;
auto* m = app_.layout[app_.main_id]->add_child<NodeMessageBox>(); auto* m = app_.layout[app_.main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document"); apply_legacy_message_box_plan(
m->m_message->set_text("Do you want to close without saving?"); *m,
m->btn_ok->m_text->set_text("Yes"); pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::close_unsaved_document));
m->btn_ok->on_click = [app](Node*) { m->btn_ok->on_click = [app](Node*) {
app->request_app_close(); app->request_app_close();
Canvas::I->m_unsaved = false; Canvas::I->m_unsaved = false;
}; };
m->btn_cancel->m_text->set_text("No");
m->btn_cancel->on_click = [dialog_already_opened, m](Node*) { m->btn_cancel->on_click = [dialog_already_opened, m](Node*) {
m->destroy(); m->destroy();
*dialog_already_opened = false; *dialog_already_opened = false;
@@ -221,18 +240,21 @@ public:
void prompt_save_before_continue() override void prompt_save_before_continue() override
{ {
auto m = app_.layout[app_.main_id]->add_child<NodeMessageBox>(); auto m = app_.layout[app_.main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document"); apply_legacy_message_box_plan(
m->m_message->set_text("Would you like to save this document before closing?"); *m,
m->btn_ok->m_text->set_text("Yes"); pp::app::plan_document_session_prompt(
m->btn_cancel->m_text->set_text("No"); pp::app::DocumentSessionPromptKind::save_before_workflow_continue));
auto* app = &app_; auto* app = &app_;
auto action = action_; auto action = action_;
m->btn_ok->on_click = [app, m, action](Node*) { m->btn_ok->on_click = [app, m, action](Node*) {
Canvas::I->project_save([app, m, action](bool success) { Canvas::I->project_save([app, m, action](bool success) {
if (success) if (success)
action(); action();
else else {
app->message_box("Saving Error", "There was a problem saving the document"); const auto plan = pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::document_save_error);
app->message_box(plan.title, plan.message, plan.show_cancel);
}
}); });
m->destroy(); m->destroy();
}; };

View File

@@ -820,6 +820,25 @@ if(TARGET pano_cli)
LABELS "app;integration;desktop-fast" LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-version\".*\"documentName\":\"demo.01\".*\"existingPaths\":1.*\"name\":\"demo.03\".*\"path\":\"D:/Paint/demo.03.ppi\"") PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-version\".*\"documentName\":\"demo.01\".*\"existingPaths\":1.*\"name\":\"demo.03\".*\"path\":\"D:/Paint/demo.03.ppi\"")
add_test(NAME pano_cli_plan_document_session_prompt_close_smoke
COMMAND pano_cli plan-document-session-prompt --kind close-unsaved)
set_tests_properties(pano_cli_plan_document_session_prompt_close_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-session-prompt\".*\"kind\":\"close-unsaved\".*\"title\":\"Unsaved document\".*\"message\":\"Do you want to close without saving\\?\".*\"okCaption\":\"Yes\".*\"cancelCaption\":\"No\".*\"showCancel\":true")
add_test(NAME pano_cli_plan_document_session_prompt_overwrite_smoke
COMMAND pano_cli plan-document-session-prompt --kind file-overwrite --name demo)
set_tests_properties(pano_cli_plan_document_session_prompt_overwrite_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-session-prompt\".*\"kind\":\"file-overwrite\".*\"message\":\"Are you sure you want to overwrite demo\\?\".*\"showCancel\":true")
add_test(NAME pano_cli_plan_document_session_prompt_rejects_unknown
COMMAND pano_cli plan-document-session-prompt --kind nope)
set_tests_properties(pano_cli_plan_document_session_prompt_rejects_unknown PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
WILL_FAIL TRUE
PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-session-prompt\".*\"message\":\"unknown document session prompt kind\"")
add_test(NAME pano_cli_plan_export_start_allowed_smoke add_test(NAME pano_cli_plan_export_start_allowed_smoke
COMMAND pano_cli plan-export-start --requires-license) COMMAND pano_cli plan-export-start --requires-license)
set_tests_properties(pano_cli_plan_export_start_allowed_smoke PROPERTIES set_tests_properties(pano_cli_plan_export_start_allowed_smoke PROPERTIES

View File

@@ -26,6 +26,7 @@ void message_dialog_preserves_cancel_policy(pp::tests::Harness& harness)
PP_EXPECT(harness, plan.title == "Import"); PP_EXPECT(harness, plan.title == "Import");
PP_EXPECT(harness, plan.message == "Import brushes?"); PP_EXPECT(harness, plan.message == "Import brushes?");
PP_EXPECT(harness, plan.ok_caption == "Ok"); PP_EXPECT(harness, plan.ok_caption == "Ok");
PP_EXPECT(harness, plan.cancel_caption == "Cancel");
PP_EXPECT(harness, plan.show_cancel); PP_EXPECT(harness, plan.show_cancel);
} }
@@ -37,6 +38,14 @@ void message_dialog_defaults_to_no_cancel(pp::tests::Harness& harness)
PP_EXPECT(harness, !plan.show_cancel); PP_EXPECT(harness, !plan.show_cancel);
} }
void message_dialog_allows_custom_button_captions(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_app_message_dialog("Unsaved", "Save first?", true, "Yes", "No");
PP_EXPECT(harness, plan.ok_caption == "Yes");
PP_EXPECT(harness, plan.cancel_caption == "No");
PP_EXPECT(harness, plan.show_cancel);
}
void input_dialog_preserves_ok_caption(pp::tests::Harness& harness) void input_dialog_preserves_ok_caption(pp::tests::Harness& harness)
{ {
const auto plan = pp::app::plan_app_input_dialog("Rename", "Layer", "Save"); const auto plan = pp::app::plan_app_input_dialog("Rename", "Layer", "Save");
@@ -62,6 +71,7 @@ int main()
harness.run("progress dialog clamps negative total", progress_dialog_clamps_negative_total); harness.run("progress dialog clamps negative total", progress_dialog_clamps_negative_total);
harness.run("message dialog preserves cancel policy", message_dialog_preserves_cancel_policy); harness.run("message dialog preserves cancel policy", message_dialog_preserves_cancel_policy);
harness.run("message dialog defaults to no cancel", message_dialog_defaults_to_no_cancel); harness.run("message dialog defaults to no cancel", message_dialog_defaults_to_no_cancel);
harness.run("message dialog allows custom button captions", message_dialog_allows_custom_button_captions);
harness.run("input dialog preserves ok caption", input_dialog_preserves_ok_caption); harness.run("input dialog preserves ok caption", input_dialog_preserves_ok_caption);
harness.run("input dialog rejects empty ok caption", input_dialog_rejects_empty_ok_caption); harness.run("input dialog rejects empty ok caption", input_dialog_rejects_empty_ok_caption);
return harness.finish(); return harness.finish();

View File

@@ -358,6 +358,43 @@ void close_request_executor_dispatches_and_preserves_wait(pp::tests::Harness& ha
PP_EXPECT(harness, services.call_order == "close;prompt;"); PP_EXPECT(harness, services.call_order == "close;prompt;");
} }
void document_session_prompt_catalog_preserves_legacy_close_and_workflow_text(pp::tests::Harness& harness)
{
const auto close = pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::close_unsaved_document);
const auto workflow = pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::save_before_workflow_continue);
PP_EXPECT(harness, close.title == "Unsaved document");
PP_EXPECT(harness, close.message == "Do you want to close without saving?");
PP_EXPECT(harness, close.ok_caption == "Yes");
PP_EXPECT(harness, close.cancel_caption == "No");
PP_EXPECT(harness, close.show_cancel);
PP_EXPECT(harness, workflow.message == "Would you like to save this document before closing?");
PP_EXPECT(harness, workflow.ok_caption == "Yes");
PP_EXPECT(harness, workflow.cancel_caption == "No");
}
void document_session_prompt_catalog_preserves_overwrite_and_error_text(pp::tests::Harness& harness)
{
const auto new_doc = pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::new_document_overwrite);
const auto overwrite = pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::document_file_overwrite,
"demo");
const auto save_error = pp::app::plan_document_session_prompt(
pp::app::DocumentSessionPromptKind::document_save_error);
PP_EXPECT(harness, new_doc.title == "Warning");
PP_EXPECT(harness, new_doc.message == "A document with this name already exists, continue?");
PP_EXPECT(harness, new_doc.show_cancel);
PP_EXPECT(harness, overwrite.message == "Are you sure you want to overwrite demo?");
PP_EXPECT(harness, overwrite.show_cancel);
PP_EXPECT(harness, save_error.title == "Saving Error");
PP_EXPECT(harness, save_error.message == "There was a problem saving the document");
PP_EXPECT(harness, !save_error.show_cancel);
}
void save_clean_existing_document_is_no_op(pp::tests::Harness& harness) void save_clean_existing_document_is_no_op(pp::tests::Harness& harness)
{ {
PP_EXPECT( PP_EXPECT(
@@ -757,6 +794,12 @@ int main()
harness.run("close clean document executes immediately", close_clean_document_executes_immediately); harness.run("close clean document executes immediately", close_clean_document_executes_immediately);
harness.run("close dirty document opens one prompt", close_dirty_document_opens_one_prompt); harness.run("close dirty document opens one prompt", close_dirty_document_opens_one_prompt);
harness.run("close request executor dispatches and preserves wait", close_request_executor_dispatches_and_preserves_wait); harness.run("close request executor dispatches and preserves wait", close_request_executor_dispatches_and_preserves_wait);
harness.run(
"document session prompt catalog preserves legacy close and workflow text",
document_session_prompt_catalog_preserves_legacy_close_and_workflow_text);
harness.run(
"document session prompt catalog preserves overwrite and error text",
document_session_prompt_catalog_preserves_overwrite_and_error_text);
harness.run("save clean existing document is no op", save_clean_existing_document_is_no_op); harness.run("save clean existing document is no op", save_clean_existing_document_is_no_op);
harness.run("save executor dispatches visible work and no ops cleanly", save_executor_dispatches_visible_work_and_no_ops_cleanly); harness.run("save executor dispatches visible work and no ops cleanly", save_executor_dispatches_visible_work_and_no_ops_cleanly);
harness.run("save new or dirty document has user visible work", save_new_or_dirty_document_has_user_visible_work); harness.run("save new or dirty document has user visible work", save_new_or_dirty_document_has_user_visible_work);

View File

@@ -154,6 +154,11 @@ struct PlanDocumentVersionArgs {
std::vector<std::string> existing_paths; std::vector<std::string> existing_paths;
}; };
struct PlanDocumentSessionPromptArgs {
pp::app::DocumentSessionPromptKind kind = pp::app::DocumentSessionPromptKind::close_unsaved_document;
std::string name = "demo";
};
struct PlanExportTargetArgs { struct PlanExportTargetArgs {
std::string kind; std::string kind;
std::string work_directory; std::string work_directory;
@@ -915,6 +920,52 @@ const char* document_workflow_decision_name(pp::app::DocumentWorkflowDecision de
return "unavailable"; return "unavailable";
} }
const char* document_session_prompt_kind_name(pp::app::DocumentSessionPromptKind kind) noexcept
{
switch (kind) {
case pp::app::DocumentSessionPromptKind::close_unsaved_document:
return "close-unsaved";
case pp::app::DocumentSessionPromptKind::save_before_workflow_continue:
return "save-before-workflow";
case pp::app::DocumentSessionPromptKind::new_document_overwrite:
return "new-document-overwrite";
case pp::app::DocumentSessionPromptKind::document_file_overwrite:
return "file-overwrite";
case pp::app::DocumentSessionPromptKind::document_save_error:
return "save-error";
}
return "close-unsaved";
}
pp::foundation::Result<pp::app::DocumentSessionPromptKind> parse_document_session_prompt_kind(
std::string_view kind)
{
if (kind == "close-unsaved") {
return pp::foundation::Result<pp::app::DocumentSessionPromptKind>::success(
pp::app::DocumentSessionPromptKind::close_unsaved_document);
}
if (kind == "save-before-workflow") {
return pp::foundation::Result<pp::app::DocumentSessionPromptKind>::success(
pp::app::DocumentSessionPromptKind::save_before_workflow_continue);
}
if (kind == "new-document-overwrite") {
return pp::foundation::Result<pp::app::DocumentSessionPromptKind>::success(
pp::app::DocumentSessionPromptKind::new_document_overwrite);
}
if (kind == "file-overwrite") {
return pp::foundation::Result<pp::app::DocumentSessionPromptKind>::success(
pp::app::DocumentSessionPromptKind::document_file_overwrite);
}
if (kind == "save-error") {
return pp::foundation::Result<pp::app::DocumentSessionPromptKind>::success(
pp::app::DocumentSessionPromptKind::document_save_error);
}
return pp::foundation::Result<pp::app::DocumentSessionPromptKind>::failure(
pp::foundation::Status::invalid_argument("unknown document session prompt kind"));
}
const char* file_menu_command_name(pp::app::FileMenuCommand command) noexcept const char* file_menu_command_name(pp::app::FileMenuCommand command) noexcept
{ {
switch (command) { switch (command) {
@@ -2179,6 +2230,7 @@ void print_help()
<< " plan-new-document --work-dir DIR --name NAME [--resolution-index N] [--target-exists]\n" << " plan-new-document --work-dir DIR --name NAME [--resolution-index N] [--target-exists]\n"
<< " plan-document-file --work-dir DIR --name NAME [--target-exists]\n" << " plan-document-file --work-dir DIR --name NAME [--target-exists]\n"
<< " plan-document-version --directory DIR --doc-name NAME [--existing-path FILE]\n" << " plan-document-version --directory DIR --doc-name NAME [--existing-path FILE]\n"
<< " plan-document-session-prompt --kind close-unsaved|save-before-workflow|new-document-overwrite|file-overwrite|save-error [--name NAME]\n"
<< " plan-export-start [--requires-license] [--demo] [--no-canvas]\n" << " plan-export-start [--requires-license] [--demo] [--no-canvas]\n"
<< " plan-export-menu --kind jpeg|png|layers|cube-faces|depth|animation-frames|animation-mp4|timelapse [--demo] [--no-canvas]\n" << " 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-target --kind file|collection|stem|name --doc-name NAME [--work-dir DIR] [--directory DIR] [--extension EXT] [--suffix SUFFIX]\n"
@@ -3388,6 +3440,57 @@ int plan_document_version(int argc, char** argv)
return 0; return 0;
} }
pp::foundation::Status parse_plan_document_session_prompt_args(
int argc,
char** argv,
PlanDocumentSessionPromptArgs& 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");
}
const auto kind = parse_document_session_prompt_kind(argv[++i]);
if (!kind) {
return kind.status();
}
args.kind = kind.value();
} else if (key == "--name") {
if (i + 1 >= argc) {
return pp::foundation::Status::invalid_argument("missing value for option");
}
args.name = argv[++i];
} else {
return pp::foundation::Status::invalid_argument("unknown option");
}
}
return pp::foundation::Status::success();
}
int plan_document_session_prompt(int argc, char** argv)
{
PlanDocumentSessionPromptArgs args;
const auto status = parse_plan_document_session_prompt_args(argc, argv, args);
if (!status.ok()) {
print_error("plan-document-session-prompt", status.message);
return 2;
}
const auto plan = pp::app::plan_document_session_prompt(args.kind, args.name);
std::cout << "{\"ok\":true,\"command\":\"plan-document-session-prompt\""
<< ",\"kind\":\"" << document_session_prompt_kind_name(args.kind)
<< "\",\"name\":\"" << json_escape(args.name)
<< "\",\"plan\":{\"title\":\"" << json_escape(plan.title)
<< "\",\"message\":\"" << json_escape(plan.message)
<< "\",\"okCaption\":\"" << json_escape(plan.ok_caption)
<< "\",\"cancelCaption\":\"" << json_escape(plan.cancel_caption)
<< "\",\"showCancel\":" << json_bool(plan.show_cancel)
<< "}}\n";
return 0;
}
pp::foundation::Status parse_plan_export_start_args( pp::foundation::Status parse_plan_export_start_args(
int argc, int argc,
char** argv, char** argv,
@@ -3806,6 +3909,7 @@ int plan_app_dialog(int argc, char** argv)
<< "\",\"plan\":{\"title\":\"" << json_escape(plan.title) << "\",\"plan\":{\"title\":\"" << json_escape(plan.title)
<< "\",\"message\":\"" << json_escape(plan.message) << "\",\"message\":\"" << json_escape(plan.message)
<< "\",\"okCaption\":\"" << json_escape(plan.ok_caption) << "\",\"okCaption\":\"" << json_escape(plan.ok_caption)
<< "\",\"cancelCaption\":\"" << json_escape(plan.cancel_caption)
<< "\",\"showCancel\":" << json_bool(plan.show_cancel) << "\",\"showCancel\":" << json_bool(plan.show_cancel)
<< "}}\n"; << "}}\n";
return 0; return 0;
@@ -11035,6 +11139,10 @@ int main(int argc, char** argv)
return plan_document_version(argc, argv); return plan_document_version(argc, argv);
} }
if (command == "plan-document-session-prompt") {
return plan_document_session_prompt(argc, argv);
}
if (command == "plan-export-start") { if (command == "plan-export-start") {
return plan_export_start(argc, argv); return plan_export_start(argc, argv);
} }