Add history command service boundary

This commit is contained in:
2026-06-03 13:22:16 +02:00
parent 6d0cc4eb15
commit 6427f218e7
7 changed files with 228 additions and 22 deletions

View File

@@ -43,7 +43,7 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0023 | Open | Modernization | Brush/color/preset UI planning now consumes pure `pp_app_core` through `App::init_sidebar`, restored/docked floating-panel callbacks, and `pano_cli plan-brush-operation`, but live execution still mutates legacy `Brush`, calls legacy brush texture loading, and refreshes legacy quick/stroke/color widgets directly | Preserve existing brush UI behavior while brush commands move toward a brush/app command boundary and asset-managed texture selection | `pp_app_core_brush_ui_tests`; `pano_cli plan-brush-operation --kind color --r 0.25 --g 0.5 --b 0.75 --a 1`; `pano_cli plan-brush-operation --kind pattern --path data/patterns/noise.png --thumb data/patterns/thumbs/noise.png`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset execution is owned by a brush/app command boundary with legacy `Brush`/UI nodes acting only as adapters or removed entirely |
| DEBT-0024 | Open | Modernization | Grid/heightmap/lightmap UI planning now consumes pure `pp_app_core` through `NodePanelGrid` and `pano_cli plan-grid-operation`, but live execution still performs legacy image loading, OpenGL texture updates, nanort lightmap baking, progress UI, and `Canvas::draw_objects` commit directly | Preserve grid/lightmap behavior while moving renderable grid commands toward app/renderer/document boundaries | `pp_app_core_grid_ui_tests`; `pano_cli plan-grid-operation --kind render --float32 --texture-resolution 1024 --samples 32`; `ctest --preset desktop-fast --build-config Debug` | Grid heightmap/lightmap execution is owned by app/renderer/document services with `NodePanelGrid` acting only as UI adapter |
| DEBT-0025 | Open | Modernization | Quick brush/color slot and mini-state planning now consumes pure `pp_app_core` through `NodePanelQuick` and `pano_cli plan-quick-operation`, but live execution still mutates legacy quick UI widgets, `Brush` previews, color picker popup state, and preset popup state directly | Preserve quick-panel behavior while quick brush/color commands move toward a brush/app command boundary with safer automation coverage | `pp_app_core_quick_ui_tests`; `pano_cli plan-quick-operation --kind brush --current-index 0 --slot-index 2`; `pano_cli plan-quick-operation --kind restore --brush-index 2 --color-index 1 --fire-event`; `ctest --preset desktop-fast --build-config Debug` | Quick-panel selection, popup, restore, reset, brush preview, and color execution are owned by app/brush/UI services with `NodePanelQuick` acting only as UI adapter |
| DEBT-0026 | Open | Modernization | Toolbar and canvas history command planning now consumes pure `pp_app_core` through `App::init_toolbar_main`, `NodeCanvas`, and `pano_cli plan-history-operation`, but live execution still mutates legacy `ActionManager` stacks and `Canvas::I` unsaved state directly | Preserve undo/redo/clear behavior while moving action history toward document/app command services | `pp_app_core_history_ui_tests`; `pano_cli plan-history-operation --kind undo --undo-count 2`; `pano_cli plan-history-operation --kind clear --undo-count 2 --redo-count 1 --memory-bytes 4096`; `ctest --preset desktop-fast --build-config Debug` | Undo/redo/clear execution is owned by document/app history services with toolbar and canvas input acting only as adapters |
| DEBT-0026 | Open | Modernization | Toolbar history command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `NodeCanvas`, `pano_cli plan-history-operation`, and the `HistoryUiServices` boundary, but the live adapter still mutates legacy `ActionManager` stacks directly | Preserve undo/redo/clear behavior while moving action history toward document/app command services | `pp_app_core_history_ui_tests`; `pano_cli plan-history-operation --kind undo --undo-count 2`; `pano_cli plan-history-operation --kind clear --undo-count 2 --redo-count 1 --memory-bytes 4096`; `ctest --preset desktop-fast --build-config Debug` | Undo/redo/clear execution is owned by injected document/app history services with no legacy `ActionManager` adapter |
| DEBT-0027 | Open | Modernization | Canvas draw-tool toolbar command, canvas input mode switching, and active-state planning now consume pure `pp_app_core` through `App::init_toolbar_draw`, `App::update`, `NodeCanvas`, `pano_cli plan-canvas-tool`, and `pano_cli plan-canvas-tool-state`, but live execution/state storage still mutates or reads legacy `Canvas` mode state, pen picking state, touch-lock state, and transform copy/cut action objects directly | Preserve current toolbar, stylus eraser, and keyboard draw/erase behavior while canvas input/tools move toward an app/document command boundary | `pp_app_core_canvas_tool_ui_tests`; `pano_cli plan-canvas-tool --kind copy`; `pano_cli plan-canvas-tool-state --mode draw --picking --touch-lock`; `ctest --preset desktop-fast --build-config Debug` | Canvas tool selection, toolbar state refresh, picking, touch lock, stylus eraser/key mode switching, and transform action execution are owned by app/document/canvas services with toolbar/canvas callbacks acting only as adapters |
| DEBT-0028 | Open | Modernization | Canvas clear command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-canvas-clear`, and the `DocumentCanvasClearServices` boundary, but the live adapter still calls legacy `Canvas::clear`, which records `ActionLayerClear`, clears the current layer/frame, and marks legacy `Canvas::I` unsaved | Preserve clear-current-layer behavior while canvas/document commands move toward document/app command services | `pp_app_core_document_canvas_tests`; `pano_cli plan-canvas-clear --r 0 --g 0.1 --b 0.2 --a 0.3`; `pano_cli plan-canvas-clear --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Canvas clear execution, undo recording, dirty-state updates, and clear color handling are owned by injected document/app services with no legacy canvas-clear adapter |
| DEBT-0029 | Open | Modernization | Image import route planning and execution dispatch now consume pure `pp_app_core` through the File menu, `pano_cli plan-image-import`, and the `DocumentImageImportServices` boundary, but the live adapter still loads images with legacy `Image`, calls legacy `Canvas::import_equirectangular`, or configures legacy import transform mode directly | Preserve current File > Import behavior while image import moves toward document/app/asset command services | `pp_app_core_document_import_tests`; `pano_cli plan-image-import --width 4096 --height 2048`; `pano_cli plan-image-import --width 1024 --height 1024`; `ctest --preset desktop-fast --build-config Debug` | Image loading, equirectangular import, transform-placement import, and failure reporting are owned by injected document/app/asset services with File-menu callbacks acting only as adapters and no legacy image-import adapter |
@@ -52,7 +52,7 @@ agent or engineer to remove them without reconstructing context from chat.
| DEBT-0032 | Open | Modernization | Layer menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_layer`, `pano_cli plan-layer-menu`, and the `DocumentLayerMenuServices` boundary, but the live adapter still calls legacy `Canvas::clear`, `App::dialog_layer_rename`, `NodePanelLayer::merge`, and reads `Canvas::I` animation/layer state directly | Preserve existing Layer menu behavior while layer commands move toward document/app services | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-layer-menu --command rename --no-current-layer`; `ctest --preset desktop-fast --build-config Debug` | Layer clear, rename, merge-down execution, animation gating, and selected-layer state are owned by injected document/app services with Layer-menu callbacks acting only as UI adapters and no legacy Layer menu adapter |
| DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and call the iOS SonarPen bridge directly | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter |
| DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, but the live adapter still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter |
| DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, but the live adapter still opens legacy open/save/settings/message-box dialogs, mutates legacy `ActionManager` history, and clears the legacy `Canvas` directly | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter |
| DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, and history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, but the live adapter still opens legacy open/save/settings/message-box dialogs and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter |
## Closed Debt

View File

@@ -538,15 +538,16 @@ pick/load/reload/clear, lightmap render capability/limit checks, and heightmap
commit used by the live grid panel before legacy image loading, OpenGL texture
updates, nanort lightmap baking, and `Canvas::draw_objects` execution continue.
`pano_cli plan-history-operation` exposes app-core planning for undo, redo, and
clear-history availability used by toolbar buttons and canvas shortcuts before
legacy `ActionManager` stack execution continues.
clear-history availability used by toolbar buttons and canvas shortcuts; live
toolbar execution now dispatches through `HistoryUiServices` before the legacy
`ActionManager` stack adapter continues.
`pano_cli plan-main-toolbar` exposes app-core planning for the live main
toolbar/status-bar shell, including open/save dialogs, undo/redo availability,
clear-history availability, clear-canvas no-canvas blocking, message-box
creation, and settings dialog routing. `pp_app_core` now also owns a
`MainToolbarServices` executor boundary, so `App::init_toolbar_main` dispatches
through a legacy adapter before legacy dialogs, `ActionManager`, and `Canvas`
execution continue.
through a legacy adapter before legacy dialogs, history/canvas adapters, and
settings UI execution continue.
`pano_cli plan-quick-operation` exposes app-core planning for quick brush/color
slot selection versus popup opening, plus quick mini-state restore/reset
validation used by the live quick panel before legacy `Brush`, color picker,
@@ -1272,7 +1273,8 @@ Results:
routing as JSON automation.
- `pp_app_core_history_ui_tests` passed, covering undo/redo availability,
no-op history commands, clear-history stack/memory state, memory-only clear,
and negative metric rejection.
negative metric rejection, service dispatch order, empty-history no-op
execution, and invalid execution metric rejection.
- `pano_cli_plan_history_operation_undo_smoke`,
`pano_cli_plan_history_operation_redo_empty_smoke`,
`pano_cli_plan_history_operation_clear_smoke`, and

View File

@@ -23,6 +23,15 @@ struct HistoryUiPlan {
bool no_op = false;
};
class HistoryUiServices {
public:
virtual ~HistoryUiServices() = default;
virtual void invoke_undo() = 0;
virtual void invoke_redo() = 0;
virtual void clear_history() = 0;
};
[[nodiscard]] inline pp::foundation::Status validate_history_metric(int value, const char* message) noexcept
{
if (value < 0) {
@@ -107,4 +116,46 @@ struct HistoryUiPlan {
return pp::foundation::Result<HistoryUiPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Status execute_history_ui_plan(
const HistoryUiPlan& plan,
HistoryUiServices& services)
{
const auto undo_status = validate_history_metric(plan.undo_count, "undo action count must not be negative");
if (!undo_status.ok()) {
return undo_status;
}
const auto redo_status = validate_history_metric(plan.redo_count, "redo action count must not be negative");
if (!redo_status.ok()) {
return redo_status;
}
const auto memory_status = validate_history_metric(plan.memory_bytes, "history memory bytes must not be negative");
if (!memory_status.ok()) {
return memory_status;
}
if (plan.no_op) {
return pp::foundation::Status::success();
}
switch (plan.operation) {
case HistoryUiOperation::undo:
if (plan.invokes_undo) {
services.invoke_undo();
}
return pp::foundation::Status::success();
case HistoryUiOperation::redo:
if (plan.invokes_redo) {
services.invoke_redo();
}
return pp::foundation::Status::success();
case HistoryUiOperation::clear:
if (plan.clears_history) {
services.clear_history();
}
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("unknown history operation");
}
} // namespace pp::app

View File

@@ -41,6 +41,7 @@ struct MainToolbarPlan {
bool records_undo = false;
bool marks_unsaved = false;
bool no_op = false;
HistoryUiPlan history;
DocumentCanvasClearPlan canvas_clear;
};
@@ -50,9 +51,9 @@ public:
virtual void show_open_dialog() = 0;
virtual void show_save_dialog() = 0;
virtual void invoke_undo() = 0;
virtual void invoke_redo() = 0;
virtual void clear_history() = 0;
virtual void invoke_undo(const HistoryUiPlan& plan) = 0;
virtual void invoke_redo(const HistoryUiPlan& plan) = 0;
virtual void clear_history(const HistoryUiPlan& plan) = 0;
virtual void clear_canvas(const DocumentCanvasClearPlan& plan) = 0;
virtual void show_message_box() = 0;
virtual void show_settings_dialog() = 0;
@@ -92,6 +93,7 @@ public:
plan.updates_memory_label = history.value().updates_memory_label;
plan.updates_title = history.value().updates_title;
plan.no_op = history.value().no_op;
plan.history = history.value();
return pp::foundation::Result<MainToolbarPlan>::success(plan);
}
@@ -108,6 +110,7 @@ public:
plan.updates_memory_label = history.value().updates_memory_label;
plan.updates_title = history.value().updates_title;
plan.no_op = history.value().no_op;
plan.history = history.value();
return pp::foundation::Result<MainToolbarPlan>::success(plan);
}
@@ -123,6 +126,7 @@ public:
plan.label = history.value().clears_history ? "Clear History" : "Clear History (Empty)";
plan.updates_memory_label = history.value().updates_memory_label;
plan.no_op = history.value().no_op;
plan.history = history.value();
return pp::foundation::Result<MainToolbarPlan>::success(plan);
}
@@ -171,13 +175,13 @@ public:
services.show_save_dialog();
return pp::foundation::Status::success();
case MainToolbarAction::invoke_undo:
services.invoke_undo();
services.invoke_undo(plan.history);
return pp::foundation::Status::success();
case MainToolbarAction::invoke_redo:
services.invoke_redo();
services.invoke_redo(plan.history);
return pp::foundation::Status::success();
case MainToolbarAction::clear_history:
services.clear_history();
services.clear_history(plan.history);
return pp::foundation::Status::success();
case MainToolbarAction::clear_canvas:
services.clear_canvas(plan.canvas_clear);

View File

@@ -317,19 +317,19 @@ public:
app_.dialog_save();
}
void invoke_undo() override
void invoke_undo(const pp::app::HistoryUiPlan& plan) override
{
ActionManager::undo();
execute_history_plan(plan);
}
void invoke_redo() override
void invoke_redo(const pp::app::HistoryUiPlan& plan) override
{
ActionManager::redo();
execute_history_plan(plan);
}
void clear_history() override
void clear_history(const pp::app::HistoryUiPlan& plan) override
{
ActionManager::clear();
execute_history_plan(plan);
}
void clear_canvas(const pp::app::DocumentCanvasClearPlan& plan) override
@@ -376,6 +376,32 @@ public:
}
private:
class LegacyHistoryUiServices final : public pp::app::HistoryUiServices {
public:
void invoke_undo() override
{
ActionManager::undo();
}
void invoke_redo() override
{
ActionManager::redo();
}
void clear_history() override
{
ActionManager::clear();
}
};
void execute_history_plan(const pp::app::HistoryUiPlan& plan)
{
LegacyHistoryUiServices services;
const auto status = pp::app::execute_history_ui_plan(plan, services);
if (!status.ok())
LOG("History action failed: %s", status.message);
}
App& app_;
};

View File

@@ -1,8 +1,36 @@
#include "app_core/history_ui.h"
#include "test_harness.h"
#include <string>
namespace {
class FakeHistoryUiServices final : public pp::app::HistoryUiServices {
public:
void invoke_undo() override
{
undo_calls += 1;
call_order += "undo;";
}
void invoke_redo() override
{
redo_calls += 1;
call_order += "redo;";
}
void clear_history() override
{
clear_calls += 1;
call_order += "clear;";
}
int undo_calls = 0;
int redo_calls = 0;
int clear_calls = 0;
std::string call_order;
};
void undo_and_redo_plan_availability(pp::tests::Harness& harness)
{
const auto undo = pp::app::plan_history_undo(2);
@@ -78,6 +106,76 @@ void rejects_negative_metrics(pp::tests::Harness& harness)
PP_EXPECT(harness, !pp::app::plan_history_clear(0, 0, -1));
}
void executor_dispatches_undo_redo_and_clear(pp::tests::Harness& harness)
{
FakeHistoryUiServices services;
const auto undo = pp::app::plan_history_undo(2);
PP_EXPECT(harness, undo);
if (undo) {
PP_EXPECT(harness, pp::app::execute_history_ui_plan(undo.value(), services).ok());
}
const auto redo = pp::app::plan_history_redo(1);
PP_EXPECT(harness, redo);
if (redo) {
PP_EXPECT(harness, pp::app::execute_history_ui_plan(redo.value(), services).ok());
}
const auto clear = pp::app::plan_history_clear(2, 1, 4096);
PP_EXPECT(harness, clear);
if (clear) {
PP_EXPECT(harness, pp::app::execute_history_ui_plan(clear.value(), services).ok());
}
PP_EXPECT(harness, services.undo_calls == 1);
PP_EXPECT(harness, services.redo_calls == 1);
PP_EXPECT(harness, services.clear_calls == 1);
PP_EXPECT(harness, services.call_order == "undo;redo;clear;");
}
void executor_preserves_empty_history_noops(pp::tests::Harness& harness)
{
FakeHistoryUiServices services;
const auto undo = pp::app::plan_history_undo(0);
PP_EXPECT(harness, undo);
if (undo) {
PP_EXPECT(harness, pp::app::execute_history_ui_plan(undo.value(), services).ok());
}
const auto redo = pp::app::plan_history_redo(0);
PP_EXPECT(harness, redo);
if (redo) {
PP_EXPECT(harness, pp::app::execute_history_ui_plan(redo.value(), services).ok());
}
const auto clear = pp::app::plan_history_clear(0, 0, 0);
PP_EXPECT(harness, clear);
if (clear) {
PP_EXPECT(harness, pp::app::execute_history_ui_plan(clear.value(), services).ok());
}
PP_EXPECT(harness, services.undo_calls == 0);
PP_EXPECT(harness, services.redo_calls == 0);
PP_EXPECT(harness, services.clear_calls == 0);
PP_EXPECT(harness, services.call_order.empty());
}
void executor_rejects_invalid_metrics(pp::tests::Harness& harness)
{
FakeHistoryUiServices services;
pp::app::HistoryUiPlan plan;
plan.operation = pp::app::HistoryUiOperation::undo;
plan.undo_count = -1;
plan.invokes_undo = true;
const auto status = pp::app::execute_history_ui_plan(plan, services);
PP_EXPECT(harness, !status.ok());
PP_EXPECT(harness, status.code == pp::foundation::StatusCode::out_of_range);
PP_EXPECT(harness, services.undo_calls == 0);
}
} // namespace
int main()
@@ -86,5 +184,8 @@ int main()
harness.run("undo and redo plan availability", undo_and_redo_plan_availability);
harness.run("clear plan tracks stacks and memory", clear_plan_tracks_stacks_and_memory);
harness.run("rejects negative metrics", rejects_negative_metrics);
harness.run("executor dispatches undo redo and clear", executor_dispatches_undo_redo_and_clear);
harness.run("executor preserves empty history noops", executor_preserves_empty_history_noops);
harness.run("executor rejects invalid metrics", executor_rejects_invalid_metrics);
return harness.finish();
}

View File

@@ -7,9 +7,24 @@ class FakeMainToolbarServices final : public pp::app::MainToolbarServices {
public:
void show_open_dialog() override { open_dialogs += 1; }
void show_save_dialog() override { save_dialogs += 1; }
void invoke_undo() override { undo_calls += 1; }
void invoke_redo() override { redo_calls += 1; }
void clear_history() override { clear_history_calls += 1; }
void invoke_undo(const pp::app::HistoryUiPlan& plan) override
{
undo_calls += 1;
last_history = plan;
}
void invoke_redo(const pp::app::HistoryUiPlan& plan) override
{
redo_calls += 1;
last_history = plan;
}
void clear_history(const pp::app::HistoryUiPlan& plan) override
{
clear_history_calls += 1;
last_history = plan;
}
void clear_canvas(const pp::app::DocumentCanvasClearPlan& plan) override
{
clear_canvas_calls += 1;
@@ -38,6 +53,7 @@ public:
int clear_canvas_calls = 0;
int message_boxes = 0;
int settings_dialogs = 0;
pp::app::HistoryUiPlan last_history;
pp::app::DocumentCanvasClearPlan last_clear;
};
@@ -74,6 +90,8 @@ void history_commands_reuse_history_breakpoints(pp::tests::Harness& harness)
PP_EXPECT(harness, undo.value().action == pp::app::MainToolbarAction::invoke_undo);
PP_EXPECT(harness, undo.value().updates_memory_label);
PP_EXPECT(harness, undo.value().updates_title);
PP_EXPECT(harness, undo.value().history.operation == pp::app::HistoryUiOperation::undo);
PP_EXPECT(harness, undo.value().history.invokes_undo);
PP_EXPECT(harness, !undo.value().no_op);
}
@@ -95,6 +113,8 @@ void history_commands_reuse_history_breakpoints(pp::tests::Harness& harness)
PP_EXPECT(harness, clear.value().action == pp::app::MainToolbarAction::clear_history);
PP_EXPECT(harness, clear.value().updates_memory_label);
PP_EXPECT(harness, !clear.value().updates_title);
PP_EXPECT(harness, clear.value().history.operation == pp::app::HistoryUiOperation::clear);
PP_EXPECT(harness, clear.value().history.clears_history);
}
}
@@ -161,6 +181,8 @@ void executor_dispatches_to_service_boundary(pp::tests::Harness& harness)
const auto status = pp::app::execute_main_toolbar_plan(undo.value(), services);
PP_EXPECT(harness, status.ok());
PP_EXPECT(harness, services.undo_calls == 1);
PP_EXPECT(harness, services.last_history.operation == pp::app::HistoryUiOperation::undo);
PP_EXPECT(harness, services.last_history.undo_count == 1);
}
auto clear_canvas = pp::app::plan_main_toolbar_command(