Route document workflow prompts through app core

This commit is contained in:
2026-06-02 22:36:05 +02:00
parent d28aa25358
commit c8d769c02c
10 changed files with 130 additions and 96 deletions

View File

@@ -395,15 +395,16 @@ Known local toolchain state:
contract as JSON and is covered for project files, ABR imports, PPBR contract as JSON and is covered for project files, ABR imports, PPBR
imports, and malformed path rejection. imports, and malformed path rejection.
- `pano_cli simulate-app-session` exposes `pp_app_core` project-open, - `pano_cli simulate-app-session` exposes `pp_app_core` project-open,
app-close, save, save-as, and save-version decisions as JSON and is covered app-close, save, save-as, save-version, and save-before-workflow decisions
for clean, dirty, already-prompting, new-document, save-as, save-version, and as JSON and is covered for clean, dirty, already-prompting, missing-canvas,
dirty-save-version states. new-document, save-as, save-version, and dirty-save-version states.
- `pp_app_core_document_route_tests` covers the app document-open route - `pp_app_core_document_route_tests` covers the app document-open route
contract for PPI/project files, ABR imports, PPBR imports, inner-dot names, contract for PPI/project files, ABR imports, PPBR imports, inner-dot names,
and malformed paths before the live `App::open_document` performs UI or and malformed paths before the live `App::open_document` performs UI or
legacy canvas work. legacy canvas work.
- `pp_app_core_document_session_tests` covers clean and dirty app session plus - `pp_app_core_document_session_tests` covers clean and dirty app session plus
save-request decisions without requiring a window, canvas, or message box. save-request and save-before-workflow decisions without requiring a window,
canvas, or message box.
- `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON` - `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON`
through the vcpkg preset; default and Android validation still use the through the vcpkg preset; default and Android validation still use the
retained vendored fallback tracked by DEBT-0012. retained vendored fallback tracked by DEBT-0012.

View File

@@ -1,7 +1,7 @@
# PanoPainter Capability Map # PanoPainter Capability Map
Status: live Status: live
Last updated: 2026-05-31 Last updated: 2026-06-02
This map is the preservation checklist for the modernization. When a component This map is the preservation checklist for the modernization. When a component
is extracted, update the relevant rows with the owning component, test label, is extracted, update the relevant rows with the owning component, test label,
@@ -13,7 +13,7 @@ and validation command.
| --- | --- | --- | --- | | --- | --- | --- | --- |
| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture | | PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture |
| Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, CLI route smoke, app open smoke | | Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, CLI route smoke, app open smoke |
| Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version decision tests, CLI session smoke, app close/open/save smoke | | Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow decision tests, CLI session smoke, app close/open/save/new/browse smoke |
| Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior | | Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior |
| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input | | Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input |
| Save-as, overwrite prompts | App/dialogs | `pp_app_core`, `pp_panopainter_ui`, `pp_platform_*` | Decision tests, UI automation, and platform smoke | | Save-as, overwrite prompts | App/dialogs | `pp_app_core`, `pp_panopainter_ui`, `pp_platform_*` | Decision tests, UI automation, and platform smoke |

View File

@@ -22,7 +22,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-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-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`, file-menu save actions, `NodeCanvas` save hotkeys, `pano_cli classify-open`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session contracts, but document loading and saving still reach legacy `Canvas::I` and UI singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `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`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, `pano_cli classify-open`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session contracts, but document loading and saving still reach legacy `Canvas::I` and UI singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `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-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-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 | | 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 |

View File

@@ -425,7 +425,8 @@ rejection smoke test for unsafe tiny canvas dimensions.
contract for project files, ABR imports, PPBR imports, and malformed path contract for project files, ABR imports, PPBR imports, and malformed path
rejection. `pano_cli simulate-app-session` exposes the pure `pp_app_core` rejection. `pano_cli simulate-app-session` exposes the pure `pp_app_core`
session decisions used by project-open, app-close, save, save-as, and session decisions used by project-open, app-close, save, save-as, and
save-version flows. save-version flows, plus the save-before-continue workflow gate used by
new-document/open/browse dialogs.
`pano_cli parse-layout` exercises the XML layout path. Continue expanding `pano_cli parse-layout` exercises the XML layout path. Continue expanding
document behavior toward legacy Canvas parity and then port OpenGL classes document behavior toward legacy Canvas parity and then port OpenGL classes
behind the renderer boundary. behind the renderer boundary.

View File

@@ -250,6 +250,7 @@ public:
void dialog_changelog(); void dialog_changelog();
void dialog_about(); void dialog_about();
void save_document(pp::app::DocumentSaveIntent intent); void save_document(pp::app::DocumentSaveIntent intent);
void continue_document_workflow_after_optional_save(std::function<void()> action);
void dialog_newdoc(); void dialog_newdoc();
void dialog_save(); void dialog_save();
void dialog_save_ver(); void dialog_save_ver();

View File

@@ -27,6 +27,12 @@ enum class DocumentSaveDecision {
save_version, save_version,
}; };
enum class DocumentWorkflowDecision {
unavailable,
continue_now,
prompt_save_before_continue,
};
[[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
@@ -78,4 +84,17 @@ enum class DocumentSaveDecision {
return DocumentSaveDecision::no_op; return DocumentSaveDecision::no_op;
} }
[[nodiscard]] constexpr DocumentWorkflowDecision plan_document_workflow(
bool has_canvas,
bool has_unsaved_changes) noexcept
{
if (!has_canvas) {
return DocumentWorkflowDecision::unavailable;
}
return has_unsaved_changes
? DocumentWorkflowDecision::prompt_save_before_continue
: DocumentWorkflowDecision::continue_now;
}
} }

View File

@@ -105,6 +105,41 @@ void App::dialog_about()
layout[main_id]->add_child(dialog); layout[main_id]->add_child(dialog);
} }
void App::continue_document_workflow_after_optional_save(std::function<void()> action)
{
const bool has_canvas = canvas != nullptr;
const bool has_unsaved_changes = has_canvas && Canvas::I->m_unsaved;
const auto decision = pp::app::plan_document_workflow(has_canvas, has_unsaved_changes);
switch (decision) {
case pp::app::DocumentWorkflowDecision::unavailable:
return;
case pp::app::DocumentWorkflowDecision::continue_now:
action();
return;
case pp::app::DocumentWorkflowDecision::prompt_save_before_continue:
break;
}
auto m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Would you like to save this document before closing?");
m->btn_ok->m_text->set_text("Yes");
m->btn_cancel->m_text->set_text("No");
m->btn_ok->on_click = [this, m, action](Node*) {
Canvas::I->project_save([this, m, action](bool success) {
if (success)
action();
else
message_box("Saving Error", "There was a problem saving the document");
});
m->destroy();
};
m->btn_cancel->on_click = [m, action](Node*) {
action();
m->destroy();
};
}
void App::dialog_newdoc() void App::dialog_newdoc()
{ {
auto show_dialog = [this] { auto show_dialog = [this] {
@@ -181,34 +216,7 @@ void App::dialog_newdoc()
}; };
}; };
if (canvas) continue_document_workflow_after_optional_save(show_dialog);
{
if (Canvas::I->m_unsaved)
{
auto m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Would you like to save this document before closing?");
m->btn_ok->m_text->set_text("Yes");
m->btn_cancel->m_text->set_text("No");
m->btn_ok->on_click = [this, m, show_dialog](Node*) {
Canvas::I->project_save([this, m, show_dialog](bool success){
if (success)
show_dialog();
else
message_box("Saving Error", "There was a problem saving the document");
});
m->destroy();
};
m->btn_cancel->on_click = [this, m, show_dialog](Node*) {
show_dialog();
m->destroy();
};
}
else
{
show_dialog();
}
}
} }
// DEPRECATED // DEPRECATED
@@ -242,34 +250,7 @@ void App::dialog_open()
}; };
}; };
if (canvas) continue_document_workflow_after_optional_save(show_dialog);
{
if (Canvas::I->m_unsaved)
{
auto m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Would you like to save this document before closing?");
m->btn_ok->m_text->set_text("Yes");
m->btn_cancel->m_text->set_text("No");
m->btn_ok->on_click = [this,m,show_dialog](Node*){
Canvas::I->project_save([this,m,show_dialog](bool success){
if (success)
show_dialog();
else
message_box("Saving Error", "There was a problem saving the document");
});
m->destroy();
};
m->btn_cancel->on_click = [this,m,show_dialog](Node*) {
show_dialog();
m->destroy();
};
}
else
{
show_dialog();
}
}
} }
void App::dialog_browse() void App::dialog_browse()
@@ -299,34 +280,7 @@ void App::dialog_browse()
}; };
}; };
if (canvas) continue_document_workflow_after_optional_save(show_dialog);
{
if (Canvas::I->m_unsaved)
{
auto m = layout[main_id]->add_child<NodeMessageBox>();
m->m_title->set_text("Unsaved document");
m->m_message->set_text("Would you like to save this document before closing?");
m->btn_ok->m_text->set_text("Yes");
m->btn_cancel->m_text->set_text("No");
m->btn_ok->on_click = [this, m, show_dialog](Node*) {
Canvas::I->project_save([this, m, show_dialog](bool success){
if (success)
show_dialog();
else
message_box("Saving Error", "There was a problem saving the document");
});
m->destroy();
};
m->btn_cancel->on_click = [this, m, show_dialog](Node*) {
show_dialog();
m->destroy();
};
}
else
{
show_dialog();
}
}
} }
void App::dialog_save_ver() void App::dialog_save_ver()

View File

@@ -375,13 +375,13 @@ if(TARGET pano_cli)
COMMAND pano_cli simulate-app-session) COMMAND pano_cli simulate-app-session)
set_tests_properties(pano_cli_simulate_app_session_clean_smoke PROPERTIES set_tests_properties(pano_cli_simulate_app_session_clean_smoke PROPERTIES
LABELS "app;integration;desktop-fast" LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"simulate-app-session\".*\"newDocument\":false.*\"unsaved\":false.*\"closePromptOpen\":false.*\"projectOpen\":\"open-now\".*\"closeRequest\":\"close-now\".*\"saveIntent\":\"save\".*\"saveRequest\":\"no-op\"") PASS_REGULAR_EXPRESSION "\"command\":\"simulate-app-session\".*\"hasCanvas\":true.*\"newDocument\":false.*\"unsaved\":false.*\"closePromptOpen\":false.*\"projectOpen\":\"open-now\".*\"closeRequest\":\"close-now\".*\"saveIntent\":\"save\".*\"saveRequest\":\"no-op\".*\"workflowStart\":\"continue-now\"")
add_test(NAME pano_cli_simulate_app_session_unsaved_smoke add_test(NAME pano_cli_simulate_app_session_unsaved_smoke
COMMAND pano_cli simulate-app-session --unsaved) COMMAND pano_cli simulate-app-session --unsaved)
set_tests_properties(pano_cli_simulate_app_session_unsaved_smoke PROPERTIES set_tests_properties(pano_cli_simulate_app_session_unsaved_smoke PROPERTIES
LABELS "app;integration;desktop-fast" LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"simulate-app-session\".*\"newDocument\":false.*\"unsaved\":true.*\"closePromptOpen\":false.*\"projectOpen\":\"prompt-discard-unsaved\".*\"closeRequest\":\"show-unsaved-prompt\".*\"saveRequest\":\"save-existing\"") PASS_REGULAR_EXPRESSION "\"command\":\"simulate-app-session\".*\"hasCanvas\":true.*\"newDocument\":false.*\"unsaved\":true.*\"closePromptOpen\":false.*\"projectOpen\":\"prompt-discard-unsaved\".*\"closeRequest\":\"show-unsaved-prompt\".*\"saveRequest\":\"save-existing\".*\"workflowStart\":\"prompt-save-before-continue\"")
add_test(NAME pano_cli_simulate_app_session_existing_prompt_smoke add_test(NAME pano_cli_simulate_app_session_existing_prompt_smoke
COMMAND pano_cli simulate-app-session --unsaved --close-prompt-open) COMMAND pano_cli simulate-app-session --unsaved --close-prompt-open)
@@ -413,6 +413,12 @@ if(TARGET pano_cli)
LABELS "app;integration;desktop-fast" LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"simulate-app-session\".*\"saveIntent\":\"save-dirty-version\".*\"saveRequest\":\"no-op\"") PASS_REGULAR_EXPRESSION "\"command\":\"simulate-app-session\".*\"saveIntent\":\"save-dirty-version\".*\"saveRequest\":\"no-op\"")
add_test(NAME pano_cli_simulate_app_session_no_canvas_smoke
COMMAND pano_cli simulate-app-session --no-canvas)
set_tests_properties(pano_cli_simulate_app_session_no_canvas_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"simulate-app-session\".*\"hasCanvas\":false.*\"workflowStart\":\"unavailable\"")
add_test(NAME pano_cli_save_project_roundtrip_smoke add_test(NAME pano_cli_save_project_roundtrip_smoke
COMMAND "${CMAKE_COMMAND}" COMMAND "${CMAKE_COMMAND}"
-DPANO_CLI=$<TARGET_FILE:pano_cli> -DPANO_CLI=$<TARGET_FILE:pano_cli>

View File

@@ -87,6 +87,34 @@ void save_version_respects_menu_and_hotkey_behaviors(pp::tests::Harness& harness
== pp::app::DocumentSaveDecision::show_save_dialog); == pp::app::DocumentSaveDecision::show_save_dialog);
} }
void workflow_without_canvas_is_unavailable(pp::tests::Harness& harness)
{
PP_EXPECT(
harness,
pp::app::plan_document_workflow(false, false)
== pp::app::DocumentWorkflowDecision::unavailable);
PP_EXPECT(
harness,
pp::app::plan_document_workflow(false, true)
== pp::app::DocumentWorkflowDecision::unavailable);
}
void workflow_with_clean_canvas_continues_now(pp::tests::Harness& harness)
{
PP_EXPECT(
harness,
pp::app::plan_document_workflow(true, false)
== pp::app::DocumentWorkflowDecision::continue_now);
}
void workflow_with_dirty_canvas_prompts_for_save(pp::tests::Harness& harness)
{
PP_EXPECT(
harness,
pp::app::plan_document_workflow(true, true)
== pp::app::DocumentWorkflowDecision::prompt_save_before_continue);
}
} }
int main() int main()
@@ -100,5 +128,8 @@ int main()
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);
harness.run("save as always shows save dialog", save_as_always_shows_save_dialog); harness.run("save as always shows save dialog", save_as_always_shows_save_dialog);
harness.run("save version respects menu and hotkey behaviors", save_version_respects_menu_and_hotkey_behaviors); harness.run("save version respects menu and hotkey behaviors", save_version_respects_menu_and_hotkey_behaviors);
harness.run("workflow without canvas is unavailable", workflow_without_canvas_is_unavailable);
harness.run("workflow with clean canvas continues now", workflow_with_clean_canvas_continues_now);
harness.run("workflow with dirty canvas prompts for save", workflow_with_dirty_canvas_prompts_for_save);
return harness.finish(); return harness.finish();
} }

View File

@@ -97,6 +97,7 @@ struct ClassifyOpenArgs {
}; };
struct SimulateAppSessionArgs { struct SimulateAppSessionArgs {
bool has_canvas = true;
bool new_document = false; bool new_document = false;
bool unsaved = false; bool unsaved = false;
bool close_prompt_open = false; bool close_prompt_open = false;
@@ -298,6 +299,20 @@ const char* document_save_decision_name(pp::app::DocumentSaveDecision decision)
return "no-op"; return "no-op";
} }
const char* document_workflow_decision_name(pp::app::DocumentWorkflowDecision decision) noexcept
{
switch (decision) {
case pp::app::DocumentWorkflowDecision::unavailable:
return "unavailable";
case pp::app::DocumentWorkflowDecision::continue_now:
return "continue-now";
case pp::app::DocumentWorkflowDecision::prompt_save_before_continue:
return "prompt-save-before-continue";
}
return "unavailable";
}
pp::foundation::Result<float> parse_float_arg(std::string_view text) pp::foundation::Result<float> parse_float_arg(std::string_view text)
{ {
float value = 0.0F; float value = 0.0F;
@@ -336,7 +351,7 @@ void print_help()
<< " simulate-document-edits [--width N] [--height N]\n" << " simulate-document-edits [--width N] [--height N]\n"
<< " simulate-document-export [--width N] [--height N]\n" << " simulate-document-export [--width N] [--height N]\n"
<< " simulate-document-history [--width N] [--height N] [--history N]\n" << " simulate-document-history [--width N] [--height N] [--history N]\n"
<< " simulate-app-session [--new-document] [--unsaved] [--close-prompt-open] [--save-intent save|save-as|save-version|save-dirty-version]\n" << " simulate-app-session [--no-canvas] [--new-document] [--unsaved] [--close-prompt-open] [--save-intent save|save-as|save-version|save-dirty-version]\n"
<< " simulate-blend\n" << " simulate-blend\n"
<< " simulate-image-import [--width N] [--height N]\n" << " simulate-image-import [--width N] [--height N]\n"
<< " simulate-stroke --x1 N --y1 N --x2 N --y2 N [--spacing N]\n" << " simulate-stroke --x1 N --y1 N --x2 N --y2 N [--spacing N]\n"
@@ -1178,6 +1193,8 @@ pp::foundation::Status parse_simulate_app_session_args(
const std::string_view key(argv[i]); const std::string_view key(argv[i]);
if (key == "--new-document") { if (key == "--new-document") {
args.new_document = true; args.new_document = true;
} else if (key == "--no-canvas") {
args.has_canvas = false;
} else if (key == "--unsaved") { } else if (key == "--unsaved") {
args.unsaved = true; args.unsaved = true;
} else if (key == "--close-prompt-open") { } else if (key == "--close-prompt-open") {
@@ -1221,8 +1238,10 @@ int simulate_app_session(int argc, char** argv)
args.new_document, args.new_document,
args.unsaved, args.unsaved,
args.save_intent); args.save_intent);
const auto workflow_decision = pp::app::plan_document_workflow(args.has_canvas, args.unsaved);
std::cout << "{\"ok\":true,\"command\":\"simulate-app-session\"" std::cout << "{\"ok\":true,\"command\":\"simulate-app-session\""
<< ",\"state\":{\"newDocument\":" << json_bool(args.new_document) << ",\"state\":{\"hasCanvas\":" << json_bool(args.has_canvas)
<< ",\"newDocument\":" << json_bool(args.new_document)
<< ",\"unsaved\":" << json_bool(args.unsaved) << ",\"unsaved\":" << json_bool(args.unsaved)
<< ",\"closePromptOpen\":" << json_bool(args.close_prompt_open) << ",\"closePromptOpen\":" << json_bool(args.close_prompt_open)
<< "},\"decisions\":{\"projectOpen\":\"" << "},\"decisions\":{\"projectOpen\":\""
@@ -1233,6 +1252,8 @@ int simulate_app_session(int argc, char** argv)
<< document_save_intent_name(args.save_intent) << document_save_intent_name(args.save_intent)
<< "\",\"saveRequest\":\"" << "\",\"saveRequest\":\""
<< document_save_decision_name(save_decision) << document_save_decision_name(save_decision)
<< "\",\"workflowStart\":\""
<< document_workflow_decision_name(workflow_decision)
<< "\"}}\n"; << "\"}}\n";
return 0; return 0;
} }