diff --git a/CMakeLists.txt b/CMakeLists.txt
index 460537d..dc8f61d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -244,7 +244,8 @@ add_library(pp_platform_api STATIC
src/platform_api/platform_policy.cpp
src/platform_api/platform_policy.h
src/platform_api/platform_services.cpp
- src/platform_api/platform_services.h)
+ src/platform_api/platform_services.h
+ ${PP_PLATFORM_LINUX_SOURCES})
target_include_directories(pp_platform_api
PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/src")
diff --git a/cmake/PanoPainterSources.cmake b/cmake/PanoPainterSources.cmake
index 04b35b1..c201817 100644
--- a/cmake/PanoPainterSources.cmake
+++ b/cmake/PanoPainterSources.cmake
@@ -79,6 +79,11 @@ set(PP_LEGACY_APP_SOURCES
src/pch.cpp
)
+set(PP_PLATFORM_LINUX_SOURCES
+ src/platform_linux/linux_platform_services.cpp
+ src/platform_linux/linux_platform_services.h
+)
+
set(PP_PANOPAINTER_APP_SOURCES
src/app.cpp
src/app_cloud.cpp
diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md
index 4ec32be..687a1d7 100644
--- a/docs/modernization/debt.md
+++ b/docs/modernization/debt.md
@@ -52,6 +52,9 @@ agent or engineer to remove them without reconstructing context from chat.
and `share_file()` now delegate Apple file actions through
`AppleDocumentPlatformServices`, leaving the legacy adapter with only the
cross-platform dispatch shell.
+- 2026-06-13: `PLT-005` was narrowed again. Linux rendered-frame title updates
+ now route through `src/platform_linux/linux_platform_services.*`; the legacy
+ platform adapter still owns the surrounding 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 cf59eae..5882f08 100644
--- a/docs/modernization/tasks.md
+++ b/docs/modernization/tasks.md
@@ -1294,6 +1294,42 @@ Completed Task Log:
| --- | --- | ---: | --- | --- |
| 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` | `6ba98ee` |
+### PLT-005 - Split Linux FPS Title Reporting From Legacy Platform Adapter
+
+Status: Done
+Score: +1 platform alignment and package parity
+Debt: `DEBT-0017`, `DEBT-0052`
+Scope: `src/platform_legacy/legacy_platform_services.cpp`,
+`src/platform_linux/*`, `tests/platform_api_tests.cpp` if coverage is needed
+
+Goal:
+
+Move Linux rendered-frame FPS title updates out of the catch-all legacy
+platform adapter into a named Linux platform service boundary. Preserve the
+current Linux title update behavior and keep non-Linux behavior unchanged.
+
+Done Checks:
+
+- `src/platform_legacy/legacy_platform_services.cpp` no longer owns the Linux
+ FPS-title update branch.
+- Linux rendered-frame reporting still updates the title as before.
+- The debt log records the reduced Linux platform tail.
+
+Validation:
+
+```powershell
+ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure
+cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests
+```
+
+Completed Task Log:
+
+| Date | Task | Score | Validation | Commit |
+| --- | --- | ---: | --- | --- |
+| 2026-06-13 | PLT-005 | +1 platform alignment and package parity | `ctest --preset desktop-fast --build-config Debug -R pp_platform_api_tests --output-on-failure`; `cmake --build --preset windows-msvc-default --config Debug --target pp_platform_api_tests` | `pending` |
+
+### STR-010 - Extract Remaining Draw Merge Composite Orchestration
+
Status: Done
Score: +2 renderer boundary and OpenGL parity
Debt: `DEBT-0036`
diff --git a/src/platform_legacy/legacy_platform_services.cpp b/src/platform_legacy/legacy_platform_services.cpp
index a689b70..ebcc47b 100644
--- a/src/platform_legacy/legacy_platform_services.cpp
+++ b/src/platform_legacy/legacy_platform_services.cpp
@@ -37,9 +37,9 @@ void save_image_library(const std::string& path);
#elif __LINUX__
#include
#include
+#include "platform_linux/linux_platform_services.h"
std::string linux_home_path();
int mkpath(const std::string& dir, mode_t mode = DEFFILEMODE);
-void linux_update_fps(int frames);
#elif __WEB__
#include
void webgl_pick_file(std::function callback);
@@ -474,7 +474,7 @@ public:
void report_rendered_frames(int frames) override
{
#ifdef __LINUX__
- linux_update_fps(frames);
+ pp::platform::linux::report_rendered_frames(frames);
#else
(void)frames;
#endif
diff --git a/src/platform_linux/linux_platform_services.cpp b/src/platform_linux/linux_platform_services.cpp
new file mode 100644
index 0000000..56f750c
--- /dev/null
+++ b/src/platform_linux/linux_platform_services.cpp
@@ -0,0 +1,35 @@
+#include "platform_linux/linux_platform_services.h"
+
+#ifdef __LINUX__
+#include
+
+#include
+
+#include "app.h"
+
+namespace pp::platform::linux {
+namespace {
+
+void linux_update_fps(int frames)
+{
+ App::I->title("PanoPainter - " + std::to_string(frames) + " FPS");
+}
+
+}
+
+void report_rendered_frames(int frames)
+{
+ linux_update_fps(frames);
+}
+
+}
+#else
+namespace pp::platform::linux {
+
+void report_rendered_frames(int frames)
+{
+ (void)frames;
+}
+
+}
+#endif
diff --git a/src/platform_linux/linux_platform_services.h b/src/platform_linux/linux_platform_services.h
new file mode 100644
index 0000000..a146d7d
--- /dev/null
+++ b/src/platform_linux/linux_platform_services.h
@@ -0,0 +1,7 @@
+#pragma once
+
+namespace pp::platform::linux {
+
+void report_rendered_frames(int frames);
+
+}