diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md
index 01d2dd8..9590867 100644
--- a/docs/modernization/debt.md
+++ b/docs/modernization/debt.md
@@ -18,6 +18,10 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions
+- 2026-06-13: `PLT-004` was narrowed again. `LegacyPlatformServices::display_file()`
+ and `share_file()` now delegate Apple file actions through
+ `AppleDocumentPlatformServices`, leaving the legacy adapter with only the
+ cross-platform dispatch shell.
- 2026-06-13: `DEBT-0036` was narrowed again. `NodeStrokePreview::draw_stroke_immediate()`
now routes final composite execution and preview copy-back through a retained
local wrapper, leaving the call site with only sequence wiring.
diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md
index 17bd462..f419522 100644
--- a/docs/modernization/tasks.md
+++ b/docs/modernization/tasks.md
@@ -1230,6 +1230,41 @@ Completed Task Log:
| --- | --- | ---: | --- | --- |
| 2026-06-13 | PLT-003 | +3 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R "panopainter_platform_build_target_matrix_self_test|panopainter_package_smoke_readiness_self_test" --output-on-failure` | `7e09298e` |
+### PLT-004 - Reduce Remaining Platform Legacy Adapter Tail
+
+Status: Done
+Score: +3 platform alignment and package parity
+Debt: `DEBT-0017`, `DEBT-0052`, `DEBT-0053`
+Scope: `src/platform_legacy/legacy_platform_services.*`, `src/platform_api/*`,
+`src/platform_apple/*`, `src/platform_web/*`, `tests/platform_api/platform_services_tests.cpp`
+
+Goal:
+
+Trim one more concrete platform policy seam out of the catch-all legacy
+platform adapter without destabilizing platform package or build validation.
+Keep the work small, measurable, and centered on a policy that already has
+test coverage in `pp_platform_api`.
+
+Done Checks:
+
+- One remaining platform policy surface moves out of `legacy_platform_services`
+ or is explicitly isolated behind a narrower adapter.
+- The affected platform API tests cover the updated policy route.
+- The debt log records the reduced legacy adapter surface.
+
+Validation:
+
+```powershell
+& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_platform_api_tests.vcxproj /p:Configuration=Debug /p:Platform=x64
+& .\out\build\windows-msvc-default\tests\Debug\pp_platform_api_tests.exe
+```
+
+Completed Task Log:
+
+| Date | Task | Score | Validation | Commit |
+| --- | --- | ---: | --- | --- |
+| 2026-06-13 | PLT-004 | +3 platform alignment and package parity | `& 'C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe' out\build\windows-msvc-default\tests\pp_platform_api_tests.vcxproj /p:Configuration=Debug /p:Platform=x64`
`& .\out\build\windows-msvc-default\tests\Debug\pp_platform_api_tests.exe` | `pending` |
+
Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
diff --git a/src/platform_apple/apple_platform_services.cpp b/src/platform_apple/apple_platform_services.cpp
index a64f278..a055852 100644
--- a/src/platform_apple/apple_platform_services.cpp
+++ b/src/platform_apple/apple_platform_services.cpp
@@ -6,6 +6,11 @@
#include
#include
+#if defined(__IOS__) || defined(__OSX__)
+#include "app_core/app.h"
+#include
+#endif
+
namespace pp::platform::apple {
namespace {
@@ -125,4 +130,34 @@ std::string AppleDocumentPlatformServices::format_working_directory_path(std::st
return std::string(path);
}
+void AppleDocumentPlatformServices::display_file(std::string_view path) const
+{
+ const std::string value(path);
+#if defined(__IOS__)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [App::I->ios_view display_file:value];
+ });
+#elif defined(__OSX__)
+ [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithUTF8String:value.c_str()]];
+#else
+ (void)value;
+#endif
+}
+
+void AppleDocumentPlatformServices::share_file(std::string_view path) const
+{
+ const std::string value(path);
+#if defined(__IOS__)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [App::I->ios_view share_file:[NSString stringWithUTF8String:value.c_str()]];
+ });
+#elif defined(__OSX__)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [App::I->osx_view share_file:[NSString stringWithUTF8String:value.c_str()]];
+ });
+#else
+ (void)value;
+#endif
+}
+
}
diff --git a/src/platform_apple/apple_platform_services.h b/src/platform_apple/apple_platform_services.h
index 0dcca10..2dbd11f 100644
--- a/src/platform_apple/apple_platform_services.h
+++ b/src/platform_apple/apple_platform_services.h
@@ -35,6 +35,8 @@ public:
[[nodiscard]] bool supports_working_directory_picker() const;
[[nodiscard]] std::string format_working_directory_path(std::string_view path) const;
+ void display_file(std::string_view path) const;
+ void share_file(std::string_view path) const;
private:
PlatformFamily family_;
diff --git a/src/platform_legacy/legacy_platform_services.cpp b/src/platform_legacy/legacy_platform_services.cpp
index 08f9cbd..a689b70 100644
--- a/src/platform_legacy/legacy_platform_services.cpp
+++ b/src/platform_legacy/legacy_platform_services.cpp
@@ -636,32 +636,12 @@ public:
void display_file(std::string_view path) override
{
- const std::string value(path);
-#ifdef __IOS__
- dispatch_async(dispatch_get_main_queue(), ^{
- [App::I->ios_view display_file:value];
- });
-#elif __OSX__
- [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithUTF8String:value.c_str()]];
-#else
- (void)value;
-#endif
+ active_apple_document_platform_services().display_file(path);
}
void share_file(std::string_view path) override
{
- const std::string value(path);
-#ifdef __IOS__
- dispatch_async(dispatch_get_main_queue(), ^{
- [App::I->ios_view share_file:[NSString stringWithUTF8String:value.c_str()]];
- });
-#elif __OSX__
- dispatch_async(dispatch_get_main_queue(), ^{
- [App::I->osx_view share_file:[NSString stringWithUTF8String:value.c_str()]];
- });
-#else
- (void)value;
-#endif
+ active_apple_document_platform_services().share_file(path);
}
void request_app_close() override