From 5e230669c459b7aafcb97c0ce83e23718f8b51cb Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 5 Sep 2026 18:07:57 -0400 Subject: [PATCH 1/3] feat: add a portable resolved-page observer that forwards page payloads and shared-prop keys to callbacks. --- CHANGELOG.md | 2 + README.md | 14 +++++ src/ResolvedPageObserver.php | 25 +++++++++ tests/ResolvedPageObserverTest.php | 86 ++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 src/ResolvedPageObserver.php create mode 100644 tests/ResolvedPageObserverTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c159321..1cf8283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.2.1 Under development +- feat: add a portable resolved-page observer that forwards page payloads and shared-prop keys to callbacks. + ## 0.2.0 August 25, 2026 - docs: add `Next steps` section with links to installation, usage, configuration, and testing guides. diff --git a/README.md b/README.md index 2c40417..4207e5f 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,20 @@ $result = Protocol::create()->page($request, $input); `Protocol` does not return a framework response. An adapter inspects the result, applies `statusCode()` and `headers()`, serializes `page()` for an Inertia visit, or embeds the page JSON in the root HTML document for an initial visit. +## Reolved-page observation + +`PHPForge\Inertia\ResolvedPageObserver` forwards the resolved page payload and shared-prop keys to a callback. +Observer failures propagate to the caller; the observer does not mutate pages or hide callback failures. + +```php +$observer = new \PHPForge\Inertia\ResolvedPageObserver($collector->observe(...)); + +$observer->observe($page); +``` + +The callback receives `array` page data and `list` shared-prop keys, without depending on a debugger +or framework. The class is extensible so adapters can preserve their own observer interfaces without duplicating logic. + ## Adapter boundary The core owns protocol decisions and page shaping. A framework adapter remains responsible for: diff --git a/src/ResolvedPageObserver.php b/src/ResolvedPageObserver.php new file mode 100644 index 0000000..efac265 --- /dev/null +++ b/src/ResolvedPageObserver.php @@ -0,0 +1,25 @@ +, list): void $callback Receives the unmodified page diagnostics. + */ + public function __construct(private Closure $callback) {} + + public function observe(Page $page): void + { + ($this->callback)($page->toArray(), $page->sharedProps()); + } +} diff --git a/tests/ResolvedPageObserverTest.php b/tests/ResolvedPageObserverTest.php new file mode 100644 index 0000000..9dad973 --- /dev/null +++ b/tests/ResolvedPageObserverTest.php @@ -0,0 +1,86 @@ +observe(new Page('Home', [], '/', '')); + + self::assertSame( + [], + $observed, + 'A page without shared metadata must forward an empty key list.', + ); + } + + public function testForwardsPagePayloadAndSharedKeysWithoutMutation(): void + { + $page = ( + new Page( + 'Dashboard', + ['user' => ['name' => 'Ada'], 'errors' => ['email' => 'Invalid']], + '/dashboard', + 'v1') + )->withMetadata((new PageMetadata())->withSharedProps(['user'])); + + $payload = $page->toArray(); + + $observed = []; + + $observer = new ResolvedPageObserver( + static function (array $data, array $sharedKeys) use (&$observed): void { + $observed[] = [$data, $sharedKeys]; + }, + ); + + $observer->observe($page); + $observer->observe($page); + + self::assertEquals( + [[$payload, ['user']], [$payload, ['user']]], + $observed, + 'Each observation must forward the original payload and shared keys exactly once.', + ); + self::assertEquals( + $payload, + $page->toArray(), + 'Observation must not change the page.', + ); + } + + public function testPropagatesCallbackFailure(): void + { + $observer = new ResolvedPageObserver( + static function (): never { + throw new RuntimeException( + 'Observer failed.', + ); + }, + ); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Observer failed.', + ); + + $observer->observe(new Page('Home', [], '/', '')); + } +} From 16af69ee56b70c11d89518e3c733bc8fc8dda477 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula <42547589+terabytesoftw@users.noreply.github.com> Date: Sat, 5 Sep 2026 18:08:22 -0400 Subject: [PATCH 2/3] Apply fixes from StyleCI (#11) --- tests/ResolvedPageObserverTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ResolvedPageObserverTest.php b/tests/ResolvedPageObserverTest.php index 9dad973..cca9cb9 100644 --- a/tests/ResolvedPageObserverTest.php +++ b/tests/ResolvedPageObserverTest.php @@ -38,7 +38,8 @@ public function testForwardsPagePayloadAndSharedKeysWithoutMutation(): void 'Dashboard', ['user' => ['name' => 'Ada'], 'errors' => ['email' => 'Invalid']], '/dashboard', - 'v1') + 'v1' + ) )->withMetadata((new PageMetadata())->withSharedProps(['user'])); $payload = $page->toArray(); From ff4f31ed1b972d986b68c3e8e3748ed4b6f96680 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Sat, 5 Sep 2026 18:13:15 -0400 Subject: [PATCH 3/3] Appky fixed Coderabbitai review. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4207e5f..d0f4622 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ $result = Protocol::create()->page($request, $input); `Protocol` does not return a framework response. An adapter inspects the result, applies `statusCode()` and `headers()`, serializes `page()` for an Inertia visit, or embeds the page JSON in the root HTML document for an initial visit. -## Reolved-page observation +## Resolved-page observation `PHPForge\Inertia\ResolvedPageObserver` forwards the resolved page payload and shared-prop keys to a callback. Observer failures propagate to the caller; the observer does not mutate pages or hide callback failures.