diff --git a/tests/Integration/View/templates/consume.blade.php b/tests/Integration/View/templates/consume.blade.php
new file mode 100644
index 000000000..ced9b232d
--- /dev/null
+++ b/tests/Integration/View/templates/consume.blade.php
@@ -0,0 +1,15 @@
+@isset($color)
+
+
+C
+D
+
+
+@else
+
+
+C
+D
+
+
+@endisset
diff --git a/tests/Integration/View/templates/different-extension.sh b/tests/Integration/View/templates/different-extension.sh
new file mode 100644
index 000000000..0e3eb4632
--- /dev/null
+++ b/tests/Integration/View/templates/different-extension.sh
@@ -0,0 +1 @@
+echo "{{ $scriptMessage }}" > output.log
diff --git a/tests/Integration/View/templates/hello.blade.php b/tests/Integration/View/templates/hello.blade.php
new file mode 100644
index 000000000..d209ed353
--- /dev/null
+++ b/tests/Integration/View/templates/hello.blade.php
@@ -0,0 +1 @@
+Hello {{ $name }}
diff --git a/tests/Integration/View/templates/partials/scoped-partial.blade.php b/tests/Integration/View/templates/partials/scoped-partial.blade.php
new file mode 100644
index 000000000..ab12730fa
--- /dev/null
+++ b/tests/Integration/View/templates/partials/scoped-partial.blade.php
@@ -0,0 +1 @@
+Parent: {{ $parentVar ?? 'undefined' }}, Explicit: {{ $explicitVar }}
\ No newline at end of file
diff --git a/tests/Integration/View/templates/renderable-exception.blade.php b/tests/Integration/View/templates/renderable-exception.blade.php
new file mode 100644
index 000000000..ba36dcce9
--- /dev/null
+++ b/tests/Integration/View/templates/renderable-exception.blade.php
@@ -0,0 +1,3 @@
+@php
+ throw new Hypervel\Tests\Integration\View\RenderableException;
+@endphp
diff --git a/tests/Integration/View/templates/uses-appendable-panel.blade.php b/tests/Integration/View/templates/uses-appendable-panel.blade.php
new file mode 100644
index 000000000..4e1744d2b
--- /dev/null
+++ b/tests/Integration/View/templates/uses-appendable-panel.blade.php
@@ -0,0 +1,9 @@
+@if ($withInjectedValue)
+
+ Panel contents
+
+@else
+
+ Panel contents
+
+@endif
diff --git a/tests/Integration/View/templates/uses-child-input.blade.php b/tests/Integration/View/templates/uses-child-input.blade.php
new file mode 100644
index 000000000..bc5bdade5
--- /dev/null
+++ b/tests/Integration/View/templates/uses-child-input.blade.php
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/Integration/View/templates/uses-include-regular.blade.php b/tests/Integration/View/templates/uses-include-regular.blade.php
new file mode 100644
index 000000000..f366d2e23
--- /dev/null
+++ b/tests/Integration/View/templates/uses-include-regular.blade.php
@@ -0,0 +1 @@
+@include('partials.scoped-partial', ['explicitVar' => $explicitVar])
\ No newline at end of file
diff --git a/tests/Integration/View/templates/uses-include-scoped.blade.php b/tests/Integration/View/templates/uses-include-scoped.blade.php
new file mode 100644
index 000000000..5e26517cd
--- /dev/null
+++ b/tests/Integration/View/templates/uses-include-scoped.blade.php
@@ -0,0 +1 @@
+@includeIsolated('partials.scoped-partial', ['explicitVar' => $explicitVar])
diff --git a/tests/Integration/View/templates/uses-link.blade.php b/tests/Integration/View/templates/uses-link.blade.php
new file mode 100644
index 000000000..189b4759e
--- /dev/null
+++ b/tests/Integration/View/templates/uses-link.blade.php
@@ -0,0 +1 @@
+This is a sentence with a link.
diff --git a/tests/Integration/View/templates/uses-panel-dynamically.blade.php b/tests/Integration/View/templates/uses-panel-dynamically.blade.php
new file mode 100644
index 000000000..48cbc3e90
--- /dev/null
+++ b/tests/Integration/View/templates/uses-panel-dynamically.blade.php
@@ -0,0 +1,3 @@
+
+ Panel contents
+
diff --git a/tests/Integration/View/templates/uses-panel.blade.php b/tests/Integration/View/templates/uses-panel.blade.php
new file mode 100644
index 000000000..aa2f07da3
--- /dev/null
+++ b/tests/Integration/View/templates/uses-panel.blade.php
@@ -0,0 +1,3 @@
+
+ Panel contents
+
diff --git a/tests/Integration/View/templates/varied-dynamic-calls.blade.php b/tests/Integration/View/templates/varied-dynamic-calls.blade.php
new file mode 100644
index 000000000..5b267ca4d
--- /dev/null
+++ b/tests/Integration/View/templates/varied-dynamic-calls.blade.php
@@ -0,0 +1,2 @@
+
+
From b1565a989df90ebe2dc559f20f4ee6d6403c9d36 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Thu, 6 Aug 2026 11:46:19 +0000
Subject: [PATCH 10/16] docs(view): document package and cache lifecycles
Explain compiled-view freshness in worker terms, recommend deployment-time view caching, and describe when live workers require an explicit cache clear after local edits. Keep the user guidance in Laravel-style prose without exposing coroutine-context or internal cache implementation details.
Update the package README with its documentation and upstream references plus the two public Hypervel differences developers need to account for: alias-first component registration and eager rendering when a View becomes stored section content.
---
src/boost/docs/views.md | 4 +++-
src/view/README.md | 12 ++++++++++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/boost/docs/views.md b/src/boost/docs/views.md
index 345f3c717..9eeccb67c 100644
--- a/src/boost/docs/views.md
+++ b/src/boost/docs/views.md
@@ -351,7 +351,7 @@ View::creator('profile', ProfileCreator::class);
## Optimizing Views
-By default, Blade template views are compiled on demand. When a request is executed that renders a view, Hypervel will determine if a compiled version of the view exists. If the file exists, Hypervel will then determine if the uncompiled view has been modified more recently than the compiled view. If the compiled view either does not exist, or the uncompiled view has been modified, Hypervel will recompile the view.
+By default, Blade template views are compiled on demand. The first time a worker renders a view, Hypervel will determine if a compiled version of the view exists. If the file exists, Hypervel will then determine if the uncompiled view has been modified more recently than the compiled view. If the compiled view either does not exist, or the uncompiled view has been modified, Hypervel will recompile the view. The worker will reuse that compiled view for subsequent requests.
Compiling views during the request may have a small negative impact on performance, so Hypervel provides the `view:cache` Artisan command to precompile all of the views utilized by your application. For increased performance, you may wish to run this command as part of your deployment process:
@@ -366,3 +366,5 @@ You may use the `view:clear` command to clear the view cache:
```shell
php artisan view:clear
```
+
+When editing views while application workers are running, you should run this command so the workers compile the updated views the next time they are rendered.
diff --git a/src/view/README.md b/src/view/README.md
index d67ed0829..de4ab43a3 100644
--- a/src/view/README.md
+++ b/src/view/README.md
@@ -1,4 +1,12 @@
-View for Hypervel
-===
+# Hypervel View
[](https://deepwiki.com/hypervel/view)
+
+Documentation: https://hypervel.org/docs/views
+
+## Differences From Laravel
+
+- `Blade::component()` accepts the component alias before the class name: `Blade::component('package-alert', Alert::class)`.
+- A `View` passed as section content is rendered before the content is stored, so later changes to that `View` instance do not affect the section.
+
+Ported from: https://github.com/laravel/framework/tree/13.x/src/Illuminate/View
From 07bd0ee6b3542d2edf330f6d023b8a967f8f9312 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Thu, 6 Aug 2026 11:46:28 +0000
Subject: [PATCH 11/16] docs(audit): record the completed View lifecycle audit
Record the signed-off View design, findings, rejected machinery, performance boundaries, test strategy, and final implementation state. Close the package checklist and routing index while carrying the completed view-01 and reflection-02 revalidations and the Foundation and Translation cross-package ownership entries.
The records distinguish retained slot memory from loop-output corruption, document the Xdebug-only parser branch without a synthetic seam, and leave no View TODO, deferred defect, compatibility workaround, or open workflow step.
---
...amework-coroutine-state-lifecycle-audit.md | 15 +-
...-coroutine-state-lifecycle-audit-ledger.md | 40 +-
...orrectness-lifecycle-and-current-parity.md | 558 ++++++++++++++++++
3 files changed, 602 insertions(+), 11 deletions(-)
create mode 100644 docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
diff --git a/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md b/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
index 26707d954..ea7abfbe9 100644
--- a/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
+++ b/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
@@ -990,9 +990,9 @@ An exceptionally large shared work unit may receive its own linked detail plan w
This compact index routes the completed-work history that must be consulted with the full plan after compaction. Detailed history remains in the [companion ledger](2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md).
-- **Active package or work unit:** None. `routing` is complete; detail plan `2026-08-05-2352-routing-correctness-current-parity-and-cache-lifecycles.md`.
-- **Ledger entries required for the active work:** None. The completed Routing work is recorded under `Complete Routing correctness, current parity, and cache lifecycles`, with `redis-24` and `collections-14` cross-referenced from the owning Redis and Collections entries.
-- **Pending revalidation carried into the active work:** None. `contracts-05`, `reflection-02`, `container-08`, `support-02`, and `routing-01` are revalidated.
+- **Active package or work unit:** None.
+- **Ledger entries required for the active work:** None.
+- **Pending revalidation carried into the active work:** None.
Update these three lines when a package starts, completes, or gains a cross-package dependency. Name exact work-unit headings or shared finding IDs from the companion ledger; never use “see recent entries” or require a full-ledger reread.
@@ -1003,7 +1003,7 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| Finding | Owning package | Affected or revalidation packages | Ledger entry |
|---|---|---|---|
| `validation-01` | `validation` | `contracts` and `validation` (revalidation complete) | `Harden framework contracts and request-scoped state`; shared finding `validation-01` |
-| `view-01` | `view` | `contracts` and `foundation` (revalidation complete); later full `view` audit | `Harden framework contracts and request-scoped state`; shared finding `view-01` |
+| `view-01` | `view` | `contracts`, `foundation`, and `view` (revalidation complete) | `Harden framework contracts and request-scoped state`; shared finding `view-01` |
| `filesystem-01` | `filesystem` | `contracts` and `filesystem` (revalidation complete) | `Harden framework contracts and request-scoped state`; shared finding `filesystem-01` |
| `queue-01` | `queue` | `contracts` and `queue` (revalidation complete) | `Harden framework contracts and request-scoped state`; shared finding `queue-01` |
| `contracts-05` | `contracts` | `http`, `foundation`, `console`, `database`, and `routing` (revalidation complete) | `Harden framework contracts and request-scoped state`; finding `contracts-05` |
@@ -1011,7 +1011,7 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `http-01` | `http` | `macroable` and `http` (revalidation complete), `testing`; later full `testing` audit | `Complete Macroable callable and test-state handling`; shared finding `http-01` |
| `console-01` | `console` | `contracts` and `console` (revalidation complete) | `Preserve typed console contracts during Composer scripts`; shared finding `console-01` |
| `reflection-01` | `reflection` | `events` and `foundation` (revalidation complete) | `Consolidate reflection metadata and correct callable inference`; finding `reflection-01` |
-| `reflection-02` | `reflection` | `foundation`, `console`, and `routing` (revalidation complete); later full `view` audit | `Consolidate reflection metadata and correct callable inference`; finding `reflection-02` |
+| `reflection-02` | `reflection` | `foundation`, `console`, `routing`, and `view` (revalidation complete) | `Consolidate reflection metadata and correct callable inference`; finding `reflection-02` |
| `reflection-04` | `reflection` | `di` and `queue` (revalidation complete), `support`, `testing`; later full consumer audits | `Consolidate reflection metadata and correct callable inference`; finding `reflection-04` |
| `config-01` | `config` | `foundation` (revalidation complete) | `Preserve configuration identity across worker reloads`; finding `config-01` |
| `config-02` | `foundation` | `reverb` (revalidation complete), `testing`; later full `testing` audit | `Preserve configuration identity across worker reloads`; finding `config-02` |
@@ -1175,6 +1175,9 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `routing-18` | `routing` | `auth` (revalidation complete) | `Complete Routing correctness, current parity, and cache lifecycles`; finding `routing-18` |
| `collections-14` | `collections` | `collections` and `routing` (revalidation complete) | `Complete Routing correctness, current parity, and cache lifecycles`; finding `collections-14` |
| `validation-18` | `validation` | `validation` and `support` (revalidation complete) | `Complete Validation correctness, parity, and compiled lifecycles`; finding `validation-18` |
+| `view-09` | `foundation` | `foundation` and `view` (revalidation complete) | `Complete View correctness, lifecycle, and current parity`; finding `view-09` |
+| `view-24` | `foundation` | `foundation` and `view` (revalidation complete) | `Complete View correctness, lifecycle, and current parity`; finding `view-24` |
+| `translation-10` | `translation` | `view` (sibling revalidation complete); later full `translation` audit | `Complete View correctness, lifecycle, and current parity`; finding `translation-10` |
## Package checklist
@@ -1262,7 +1265,7 @@ The order is lower-level first where practical. Hypervel has cross-cutting depen
- [x] `auth`
- [x] `validation`
- [x] `routing`
-- [ ] `view`
+- [x] `view`
- [ ] `translation`
- [ ] `pagination`
- [ ] `socialite`
diff --git a/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
index fa8c69d5f..63d1409c7 100644
--- a/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -92,7 +92,7 @@ Append package entries in checklist order. Keep each entry compact but complete
| `notifications-07` | Contract defect | Major | High | The Notification Factory rejects a supported single notifiable even though every implementation and the Dispatcher accepts it | Widen Factory notifiables to `mixed`, retain its two-parameter `sendNow()`, and keep channels on the Dispatcher contract |
- **Important rejected concerns:** Do not rewrite validation around only `ValidationRule`; Laravel's deprecated contracts remain its live internal execution protocol, userland already receives the modern API, and a rewrite would add permanent synchronization cost without behavior gain. Do not remove `Console\Application::add()`: Symfony deprecated its underlying method, but Laravel deliberately retains, uses, and tests a non-deprecated wrapper over `addCommand()`. Do not add orphan Image contracts before Hypervel has a coherent Image package. Do not relocate the Monolog-specific context contract without a separately approved API redesign. Do not add optional dependencies merely because they appear only in lazy parameter/return types. Do not make public `View::share()` request-dependent or add a public request-sharing API. Do not keep a throwing `Request::get()` tombstone; record the intentional omission so static analysis rejects it.
-- **Cross-package implications:** `validation-01` affects `validation`; `view-01` affects `view` and `foundation`; `filesystem-01` affects `filesystem`; `queue-01` affects `queue`; `contracts-04` affects `server`, `server-process`, `websocket-server`, and `reverb`; `contracts-05` affects `http`, `routing`, `foundation`, `console`, and `database`; `contracts-09` affects Foundation and Broadcasting; `contracts-10` affects Mail; `contracts-11` affects Mail and Console; and `notifications-07` affects Notifications. Queue, Foundation, Broadcasting, HTTP, Mail, Notifications, and the affected Console consumer revalidation are complete; the later HTTP work also completed the Contracts-owned part of `routing-01` by restoring the URL generator's open parameter-normalization contract. The remaining package checkboxes stay open until their own complete audits.
+- **Cross-package implications:** `validation-01` affects `validation`; `view-01` affects `view` and `foundation`; `filesystem-01` affects `filesystem`; `queue-01` affects `queue`; `contracts-04` affects `server`, `server-process`, `websocket-server`, and `reverb`; `contracts-05` affects `http`, `routing`, `foundation`, `console`, and `database`; `contracts-09` affects Foundation and Broadcasting; `contracts-10` affects Mail; `contracts-11` affects Mail and Console; and `notifications-07` affects Notifications. Queue, Foundation, Broadcasting, HTTP, Mail, Notifications, View, and the affected Console consumer revalidation are complete. The later HTTP work also completed the Contracts-owned part of `routing-01` by restoring the URL generator's open parameter-normalization contract. The remaining package checkboxes stay open until their own complete audits.
- **Implementation:** Declared every external parent-interface dependency in the split package; added evidence-backed contract and implementation types; ported `ShouldBeDiscovered` with current upstream source, fixtures, tests, and docs; corrected the handshake/process spellings without aliases; removed only APIs directly deprecated by Laravel plus dead migration wiring; retained Laravel's live `Application::add()` wrapper; normalized every statically valid `resolveCommands()` argument shape; corrected Engine diagnostics; completed contract docs and upstream references; and added lifecycle warnings to concrete worker-state mutators. The later Queue work removed the optional broadcaster capability and widened Factory notifiables without changing Factory `sendNow()` arity or the Dispatcher channel boundary. The validation, view, filesystem, and queue corrections below were implemented at their owning boundaries, with superseded state writes, loose construction types, unrestorable identifier conversion, raw morph-alias restoration, dead dependencies, obsolete tests, and stale comments removed.
- **Regression tests:** Added split-package dependency-presence coverage, discovery opt-out fixtures, exact Engine diagnostic coverage inside and outside a coroutine, custom-filesystem result validation, configured-rule clone/isolation coverage, deterministic concurrent view-error isolation, ordinary serialization for non-Eloquent queue contracts, morph-mapped Eloquent collection restoration, and all supported scalar/array `resolveCommands()` forms. Updated every affected contract implementation and test double, including integration-only caster fixtures; the later Queue gate verifies the broadcaster command and Notification implementers against the corrected contracts.
- **Later Mail revalidation:** `contracts-10` aligns every Mail queue entry point with the framework's enum-capable identifier boundary while retaining non-nullable `queueOn()` and `laterOn()` aliases. `contracts-11` replaces unsupported `mixed` callback metadata with `Closure|string`; focused Mail and Console scheduling coverage proves the narrowed contract through the real consumer path without changing runtime behavior.
@@ -118,8 +118,8 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Failure:** Concurrent middleware scopes overwrite `Factory::$shared['errors']`, allowing one request to render another request's validation errors.
- **Decision:** Keep public `share()` as a boot-time worker baseline. Store only the request overlay in a minimal internal coroutine-local state owner with exact nested/exceptional restoration. `Factory::mergeSharedData()` composes global, request, and local data in one merge; `shared()` and `getShared()` observe the overlay. No public Factory-contract expansion or scoped Factory clone.
- **Implementation and cleanup:** Added the minimal coroutine-local `RequestSharedData` owner. Session-error middleware now scopes its overlay for exactly the downstream request lifetime and restores prior state in `finally`. Factory render/shared reads compose global, request, and local data with the intended precedence; the singleton error write and middleware constructor dependency were removed.
-- **Validation:** Precedence, nested exceptional restoration, and deterministic two-request interleaving regressions pass; the work-unit review is signed off and the later full `view` and `foundation` audits remain pending.
-- **Revalidation:** Full `view` and `foundation` audits remain pending.
+- **Validation:** Precedence, nested exceptional restoration, and deterministic two-request interleaving regressions pass; the work-unit review and complete Foundation and View audits are signed off.
+- **Revalidation:** The complete Foundation and View audits retained the request overlay, merge precedence, exact restoration, and worker-baseline contract without change.
### Shared finding `filesystem-01`: truthful filesystem construction boundary
@@ -248,11 +248,12 @@ Append package entries in checklist order. Keep each entry compact but complete
| `reflection-06` | Improvement | Improvement | High | Public `lazy()` and `proxy()` helpers were ported without their current upstream runtime tests, type assertions, or user documentation | Port the current distinct behavior/type coverage and add proportionate helper documentation |
- **Important rejected concerns:** Keep `object|string` on the shared metadata APIs: removing one measurable approximately 7–12 nanosecond normalization branch does not justify narrowing a useful shared API. Retain Laravel's deliberate `Exception`-only attribute-constructor fallback and null caching; programming `Error`s must still surface and remain uncached. Do not add relative-type resolution, reflection locks, recursive nested-trait discovery, a general callable rewrite, guards around invalid lazy-helper overload combinations, a package README without package-specific content, or a framework-wide conversion of unrelated reflection construction.
-- **Cross-package implications:** `reflection-01` affects `events` and `foundation`; `reflection-02` affects `console`, `routing`, `view`, and `foundation`; `reflection-03` affects `container`; `reflection-04` affects `container`, `di`, `support`, `queue`, and `testing`; `reflection-05` adds accurate exception documentation in `database`, `events`, and `routing`. Queue revalidation is complete; the remaining package checkboxes stay open until their own complete audits.
+- **Cross-package implications:** `reflection-01` affects `events` and `foundation`; `reflection-02` affects `console`, `routing`, `view`, and `foundation`; `reflection-03` affects `container`; `reflection-04` affects `container`, `di`, `support`, `queue`, and `testing`; `reflection-05` adds accurate exception documentation in `database`, `events`, and `routing`. Queue, Foundation, Console, Routing, and View revalidation are complete. The remaining package checkboxes stay open until their own complete audits.
- **Approved implementation boundary:** The owner approved the canonical metadata owner, deletion of the redundant internal managers, the evidence-backed public class-target narrowing, and the public helper documentation. Preserve the flexible `object|string` cache API and separate class/method arrays. Route only the existing duplicate-manager consumers and scoped same-package reflection sites through the cache; do not convert unrelated framework reflection calls. Add no lock, context state, registry, compatibility layer, or relative-type resolver.
- **Implementation:** `ClassMetadataCache` now owns canonical class and method metadata, and the duplicate container and DI managers, their dead APIs/tests, and redundant test-state resets are removed. Existing manager consumers and the scoped reflection-package sites use the canonical cache. Callable-array validation, first-parameter inference, relative return-type rejection, class-target types, exception annotations, and proxy callback documentation now match their verified behavior. The current `lazy()`/`proxy()` runtime, type, and user-documentation surfaces are complete.
- **Regression tests:** Focused coverage reproduces the invalid-first-parameter, malformed-callable-array, and unresolved-`parent` failures; preserves valid union, callable, and attribute-fallback behavior; verifies canonical class/method identity and reset semantics; and covers current `lazy()`/`proxy()` runtime and PHPStan inference behavior.
- **Later Routing revalidation:** The complete Routing audit revalidated `reflection-02` through route callable and controller metadata paths without changing Reflection source.
+- **Later View revalidation:** The complete View audit revalidated `reflection-02` through stringable closure inference and component/compiler reflection paths without changing Reflection source.
- **Performance and complexity:** The consolidation is a net deletion and retains lock-free worker-static metadata reads. Separate canonical class/method caches benchmark at least as well as the duplicate managers. Existing `object|string` normalization remains for API flexibility; no new hot-path work is introduced. Closure-first-parameter handling becomes simpler and cheaper by removing collection traversal.
- **Laravel-facing result:** Supported Laravel-facing runtime call shapes and configuration remain unchanged. Current upstream `lazy()` and `proxy()` behavior and documentation are complete, and narrowed types reject only invalid inputs.
- **Validation and review:** Focused Reflection, Container, DI, Events, Routing, Eloquent, and Foundation tests are green. Dedicated helper runtime and PHPStan type coverage, `composer analyse`, the complete `composer fix` gate, `git diff --check`, stale-reference scans, package-checklist parity, fresh self-review, independent code review, and owner pre-commit approval are complete.
@@ -1031,7 +1032,7 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Approved owner gates:** The owner approved the process-local/testing-only array maintenance driver, nullable redirect and current Laravel API additions, the truthful `Http\Kernel` contract expansion required by middleware configuration, the `0600` default for newly decrypted plaintext, and correcting verified upstream defects rather than preserving parity bugs. The Kernel contract change is documented for custom implementations. The existing worker maintenance wrapper remains a periodic same-process snapshot and does not make the array driver cross-process.
- **Important rejected concerns:** Do not add shutdown-callback registries, PHPUnit meta-fixtures, a generic finalizer, request-scoped Application/Vite clones, Carbon managers, locks, watchers, retry loops, PID incarnation tracking, publication/delete transaction services, arbitrary-stream copy transactions, SQL parsers, TTY emulation, or broad PHPStan impurity annotations. The supported paths are covered by existing lifecycle, coroutine-context, Filesystem replacement, Process, and typed-reflection primitives. Retain `Filesystem::delete()` cache clearing because the full caller set and PHP stat cache require it; use targeted cache invalidation only where raw command/native postconditions demand it.
-- **Cross-package implications and revalidation:** Foundation revalidated all carried `view-01`, `testbench-01`, `reflection-01`, `reflection-02`, `config-01`, `config-02`, `context-01`, `context-04`, `coroutine-06`, `foundation-02`, `concurrency-01`, `concurrency-03`, `di-02`, `http-02`, `filesystem-07`, `foundation-04`, `events-01`, `events-04`, `events-06`, `foundation-01`, `support-02`, `encryption-03`, `server-process-10`, `bus-03`, `bus-17`, `bus-18`, `core-01`, and `core-05` assumptions. The later Queue work completed `queue-14` and revalidated Foundation's canonical base queue configuration under `queue-29`. The Reverb work completed `reverb-24` by adding the supported server path to Foundation's canonical broadcasting client options with focused config coverage; no Foundation runtime assumption changed. The Scout work adds `foundation-17` and `foundation-18`: Scout delegates to Foundation's truthful Meilisearch waiter, deletes its duplicate Support implementation, uses a service-valid custom key with exact remote precondition coverage, and inherits exact Meilisearch and Algolia cleanup-task ownership. The later HTTP work completed `http-03` and Foundation's helper side of `routing-01`, restoring open URL parameter forwarding without adding conversion at the helper boundary. `foundation-06` requires later full Testbench revalidation; `auth-02` requires the Auth audit; and `database-03` requires the Database and Testbench audits.
+- **Cross-package implications and revalidation:** Foundation revalidated all carried `view-01`, `testbench-01`, `reflection-01`, `reflection-02`, `config-01`, `config-02`, `context-01`, `context-04`, `coroutine-06`, `foundation-02`, `concurrency-01`, `concurrency-03`, `di-02`, `http-02`, `filesystem-07`, `foundation-04`, `events-01`, `events-04`, `events-06`, `foundation-01`, `support-02`, `encryption-03`, `server-process-10`, `bus-03`, `bus-17`, `bus-18`, `core-01`, and `core-05` assumptions. The later Queue work completed `queue-14` and revalidated Foundation's canonical base queue configuration under `queue-29`. The Reverb work completed `reverb-24` by adding the supported server path to Foundation's canonical broadcasting client options with focused config coverage; no Foundation runtime assumption changed. The Scout work adds `foundation-17` and `foundation-18`: Scout delegates to Foundation's truthful Meilisearch waiter, deletes its duplicate Support implementation, uses a service-valid custom key with exact remote precondition coverage, and inherits exact Meilisearch and Algolia cleanup-task ownership. The later HTTP work completed `http-03` and Foundation's helper side of `routing-01`, restoring open URL parameter forwarding without adding conversion at the helper boundary. The View work completes Foundation-owned `view-09` and `view-24` by publishing the canonical compiler defaults and correcting `view:cache` root deduplication with focused Foundation coverage. `foundation-06` requires later full Testbench revalidation; `auth-02` requires the Auth audit; and `database-03` requires the Database and Testbench audits.
- **Upstream and documentation:** Originating Laravel implementation and documentation pull requests supplied discovery history; current local Laravel default-branch source, tests, metadata, and docs supplied the porting reference. Hypervel deliberately retains its Swoole exception renderer, coroutine-aware test lifecycle, worker-cached maintenance model, and checked TTY behavior. Authentication, configuration, database testing, deployment, HTTP testing, middleware, and queue documentation now covers every new public call shape and important runtime limitation without exposing internal choreography.
- **Implementation:** Foundation/Testbench teardown now exhausts every independent owner and clears state exactly once. External-service clients, global clocks/environment, and dump recursion are restored at their actual lifetimes. Maintenance drivers, middleware, commands, Vite, renderers, clear/link/cache/environment commands, generators, publishers, and installers check native failures and publish only validated state. Current Laravel dispatch, retry-stopping, preferred-JSON, health, HTTP testing, route-list, nullable redirect, and database-testing APIs are complete; the narrow retry callback upstream defect is corrected. Dead duplicate Scout setup, stale helpers/properties/imports, unsafe raw boundaries, false success paths, and obsolete comments are removed.
- **Regression tests:** Deterministic coverage injects failure into every teardown/termination phase, setup wrapper shape, SDK/clock/dumper/environment restoration, maintenance/cache/file publication, generator/installer/publisher operation, reflection/Blade/native read, health state, retry callback typing, and lazy database refresh boundary. It also covers coroutine isolation, process-local array semantics, current Laravel public API call shapes, custom dispatch construction, Kernel contract consumers, mode preservation, old-artifact survival, and exact throwable precedence.
@@ -1805,3 +1806,32 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Laravel-facing result:** Supported Validation signatures, named arguments, Factory/Validator behavior, protected extension paths, FormRequest responses, facade methods, and rule APIs remain compatible or are restored to current Laravel. The deliberate strict-membership and malformed-date differences remove coercive or nondeterministic behavior rather than preserving a known defect through compatibility machinery.
- **Validation and review:** Every changed test file and the complete Validation unit/integration group are green. The authoritative `composer fix` gate passed formatting, both PHPStan configurations, the full parallel suite, Testbench package mode, and dogfood. `git diff --check`, metadata/facade regeneration, stale-symbol scans, and fresh caller/callee, lifecycle, API, hot-path, dead-code, overengineering, and independent code review are complete.
- **Assessment:** The implementation fixes each accepted finding at its owning boundary and removes stale metadata and unsafe fast-path assumptions without adding speculative machinery. No accepted defect, TODO, workaround, unintended Laravel API break, or meaningful performance regression remains.
+
+### Complete View correctness, lifecycle, and current parity
+
+- **Status and inspected surface:** Complete; implementation, focused validation, the authoritative gate, fresh self-review, and independent code review are signed off. The audit covered every View source and test file; Foundation configuration and `view:cache`; Support facade metadata; Mail's Markdown consumer; Boost documentation; split metadata; current Laravel View source, tests, documentation, and full-app fixtures; and carried `view-01` and `reflection-02`. The detailed design is recorded in [`2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md`](2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md).
+
+| Findings | Final decision |
+|---|---|
+| `view-02` | Flush slot and slot-stack state so rendered slot content and `ComponentSlot` objects are not retained for the rest of the request. |
+| `view-08` | Flush loop frames so a failed render cannot corrupt loop depth and parent metadata in the next render. |
+| `view-03`, `view-05`, `view-06`, `view-14`, `view-21`, `view-22` | Correct style merging, conditional stacks, nested component resolution, end-directive signatures, enum component names, and unnamed slots. |
+| `view-04`, `view-26`, `view-27` | Publish exact inline-template contents atomically, bound each retained cache key's bytes, and replace the reserved namespace hint. |
+| `view-07`, `view-15`, `view-16`, `view-23`, `view-28`, `view-29`, `view-31`, `view-36` | Give transient compiler state exact compile-pass/coroutine ownership, restore extension contracts, isolate temporary echo formats, and complete strict/current maintenance. |
+| `view-09`, `view-17`, `view-18` | Publish canonical compiler defaults, restore provider extension visibility, and document worker-lived mutators. |
+| `view-10`, `view-11`, `view-19`, `view-20`, `view-32` | Correct split dependencies, remove dead validation middleware, document public differences and compiled-view freshness, and break the View→Foundation dependency cycle. |
+| `view-12`, `view-13`, `view-25` | Port bounded current maintenance, truthful named-argument/PHPDoc surfaces, and current full-app integration coverage. |
+| `view-24` | Deduplicate deployment-time cache roots by canonical directory boundary while preserving prefix siblings and filesystem roots. |
+| `view-30`, `view-35` | Memoize only verified-fresh compiled views and exhaustively release compiled-path ownership. |
+| `view-33`, `view-34` | Render mutable View section content before storage and retain Laravel's lazy yield-default behavior. |
+
+- **Architecture and worker ownership:** Factory, BladeCompiler, EngineResolver, component metadata, compiled-template names, and verified freshness remain worker-lived. Render state and transient compiler state remain coroutine-local. Every compile pass initializes its current-section owner, so sequential or cross-instance compilers in one coroutine cannot leak a prior section into standalone `@parent`; no compiler stack or clone was added. Inline publication stays lock-free at the existing atomic Filesystem replacement boundary.
+- **Correctness and parity:** Failed renders now clear loop frames before they can corrupt later loop metadata. Slot cleanup closes a retained-state leak—no later render reads stale slot state, but rendered slot HTML and `ComponentSlot` objects no longer remain in coroutine context for the rest of the request. Components resolve current nested, slot, enum, attribute, and directive forms. Inline templates recognize legitimate empty files and repair incomplete files. Compiled views honor disabled caching, recover after verified-file deletion, and always pop diagnostic path state. Provider visibility, named arguments, protected compiler contracts, facades, dependencies, and current integration scenarios are restored. `@elsePushIf`, exact inline publication, loop cleanup, directory-boundary roots, worker/coroutine echo ownership, render-before-store section content, and exhaustive path cleanup intentionally correct behavior beyond current Laravel without removing a useful Laravel API.
+- **Important rejected concerns and closed limitations:** Do not add publication locks, retries, polling, file watchers, cache eviction, a section stack, compiler/Factory clones, request-scoped services, render-state snapshots, placeholder registries, or compatibility aliases. Dynamic inline templates still retain one fixed-size key and compiled file per distinct template; bounding cardinality would require unjustified eviction machinery. The opaque parent-placeholder salt is immutable worker state and needs no test reset because stored sections are already cleared. Compiled output deliberately names the base Factory, so overriding the protected salt on a Factory subclass is not a complete cached-template extension point. `@parent` inside a separately compiled include no longer depends on cold-cache ordering; that unsupported pattern consistently uses the include's empty compile-pass placeholder instead of freezing whichever outer section happened to compile first.
+- **Cross-package revalidation:** The complete View surface retains `view-01` request overlay precedence/restoration and revalidates `reflection-02` through closure inference and component/compiler reflection. Foundation owns `view-09` and `view-24`; its canonical config and command tests cover both. View removes its false Foundation/Validation requirements while retaining optional Foundation directives through Composer `suggest`. The byte-identical Translation stringable boundary is separately owned as `translation-10`; View's sibling boundary is complete under `view-28` without editing the active Translation worktree.
+- **Implementation and cleanup:** Compiler, layout, component, engine, provider, Factory, finder, and package boundaries now use their final ownership model. The obsolete footer property, parent-placeholder context map/getter, validation middleware, duplicate defaults, stale dependencies, superseded compiled files through marker `v3`, and inaccurate docs/comments are removed. Facades are generated from the corrected concrete methods rather than edited by hand.
+- **Regression tests:** Counterfactual coverage proves slot/loop cleanup, exact inline publication—including the complete-file no-write branch—and bounded keys, nested/unnamed/enum components, conditional-stack parsing, sequential and concurrent compile ownership, nested/failing/concurrent echo overrides including Mail, footer/end-directive contracts, lazy defaults, verified-fresh cache behavior, exhaustive compiled-path cleanup, provider/config/metadata/facade contracts, root-safe cache traversal, and current full-app View behavior. The protected slot-context assertion pins retained-state cleanup because no output path rereads stale slots. The Xdebug-only `ParseError` branch is documented at the source boundary without adding a synthetic test seam; ordinary incomplete expressions cover the trailing-token guard.
+- **Performance and complexity:** The component path adds one xxh128 digest per uncached lookup and filesystem checks only before the existing static name cache is populated. Compiler isolation adds constant-time context operations only at compilation boundaries; echo overrides add one context read per escaped echo during compilation, not rendering. Parent placeholders use a fast digest instead of retaining a per-section map. Fresh compiled views retain their one-check-per-worker fast path; only the render immediately after a stale compile performs one additional freshness check next time. Cache-root work is deployment-only. No request path gains a lock, retry, yield, poll, watcher, container loop, network call, serialization layer, or unbounded new state.
+- **Laravel-facing result:** Supported View and Blade method names, named arguments, provider extension points, protected compiler hooks, component syntax, facades, configuration, and render behavior are compatible or restored to current Laravel. Alias-first `Blade::component()` and render-before-store View section content remain the two documented public Hypervel differences. The removed `getParentPlaceholder()` was a superseded Hypervel divergence, not a current Laravel API.
+- **Validation and review:** Changed test files and the combined View, Integration/View, Foundation View, Mail View, facade, and metadata coverage are green. Facades and split metadata have been regenerated and checked. After the final self-review corrections, the authoritative `composer fix` gate passed formatting, both PHPStan configurations, the complete parallel suite, Testbench package mode, and dogfood. Review-amendment coverage, both PHPStan configurations, formatting, and `git diff --check` are green; independent review verified the final tree and signed off with no remaining finding.
+- **Assessment:** Every accepted View finding is implemented at its owning boundary without a local workaround, speculative abstraction, stale compatibility path, meaningful hot-path regression, or unintended Laravel API break. No View TODO, deferred defect, or open workflow step remains.
diff --git a/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md b/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
new file mode 100644
index 000000000..4d9eb6c5c
--- /dev/null
+++ b/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
@@ -0,0 +1,558 @@
+# View Correctness, Lifecycle, and Current Parity
+
+## Status and scope
+
+**Status:** Complete; implementation, validation, self-review, and code review signed off.
+
+Complete the View audit against:
+
+- Hypervel `0.4` at this branch's base;
+- current Laravel framework source, View tests, and full-app View integration fixtures under `examples/laravel/framework`;
+- the completed `view-01` request-shared-data and `reflection-02` callable-shape decisions;
+- current View, Foundation configuration and console commands, Support facade metadata, Boost documentation, split-package metadata, Mail factory clones, and framework test-state cleanup.
+
+This is a correction and parity pass, not a redesign. Preserve Hypervel's worker-singleton Factory/compiler architecture, coroutine-local render and compile state, worker-lived immutable metadata and freshness caches, lock-free component creation, alias-first `Blade::component()` registration, strict string compiler paths, and request-shared-data overlay. No accepted change adds a lock, watcher, retry, registry, request-scoped Factory/compiler, render-state snapshot, eviction policy, or compatibility shim.
+
+No useful Laravel API is removed or narrowed. The restored provider, compiler, layout, dynamic-component, named-argument, and PHPDoc surfaces improve parity. The intentional alias-first API and strict path model remain documented Hypervel differences. The `@elsePushIf` repair and failed-render loop cleanup correct defects that current Laravel also carries.
+
+### Approved tradeoffs
+
+| Findings | Benefit | Cost | Rejected machinery |
+|---|---|---|---|
+| `view-04`, `view-26`, `view-27` | Publish complete inline templates, bound retained key bytes, and keep the reserved namespace bounded. | One xxh128 digest per cache lookup; filesystem replacement only on first/incomplete publication. | Locks, retries, polling, a publication registry, LRU/TTL, or a second render path. |
+| `view-07`, `view-08`, `view-29`, `view-33`, `view-35` | Isolate transient compiler/render state and clean it deterministically. | Constant-time coroutine-context reads/writes at existing compile, render, or cleanup boundaries. | Compiler/Factory cloning, request-scoped services, or full-state transactions. |
+| `view-24` | Avoid duplicate deployment-time compilation below nested roots. | A bounded in-memory path comparison in `view:cache`; no request-path cost. | Persistent path indexes or filesystem watchers. |
+| `view-25` | Prove provider/facade/full-app interactions currently absent from package tests. | Test-only fixtures and execution. | A View-specific harness or one oversized integration test. |
+
+## Post-compaction and design rules
+
+After compaction, re-read `AGENTS.md` and this plan in full before editing. Re-open the active source and tests; summaries are navigation only.
+
+## What this audit is not
+
+This audit is not permission to add defensive machinery for every imaginable failure. Do not add an abstraction, state machine, retry loop, configurable timeout, registry, mutex, context slot, cache, or compatibility API merely because it sounds robust.
+
+Complexity must pay for itself with at least one of:
+
+- a demonstrated failure;
+- a complete source trace proving a realistic vulnerable schedule;
+- a clear general capability with real consumers and owner approval;
+- deletion of greater or riskier complexity elsewhere.
+
+Typical Laravel lifecycle semantics define the supported contract. A package that intentionally relies on model events, middleware, listeners, transactions, or another documented mechanism is not defective merely because userland can explicitly bypass that mechanism. Do not build a parallel enforcement path for `withoutEvents()`, raw database writes, disabled middleware, direct transport access, or comparable deliberate bypasses unless the public contract explicitly promises behavior through that bypass.
+
+Underengineering is equally a failure. Fix every verified defect completely at its lowest owning boundary, never with a partial fix or a local patch over a broken shared contract, and always surface meaningful evidence-backed improvements rather than dropping them to avoid effort. Restraint applies to speculative machinery and cosmetic change, not to complete fixes or worthwhile opportunities.
+
+Do not treat an upstream difference as a bug without tracing it. Do not treat upstream parity as proof of correctness. A real Hypervel defect remains a defect when Laravel, Hyperf, Symfony, or an SDK has the same hole.
+
+The audit categories are discovery lenses, not boundaries around what may be corrected. Any genuine issue discovered while auditing, implementing, testing, or reviewing must be investigated, assigned to its lowest owning boundary, and taken through the applicable consensus, implementation, validation, review, and approval workflow—even when it is outside the current package, initial taxonomy, or changed diff. Do not dismiss a verified issue as unrelated or defer it merely to preserve package order. This rule applies only after the evidence threshold is met; it does not turn speculative concerns, deliberate bypasses, unsupported use, or contract violations into work.
+
+## Audit principles
+
+### 1. Verify before changing
+
+A suspicious pattern is not an actionable finding until the audit establishes:
+
+- the exact file and symbol;
+- every relevant caller and callee across `src/` and `tests/`;
+- the state or resource owner;
+- the initialization, commit, use, and cleanup boundaries;
+- a realistic production or test failure schedule;
+- why current guards and tests do not prevent it;
+- sibling implementations and same-family sites;
+- relevant upstream behavior;
+- the lowest correct fix boundary;
+- a regression strategy;
+- the performance and complexity effect of the proposed fix.
+
+Use a focused probe when source reasoning cannot settle native or scheduler behavior. Do not repeatedly run the full suite hoping to reproduce a rare flake.
+
+### 2. Fix the lowest inconsistent contract
+
+Do not add local compensation when a shared lower-level contract is wrong. A caller catch is not enough when a typed filesystem method can return `false`; a per-consumer spawn catch is not enough when Engine exposes an ambiguous spawn contract; a proxy workaround is not enough when pool ownership is undefined.
+
+After changing a lower-level contract, re-audit every affected caller and revisit completed packages that depend on it. Record cross-references in both the owning package and each affected package ledger entry.
+
+### 3. Make ownership explicit
+
+The component that acquires or registers a resource records the exact handle and releases that exact handle. Cleanup must not reconstruct identity from mutable state when the original handle can be retained.
+
+Examples include coroutine IDs, timer IDs, process IDs plus incarnation checks, listener callbacks, pool leases, subscriber objects, stream handles, temporary filenames, signal watcher IDs, and channel tokens.
+
+### 4. Make creation transactional
+
+If code reserves capacity or publishes state before a later operation can fail, it must either finish creation or roll back every earlier change. Do not expose half-initialized objects, registered-but-dead pools, leaked wait-group counts, or published runtime paths without their cleanup owner.
+
+### 5. Make cleanup exhaustive
+
+Independent cleanup steps run even when an earlier step fails. The earliest operation or cleanup failure remains primary. Cleanup failures must not corrupt bookkeeping, skip unrelated cleanup, or turn a successful ownership transfer into a reported failure.
+
+### 6. Bound only external progress
+
+Use deadlines where progress depends on a process, socket peer, lock owner, IPC child, or external service that can disappear. Do not add arbitrary timeouts to ordinary internal coroutine joins once successful creation and ownership guarantee completion.
+
+### 7. Preserve hot-path quality
+
+For every fix, inspect:
+
+- additional allocations;
+- container or facade resolutions;
+- locking and atomics;
+- hashing and serialization;
+- new yields or sleeps;
+- retries and polling;
+- logging or exception construction;
+- retained worker memory;
+- cache invalidation and eviction.
+
+A correctness guard on a cold failure path has a different cost from a new lock or resolver on every request. State the difference explicitly.
+
+Any proposed change with a measured or source-proven hot-path regression requires explicit owner approval before implementation, even when it fixes a defect. Present the expected frequency and magnitude, the evidence, and the viable alternatives. Do not hide an unavoidable tradeoff inside a general correctness claim.
+
+Performance improvements must provide a meaningful practical benefit after accounting for code complexity and divergence from upstream. Measure representative behavior where practical. Always surface an evidence-backed opportunity to the owner, but do not implement it without approval; a micro-optimization within measurement noise is neither a reason to diverge nor an actionable finding.
+
+### 8. Remove superseded design completely
+
+When a fix changes the owning model, delete obsolete helpers, callbacks, properties, config keys, comments, tests, and documentation. Do not leave a compatibility path or comment describing behavior that no longer exists. Preserve intentional upstream comments unless the new design makes them incorrect.
+
+### 9. Treat remediation patterns as candidates
+
+The established patterns later in this plan are a vocabulary, not a lookup table. Choose among per-call parameters, immutable values, scoped bindings, cloning, CoroutineContext, factories, explicit ownership, static reset, or resource teardown only after proving the real lifetime and owner.
+
+### 10. Reject speculative complexity
+
+Record low-confidence concerns under rejected or unresolved analysis. Do not implement them. Surface every evidence-backed, meaningful non-defect improvement to the owner with its benefit, cost, and alternatives, then stop for explicit approval. This requirement exists to keep worthwhile opportunities visible, not to discourage finding them.
+
+## Research and final decisions
+
+### State and ownership
+
+`ViewServiceProvider` creates worker-singleton Factory, Blade compiler, and EngineResolver instances. Boot configuration, resolved engines, finder hints, component metadata, inline-template names, and verified compiled-view freshness are intentionally worker-lived. Sections, stacks, components, slots, loops, fragments, translations, compiled paths, and transient compiler state are coroutine-local. Component instances remain fresh per render through `buildWith()`.
+
+`Coroutine::fork()` copies ordinary objects by reference. A `View` stored as section content must therefore render before entering `CoroutineContext`; a default passed to `yieldContent()` is only a local value and must render only when the section is absent.
+
+The package owns filesystem publication and output-buffer cleanup, but no persistent socket, process, timer, channel, or lease. Filesystem replacement is the existing transaction boundary for inline templates. `PhpEngine` unwinds output buffers and formats exceptions before `CompilerEngine::get()` returns, so a surrounding `finally` can always pop the compiled path without losing diagnostics.
+
+### Upstream and compatibility
+
+The accepted maintenance set was checked against current Laravel source, unit tests, `tests/Integration/View`, and the originating changes listed in the audit evidence. Preserve Hypervel's worker-lived freshness/component metadata, coroutine-local request-shared-data overlay (`view-01`), alias-first component registration, and strict string compiler paths. The intentional implementation divergences relevant to this work are exact-size inline publication (`view-04`), corrected `@elsePushIf` parsing (`view-05`), loop cleanup (`view-08`), direct placeholder hashing without Laravel's memo map (`view-15`), directory-boundary cache-root comparison (`view-24`), reserved-namespace replacement (`view-27`), split worker/coroutine echo formats (`view-29`), verified-fresh-only memoization (`view-30`), render-before-store section content (`view-33`), exhaustive compiled-path cleanup (`view-35`), and strict loop/token identity (`view-36`). `view-34` removes a divergence by restoring Laravel's lazy default rendering. These are internal correctness or lifecycle differences and do not remove a useful Laravel-facing API.
+
+The inherited compiled Factory FQCN means overriding `parentPlaceholderSalt()` on a Factory subclass is not complete for cached `@parent` output. Do not claim otherwise and do not invent a dynamic compiler indirection in this work.
+
+### Findings
+
+| ID | Result |
+|---|---|
+| `view-01` | Revalidate the completed coroutine-local request-data overlay; no source change. |
+| `view-02` | Flush slot and slot-stack state with component state. |
+| `view-03` | Terminate a nonempty default `style` before concatenation. |
+| `view-04` | Publish missing, empty, or partial inline templates with `Filesystem::replace()`. |
+| `view-05` | Parse comma-bearing `@pushIf` and `@elsePushIf` expressions like Laravel while preserving ordinary two-argument output. |
+| `view-06` | Resolve `Namespace\Accordion\Accordion` for nested default components. |
+| `view-07` | Scope the compiler's current section name to one coroutine and compile pass, and define standalone `@parent` as the empty-section placeholder. |
+| `view-08` | Flush loop frames through `Factory::flushState()`. |
+| `view-09` | Put the four shipped compiler defaults in Foundation's View config and remove duplicate provider fallbacks. |
+| `view-10` | Declare direct Symfony dependencies and View package provenance. |
+| `view-11` | Delete dead `ValidationExceptionHandle` and remove the unused Validation dependency. |
+| `view-12` | Port the bounded current-Laravel readability/reflection maintenance and missing loop coverage. |
+| `view-13` | Restore named-argument and PHPDoc accuracy. |
+| `view-14` | Accept `?string $expression` on the three protected end-directive compilers. |
+| `view-15` | Restore static `parentPlaceholder()` and protected static salt; remove `getParentPlaceholder()`, context map, and stale compiled output via marker `v3`. |
+| `view-16` | Restore one-argument protected `addFooters()` using context-owned footer state. |
+| `view-17` | Restore public visibility on seven provider registration methods. |
+| `view-18` | Document worker-lived mutators at their concrete boundaries. |
+| `view-19` | Record the approved alias-first component API as a Laravel difference. |
+| `view-20` | Document first-use-per-worker compiled-view freshness accurately. |
+| `view-21` | Accept `BackedEnum|string` dynamic component names. |
+| `view-22` | Default unnamed slots to `slot`. |
+| `view-23` | Catch Xdebug-originated `ParseError` in `hasEvenNumberOfParentheses()`. |
+| `view-24` | Deduplicate nested `view:cache` roots without conflating path-prefix siblings. |
+| `view-25` | Port the current full-app View integration surface and fixtures. |
+| `view-26` | Hash inline-template cache keys to bound retained bytes. |
+| `view-27` | Replace, rather than append, the reserved `__components` namespace. |
+| `view-28` | Narrow `stringable()` to its actually supported `Closure|string` domain; route the Translation twin separately. |
+| `view-29` | Make boot echo format worker-lived and callback overrides coroutine-local and nest-safe. |
+| `view-30` | Memoize only verified-fresh compiled paths, restoring `view.cache=false` and first-render deletion recovery. |
+| `view-31` | Delete the superseded footer property and type/relocate `pushFooter()`. |
+| `view-32` | Remove the View→Foundation cycle; suggest Foundation for optional Vite/fonts directives. |
+| `view-33` | Preserve and document render-before-store for View-valued section content copied across coroutine contexts. |
+| `view-34` | Do not render a default View when `yieldContent()` finds an existing section. |
+| `view-35` | Pop the compiled-path stack in `finally` across success and failure. |
+| `view-36` | Use strict identity for the six loose loop/token comparisons in View source. |
+
+## Implementation design
+
+### 1. Complete render-state cleanup (`view-02`, `view-08`)
+
+Keep cleanup with the traits that own the context keys:
+
+```php
+protected function flushComponents(): void
+{
+ CoroutineContext::set(static::COMPONENT_STACK_CONTEXT_KEY, []);
+ CoroutineContext::set(static::COMPONENT_DATA_CONTEXT_KEY, []);
+ CoroutineContext::set(static::CURRENT_COMPONENT_DATA_CONTEXT_KEY, []);
+ CoroutineContext::set(static::SLOTS_CONTEXT_KEY, []);
+ CoroutineContext::set(static::SLOT_STACK_CONTEXT_KEY, []);
+}
+
+protected function flushLoops(): void
+{
+ CoroutineContext::set(static::LOOPS_STACK_CONTEXT_KEY, []);
+}
+```
+
+Call `flushLoops()` from the existing `Factory::flushState()` sequence. Add one real failed-render regression for loop cleanup and the upstream slot reset regression. Do not snapshot state around every render.
+
+Loop cleanup prevents stale depth and parent metadata from affecting a later render. Slot cleanup instead bounds retained request memory: every component creation overwrites its readable slot entry, but without the flush, rendered slot HTML and `ComponentSlot` objects remain in coroutine context for the rest of the request.
+
+### 2. Correct attributes, directives, component resolution, and public types (`view-03`, `view-05`, `view-06`, `view-13`, `view-14`, `view-21`, `view-22`)
+
+Normalize both sides of a style merge without changing non-string appendable defaults:
+
+```php
+if ($key === 'style') {
+ $value = Str::finish($value, ';');
+
+ if (is_string($defaultsValue) && $defaultsValue !== '') {
+ $defaultsValue = Str::finish($defaultsValue, ';');
+ }
+}
+```
+
+Share only the parsing rule genuinely used twice:
+
+```php
+protected function parseConditionalStackExpression(string $expression): array
+{
+ $segments = explode(',', $this->stripParentheses($expression));
+
+ if (count($segments) > 2) {
+ $stack = array_pop($segments);
+
+ return [implode(',', $segments), trim($stack)];
+ }
+
+ return $segments;
+}
+```
+
+Use it in both conditional stack compilers. This retains the leading whitespace in Laravel's ordinary two-argument compiled output and preserves fail-loud behavior for malformed one-argument directives. Port Laravel's two multi-comma `@pushIf` regressions and add the equivalent `@elsePushIf` case. Add the nested conventional candidate after the direct class candidate misses:
+
+```php
+if (class_exists($class = $class . '\\' . Str::afterLast($class, '\\'))) {
+ return $class;
+}
+
+return null;
+```
+
+Port the upstream unnamed-slot fallback and enum boundary without a resolver:
+
+```php
+$name = $this->stripQuotes(
+ $matches['inlineName'] ?: $matches['name'] ?: $matches['boundName']
+) ?: "'slot'";
+
+public string $component;
+
+public function __construct(BackedEnum|string $component)
+{
+ $this->component = (string) enum_value($component);
+}
+```
+
+Rename `FileViewFinder::find()` to `$view` and View's ArrayAccess parameters to `$offset`; add the conditional `ComponentAttributeBag::data()` return PHPDoc and `ComponentSlot::hasActualContent()` exception PHPDoc. Give `compileEndsession()`, `compileEnderror()`, and `compileEndcontext()` the nullable expression argument their dispatcher already supplies. Add direct output, reflection, and subclass compatibility tests.
+
+### 3. Make inline component templates bounded and transactional (`view-04`, `view-26`, `view-27`)
+
+Use a fixed-size cache key and keep cache cardinality/values unchanged:
+
+```php
+$key = hash('xxh128', sprintf('%s::%s', static::class, $contents));
+```
+
+At publication, replace the reserved hint and accept a file only when its exact size matches the content:
+
+```php
+$container = Container::getInstance();
+$files = $container->make(Filesystem::class);
+$directory = $container->make('config')->string('view.compiled');
+$viewFile = $directory . '/' . hash('xxh128', $contents) . '.blade.php';
+
+$factory->replaceNamespace('__components', $directory);
+
+if (! $files->exists($viewFile) || $files->size($viewFile) !== strlen($contents)) {
+ $files->ensureDirectoryExists($directory);
+ $files->replace($viewFile, $contents);
+}
+```
+
+This recognizes legitimate empty templates and repairs zero-byte or partial artifacts. Cover an empty template, a truncated template, atomic publication, fixed-size keys without raw contents, repeated reserved-namespace replacement, and normal cache hits. Do not add locks or eviction.
+
+### 4. Isolate compile-pass state and restore compiler extension contracts (`view-07`, `view-12`, `view-16`, `view-23`, `view-28`, `view-29`, `view-31`, `view-36`)
+
+Declare `protected const LAST_SECTION_CONTEXT_KEY = '__view.last_section';` on `CompilesLayouts`, which exclusively owns the transient section state like the other compiler concerns own their context keys. Replace `$lastSection` with that context entry and define a standalone `@parent` as the empty-section placeholder instead of an incidental `TypeError`:
+
+```php
+CoroutineContext::set(static::LAST_SECTION_CONTEXT_KEY, '');
+
+CoroutineContext::set(static::LAST_SECTION_CONTEXT_KEY, trim($expression, "()'\" "));
+
+$lastSection = CoroutineContext::get(static::LAST_SECTION_CONTEXT_KEY, '');
+$escapedLastSection = strtr($lastSection, ['\\' => '\\\\', "'" => "\\'"]);
+```
+
+Initialize the key at the start of every `compileString()` call, beside the footer reset. This prevents one sequential compile, including a compile on another compiler instance in the same coroutine, from supplying the section for a later standalone `@parent`. Prove deterministic interleaving with a yielding custom directive and strengthen the standalone regression by compiling a named section first on the same compiler. Preserve the existing single-slot compile-pass semantics within one coroutine; this finding isolates sibling compilations and compile-pass ownership rather than adding a reentrant compiler stack. No lock, instance discriminator, compiler clone, or reset hook is permitted.
+
+An `@parent` inside an included partial previously depended on whichever section happened to be current when that partial was first compiled. Precompilation or a different cache order could therefore freeze a different placeholder. This unsupported, cache-order-dependent pattern is not preserved with extra state machinery; each separately compiled template owns its own section context.
+
+Restore footer ownership and delete the now-dead property:
+
+```php
+protected function addFooters(string $result): string
+{
+ $footers = CoroutineContext::get(static::FOOTER_CONTEXT_KEY, []);
+
+ return ltrim($result, "\n") . "\n" . implode("\n", array_reverse($footers));
+}
+
+/**
+ * Push a footer onto the stack.
+ */
+protected function pushFooter(string $footer): void
+{
+ $footers = CoroutineContext::get(static::FOOTER_CONTEXT_KEY, []);
+ $footers[] = $footer;
+ CoroutineContext::set(static::FOOTER_CONTEXT_KEY, $footers);
+}
+```
+
+Place `pushFooter()` immediately after `addFooters()` so the ownership stays together.
+
+Update `BladeCompiler::compileString()`'s existing guarded call site to use the restored one-argument contract while retaining the local footer guard:
+
+```php
+$footers = CoroutineContext::get(static::FOOTER_CONTEXT_KEY, []);
+
+if (count($footers) > 0) {
+ $result = $this->addFooters($result);
+}
+```
+
+The echo format has a worker default and optional coroutine override:
+
+```php
+protected string $echoFormat = 'e(%s)';
+
+public function setEchoFormat(string $format): void
+{
+ $this->echoFormat = $format;
+}
+
+protected function getEchoFormat(): string
+{
+ return CoroutineContext::get(static::ECHO_FORMAT_CONTEXT_KEY, $this->echoFormat);
+}
+
+public function usingEchoFormat(string $format, callable $callback): string
+{
+ $hadOverride = CoroutineContext::has(static::ECHO_FORMAT_CONTEXT_KEY);
+ $previous = CoroutineContext::get(static::ECHO_FORMAT_CONTEXT_KEY);
+
+ CoroutineContext::set(static::ECHO_FORMAT_CONTEXT_KEY, $format);
+
+ try {
+ return call_user_func($callback);
+ } finally {
+ if ($hadOverride) {
+ CoroutineContext::set(static::ECHO_FORMAT_CONTEXT_KEY, $previous);
+ } else {
+ CoroutineContext::forget(static::ECHO_FORMAT_CONTEXT_KEY);
+ }
+ }
+}
+```
+
+`usingEchoFormat()` must not call `setEchoFormat()`: it owns only the temporary coroutine override. `withDoubleEncoding()` and `withoutDoubleEncoding()` update the worker default through `setEchoFormat()`. The only format read is in `CompilesEchos`, so this adds one context lookup per escaped echo at compile time, not per render. Mail's `Markdown` is the only external consumer and requires the override across nested compilation. `$echoFormat` needs no static cleanup because the test container owns and discards the singleton instance. Add boot-outside/request-inside, nested, exceptional, Mail nesting, and deterministic sibling-coroutine tests.
+
+Narrow `stringable()` to its implementable contract:
+
+```php
+public function stringable(Closure|string $class, ?callable $handler = null): void
+```
+
+Keep the two supported registration forms and regenerate facade metadata from the corrected concrete method; do not hand-edit generated signatures. Revalidate and route the byte-identical Translation issue as `translation-10`; do not edit the concurrent Translation worktree from this branch.
+
+Port the bounded upstream maintenance in place: direct empty-array checks, explicit `implode('', ...)`, direct `Stringable` use, current component reflection/filtering, alias derivation, and the two missing uncountable-loop tests. Make the complete loose-comparison inventory strict: `ManagesLoops` uses `===` for initial `last`, incremented `first`, and incremented `last`; `BladeCompiler::parseToken()` uses `=== T_INLINE_HTML`; and both parenthesis-token comparisons use `===`. Catch only `ParseError` around `token_get_all()` with a concise WHY naming Xdebug. Do not add a synthetic runtime seam.
+
+### 5. Restore the parent-placeholder and section-content model (`view-15`, `view-33`, `view-34`)
+
+Use one immutable worker salt and no section map:
+
+```php
+protected static ?string $parentPlaceholderSalt = null;
+
+public static function parentPlaceholder(string $section = ''): string
+{
+ // Deliberately not memoized: this is a pure function of one immutable salt.
+ return '##parent-placeholder-' . hash('xxh128', static::parentPlaceholderSalt() . $section) . '##';
+}
+
+protected static function parentPlaceholderSalt(): string
+{
+ return static::$parentPlaceholderSalt ??= Str::random(40);
+}
+```
+
+Remove `PARENT_PLACEHOLDER_CONTEXT_KEY` and `getParentPlaceholder()`. Use `static::parentPlaceholder()` in the layout trait and emit `\Hypervel\View\Factory::parentPlaceholder(...)` from compiled output. Regenerate the View facade and update tests. Bump the Compiler marker from `v2` to `v3` so existing compiled files cannot call the removed method, and update every marker-dependent expectation in `ViewBladeCompilerTest`; add no alias or manual-clear instructions. The salt needs no reset because it is opaque, immutable worker configuration and all stored sections are already reset.
+
+Keep the existing eager conversion only where a mutable View becomes stored section content, and add a concise WHY at that ownership boundary:
+
+```php
+$this->extendSection($section, $content instanceof View ? $content->render() : e($content));
+```
+
+Avoid rendering an unused yield default:
+
+```php
+$sections = CoroutineContext::get(static::SECTIONS_CONTEXT_KEY, []);
+$sectionContent = isset($sections[$section])
+ ? $sections[$section]
+ : ($default instanceof View ? $default->render() : e($default));
+```
+
+Keep `isset` semantics. Prove that stored section content does not share a mutable View across copied contexts, an absent default still renders, and a present section does not render the default or produce its stack side effects. Document only the section-storage timing difference.
+
+### 6. Preserve compiled-view cache semantics and diagnostics (`view-30`, `view-35`)
+
+Memoize only a path verified fresh before evaluation:
+
+```php
+if (! isset(static::$compiledOrNotExpired[$path])) {
+ if ($this->compiler->isExpired($path)) {
+ $this->compiler->compile($path);
+ } else {
+ static::$compiledOrNotExpired[$path] = true;
+ }
+}
+```
+
+Remove the unconditional memo after evaluation. This makes `view.cache=false` compile every time, retains the one-check-per-worker hot path for fresh/precompiled views, pays one extra freshness check only after a first stale compile, and lets the existing missing-file recovery handle deletion after the first fresh check.
+
+Wrap the entire path ownership in `finally`:
+
+```php
+$this->pushCompiledPath($path);
+
+try {
+ // freshness, evaluation, and missing-file recovery
+ return $results;
+} finally {
+ $this->popCompiledPath();
+}
+```
+
+Add cache-enabled hot-path, cache-disabled repeated compilation, first-render deletion recovery, successful stack cleanup, and caught-failure stack cleanup tests. Keep exception formatting inside `PhpEngine`, where the current path remains visible before `finally` runs.
+
+### 7. Make configuration and provider contracts explicit (`view-09`, `view-17`, `view-18`)
+
+Add the canonical Foundation defaults:
+
+```php
+'relative_hash' => false,
+'cache' => true,
+'compiled_extension' => 'php',
+'check_cache_timestamps' => true,
+```
+
+Read them without duplicate fallbacks in `ViewServiceProvider`. Make `registerFactory()`, `registerViewFinder()`, `registerBladeCompiler()`, `registerEngineResolver()`, `registerFileEngine()`, `registerPhpEngine()`, and `registerBladeEngine()` public; keep `createFactory()` protected.
+
+Add concise boot/test lifecycle warnings to `EngineResolver::register()`/`forget()` and `BladeCompiler::withoutComponentTags()`, `stringable()`, `setEchoFormat()`, `withDoubleEncoding()`, and `withoutDoubleEncoding()`. Do not warn on the `Factory::getFinder()` getter and do not make boot registries coroutine-local.
+
+### 8. Correct package boundaries and public documentation (`view-10`, `view-11`, `view-19`, `view-20`, `view-32`)
+
+In `src/view/composer.json`:
+
+```json
+"require": {
+ "symfony/http-foundation": "^8.1",
+ "symfony/http-kernel": "^8.1"
+},
+"suggest": {
+ "hypervel/foundation": "Required for the @vite, @viteReactRefresh, and @fonts directives."
+}
+```
+
+Remove `hypervel/foundation` and `hypervel/validation` from `require`. View references `Vite::class` only to emit generated code; Foundation remains an optional integration, matching Laravel's split boundary. Delete only `Middleware/ValidationExceptionHandle.php`; retain `ShareErrorsFromSession`, which Foundation registers.
+
+Add a View package metadata test pinning both Symfony constraints, both deliberate Hypervel omissions, and View provider discovery in root/split manifests. Keep the README minimal and in the prescribed order: package header and badge; `Documentation: https://hypervel.org/docs/views`; `Differences From Laravel` for alias-first registration and eager rendering only when a View becomes stored section content; then `Ported from: https://github.com/laravel/framework/tree/13.x/src/Illuminate/View`. Record optional Foundation integration only through Composer's `suggest` metadata, not as a public difference. Update Boost View docs to state that compiled freshness is checked on first use per worker, `view:cache` belongs in deployment, and `view:clear` is needed after local edits with live workers. Use Laravel-style user prose and do not expose internal context/cache implementation beyond what users need.
+
+### 9. Deduplicate cache roots correctly and port integration coverage (`view-24`, `view-25`)
+
+Canonicalize roots where possible, remove trailing separators before deduplication while preserving filesystem roots, and reject only a true descendant with a directory boundary:
+
+```php
+$paths = $paths
+ ->map(function (string $path): string {
+ $path = realpath($path) ?: $path;
+
+ return dirname($path) === $path ? $path : rtrim($path, DIRECTORY_SEPARATOR);
+ })
+ ->unique();
+
+return $paths->reject(function (string $path) use ($paths): bool {
+ // Trimming before appending preserves one boundary separator for filesystem roots.
+ $boundary = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+
+ return $paths->contains(
+ fn (string $existing): bool => $existing !== $path
+ && str_starts_with(
+ $boundary,
+ rtrim($existing, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR
+ )
+ );
+})->values();
+```
+
+Preserve siblings such as `/views` and `/views-admin`, collapse trailing-separator duplicates even when `realpath()` fails, and retain `/` itself. Port Laravel's current `BladeTest`, `BladeAnonymousComponentTest`, `RenderableViewExceptionTest`, and fixtures into package-scoped Hypervel tests. Keep the classes separate. Rename the upstream `tested_...` method only for clear naming/TestDox; PHPUnit 13 already executes it, so do not claim the rename enables coverage. Adapt only framework bootstrap/namespaces and intentional Hypervel differences.
+
+## Testing and validation
+
+### Focused tests
+
+- `tests/View/ViewFactoryTest.php`: slot/slot-stack and loop cleanup, placeholder parity, stored View isolation, and unused yield-default side effects. Use a closure bound to the Factory only to prove both protected slot context entries are empty after `flushState()`; no public behavior exposes the retained slot-stack state.
+- `tests/View/ViewComponentAttributeBagTest.php`: default/current style termination and PHPDoc-preserving behavior.
+- `tests/View/ComponentTest.php`: digest keys, empty/partial publication recovery, namespace replacement, and cache hits. Protected cache inspection is explicitly allowed here because bounded retained key bytes are the load-bearing `view-26` invariant and no public output exposes the cache key.
+- `tests/View/Blade/BladePushTest.php`: comma-bearing `@pushIf` and `@elsePushIf`.
+- `tests/View/Blade/BladeComponentTagCompilerTest.php`: nested custom namespace, unnamed slot, and backed-enum paths where owned.
+- `tests/View/ViewBladeCompilerTest.php` and focused Blade suites: compiler marker `v3`, coroutine section isolation, echo-format ownership, footer signature, end-directive overrides, Xdebug-only `ParseError` source behavior without a synthetic seam, supported stringable forms, strict maintenance, and loop cases.
+- `tests/View/ViewCompilerEngineTest.php`: cache true/false, deletion recovery, and compiled-path cleanup.
+- `tests/View/ViewEngineResolverTest.php`, provider/config tests, and new `tests/View/PackageMetadataTest.php`: visibility, canonical defaults, lifecycle docs, and split metadata.
+- `tests/Integration/View`: nested-root, path-prefix sibling, trailing-separator duplicate, filesystem-root, and current upstream full-app View scenarios.
+- Mail View tests: cloned Factory/finder isolation remains intact.
+
+Tests must be deterministic. Concurrency tests use explicit channels/barriers, not sleeps. Publication tests use owned temporary directories, prove View delegates to the existing replacement boundary, and do not duplicate Filesystem's atomicity tests. Avoid Reflection and closure-bound protected-state inspection where a public/protected path can prove the behavior. The only approved closure-bound state assertions are the slot/slot-stack cleanup invariant and the bounded component-cache key, neither of which has observable public output.
+
+### Validation sequence
+
+1. Regenerate facades with `composer facade` after concrete API changes.
+2. During implementation, run PHP CS Fixer on touched files and focused View, Foundation View command/config, Testbench View, Mail View, and package metadata tests.
+3. Run `composer fix` once as the authoritative full formatter, both-PHPStan, parallel, Testbench, and dogfood checkpoint.
+4. Confirm the regenerated View facade contains `@method static string parentPlaceholder(string $section = '')` and no `getParentPlaceholder` entry.
+5. Review the full diff against current Laravel and every caller/callee; check API names, coroutine ownership, cleanup, stale symbols/comments/docs, and hot-path cost.
+6. Request independent code review and resolve every finding before completion.
+
+## Records and completion
+
+Before implementation, set all three core routing-index bullets to this View work and name the carried `view-01` and `reflection-02` entries. During implementation:
+
+- replace the “later full `view` audit” marker inside the `view-01` and `reflection-02` rows of the cross-package dependency index with complete revalidation;
+- tick the core package checklist's `view` entry only in the final bookkeeping commit;
+- add one complete View ledger entry covering `view-02` through `view-36`, rejected machinery, performance, validation, and the detail-plan link;
+- amend the earlier `view-01` and `reflection-02` entries with View revalidation;
+- add only genuine cross-package rows: Foundation-owned config/command changes and the separately routed `translation-10` twin;
+- record the inherited parent-placeholder subclass limitation without presenting speculative machinery as unresolved work;
+- leave no TODO, deferral, compatibility alias, dead middleware/property/helper, or superseded documentation.
+
+The View package is complete only after focused and full gates are green, fresh self-review finds no unresolved issue, independent code review signs off, and all records describe the final code rather than decision history.
From e7193666a5dca0d231c6d4cd104dad1fd5bfe86a Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Thu, 6 Aug 2026 13:44:07 +0000
Subject: [PATCH 12/16] fix(view): parse directive arguments by PHP structure
Replace comma-based Blade directive splitting with a shared top-level parser backed by PHP's tokenizer. The parser keeps commas inside strings, interpolation, arrays, calls, attributes, and other nested expressions while retaining the cheap path for expressions without commas.
Apply the corrected boundary to conditional stacks, once-only push and prepend directives, and @json. This preserves JSON_HEX defaults and explicit depth, keeps generated IDs correct, and prevents malformed compiled output for nested expressions.
Add focused regressions for literal and nested commas, interpolated strings, generated once IDs, and JSON encoding options. The work remains compilation-only and adds no render-time path.
---
src/view/src/Compilers/BladeCompiler.php | 57 ++++++++++++++++++
.../Concerns/CompilesConditionals.php | 26 ++------
.../src/Compilers/Concerns/CompilesJson.php | 2 +-
.../src/Compilers/Concerns/CompilesStacks.php | 4 +-
tests/View/Blade/BladeJsonTest.php | 28 ++++++++-
tests/View/Blade/BladePrependTest.php | 25 +++++++-
tests/View/Blade/BladePushTest.php | 60 +++++++++++++++----
7 files changed, 164 insertions(+), 38 deletions(-)
diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php
index 928025e47..156015072 100644
--- a/src/view/src/Compilers/BladeCompiler.php
+++ b/src/view/src/Compilers/BladeCompiler.php
@@ -423,6 +423,9 @@ protected function compileComponentTags(string $value): string
return $this->getComponentTagCompiler()->compile($value);
}
+ /**
+ * Get the component tag compiler.
+ */
protected function getComponentTagCompiler(): ComponentTagCompiler
{
if (isset($this->componentTagCompiler)) {
@@ -657,6 +660,60 @@ public function stripParentheses(string $expression): string
return $expression;
}
+ /**
+ * Split an expression at top-level commas.
+ *
+ * @return list
+ */
+ protected function splitTopLevel(string $expression, int $limit): array
+ {
+ $expression = $this->stripParentheses($expression);
+
+ if (! str_contains($expression, ',')) {
+ return [$expression];
+ }
+
+ try {
+ $tokens = token_get_all('parseConditionalStackExpression($expression);
+ $parts = $this->splitTopLevel($expression, 2);
+ $stack = trim($parts[1]);
- return "startPush({$parts[1]}); ?>";
+ return "startPush({$stack}); ?>";
}
/**
@@ -311,25 +312,10 @@ protected function compilePushIf(string $expression): string
*/
protected function compileElsePushIf(string $expression): string
{
- $parts = $this->parseConditionalStackExpression($expression);
+ $parts = $this->splitTopLevel($expression, 2);
+ $stack = trim($parts[1]);
- return "stopPush(); elseif({$parts[0]}): \$__env->startPush({$parts[1]}); ?>";
- }
-
- /**
- * Parse a conditional stack expression.
- */
- protected function parseConditionalStackExpression(string $expression): array
- {
- $segments = explode(',', $this->stripParentheses($expression));
-
- if (count($segments) > 2) {
- $stack = array_pop($segments);
-
- return [implode(',', $segments), trim($stack)];
- }
-
- return $segments;
+ return "stopPush(); elseif({$parts[0]}): \$__env->startPush({$stack}); ?>";
}
/**
diff --git a/src/view/src/Compilers/Concerns/CompilesJson.php b/src/view/src/Compilers/Concerns/CompilesJson.php
index 0df43d4a1..f5e4b1d22 100644
--- a/src/view/src/Compilers/Concerns/CompilesJson.php
+++ b/src/view/src/Compilers/Concerns/CompilesJson.php
@@ -16,7 +16,7 @@ trait CompilesJson
*/
protected function compileJson(string $expression): string
{
- $parts = explode(',', $this->stripParentheses($expression));
+ $parts = $this->splitTopLevel($expression, 3);
$options = isset($parts[1]) ? trim($parts[1]) : $this->encodingOptions;
diff --git a/src/view/src/Compilers/Concerns/CompilesStacks.php b/src/view/src/Compilers/Concerns/CompilesStacks.php
index e442f7d5d..1e3804f46 100644
--- a/src/view/src/Compilers/Concerns/CompilesStacks.php
+++ b/src/view/src/Compilers/Concerns/CompilesStacks.php
@@ -29,7 +29,7 @@ protected function compilePush(string $expression): string
*/
protected function compilePushOnce(string $expression): string
{
- $parts = explode(',', $this->stripParentheses($expression), 2);
+ $parts = $this->splitTopLevel($expression, 2);
[$stack, $id] = [$parts[0], $parts[1] ?? ''];
@@ -68,7 +68,7 @@ protected function compilePrepend(string $expression): string
*/
protected function compilePrependOnce(string $expression): string
{
- $parts = explode(',', $this->stripParentheses($expression), 2);
+ $parts = $this->splitTopLevel($expression, 2);
[$stack, $id] = [$parts[0], $parts[1] ?? ''];
diff --git a/tests/View/Blade/BladeJsonTest.php b/tests/View/Blade/BladeJsonTest.php
index 64564a295..6c36fe7e9 100644
--- a/tests/View/Blade/BladeJsonTest.php
+++ b/tests/View/Blade/BladeJsonTest.php
@@ -6,7 +6,7 @@
class BladeJsonTest extends AbstractBladeTestCase
{
- public function testStatementIsCompiledWithSafeDefaultEncodingOptions()
+ public function testStatementIsCompiledWithSafeDefaultEncodingOptions(): void
{
$string = 'var foo = @json($var);';
$expected = 'var foo = ;';
@@ -14,11 +14,35 @@ public function testStatementIsCompiledWithSafeDefaultEncodingOptions()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testEncodingOptionsCanBeOverwritten()
+ public function testEncodingOptionsCanBeOverwritten(): void
{
$string = 'var foo = @json($var, JSON_HEX_TAG);';
$expected = 'var foo = ;';
$this->assertEquals($expected, $this->compiler->compileString($string));
}
+
+ public function testInlineArrayCommasPreserveSafeDefaultEncodingOptions(): void
+ {
+ $string = 'var foo = @json(["name" => $name, "id" => $id]);';
+ $expected = 'var foo = $name, "id" => $id], 15, 512) ?>;';
+
+ $this->assertSame($expected, $this->compiler->compileString($string));
+ }
+
+ public function testInterpolatedStringsPreserveSafeDefaultEncodingOptions(): void
+ {
+ $string = 'var foo = @json(["name" => "{$prefix}-x", "id" => $id]);';
+ $expected = 'var foo = "{$prefix}-x", "id" => $id], 15, 512) ?>;';
+
+ $this->assertSame($expected, $this->compiler->compileString($string));
+ }
+
+ public function testInlineArrayCommasPreserveExplicitOptionsAndDepth(): void
+ {
+ $string = 'var foo = @json(["name" => $name, "id" => $id], JSON_HEX_TAG, 256);';
+ $expected = 'var foo = $name, "id" => $id], JSON_HEX_TAG, 256) ?>;';
+
+ $this->assertSame($expected, $this->compiler->compileString($string));
+ }
}
diff --git a/tests/View/Blade/BladePrependTest.php b/tests/View/Blade/BladePrependTest.php
index f45834a3d..2978601ab 100644
--- a/tests/View/Blade/BladePrependTest.php
+++ b/tests/View/Blade/BladePrependTest.php
@@ -9,7 +9,7 @@
class BladePrependTest extends AbstractBladeTestCase
{
- public function testPrependIsCompiled()
+ public function testPrependIsCompiled(): void
{
$string = '@prepend(\'foo\')
bar
@@ -21,7 +21,7 @@ public function testPrependIsCompiled()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testPrependOnceIsCompiled()
+ public function testPrependOnceIsCompiled(): void
{
$string = '@prependOnce(\'foo\', \'bar\')
test
@@ -35,7 +35,7 @@ public function testPrependOnceIsCompiled()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testPrependOnceIsCompiledWhenIdIsMissing()
+ public function testPrependOnceIsCompiledWhenIdIsMissing(): void
{
Str::createUuidsUsing(fn () => Uuid::fromString('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'));
@@ -50,4 +50,23 @@ public function testPrependOnceIsCompiledWhenIdIsMissing()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
+
+ public function testPrependOnceCompilesCommaBearingStackWithExplicitId(): void
+ {
+ $expected = "hasRenderedOnce('id')): \$__env->markAsRenderedOnce('id');\n"
+ . "\$__env->startPrepend('body,end'); ?>";
+
+ $this->assertSame($expected, $this->compiler->compileString("@prependOnce('body,end', 'id')"));
+ }
+
+ public function testPrependOnceCompilesNestedCommaExpressionWithGeneratedId(): void
+ {
+ Str::createUuidsUsing(fn () => Uuid::fromString('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'));
+
+ $expected = "hasRenderedOnce('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f')): "
+ . "\$__env->markAsRenderedOnce('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f');\n"
+ . "\$__env->startPrepend(config('view.stack', 'fallback')); ?>";
+
+ $this->assertSame($expected, $this->compiler->compileString("@prependOnce(config('view.stack', 'fallback'))"));
+ }
}
diff --git a/tests/View/Blade/BladePushTest.php b/tests/View/Blade/BladePushTest.php
index b98e81308..613a69c6a 100644
--- a/tests/View/Blade/BladePushTest.php
+++ b/tests/View/Blade/BladePushTest.php
@@ -9,7 +9,7 @@
class BladePushTest extends AbstractBladeTestCase
{
- public function testPushIsCompiled()
+ public function testPushIsCompiled(): void
{
$string = '@push(\'foo\')
test
@@ -20,7 +20,7 @@ public function testPushIsCompiled()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testPushIsCompiledWithParenthesis()
+ public function testPushIsCompiledWithParenthesis(): void
{
$string = '@push(\'foo):))\')
test
@@ -31,7 +31,7 @@ public function testPushIsCompiledWithParenthesis()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testPushOnceIsCompiled()
+ public function testPushOnceIsCompiled(): void
{
$string = '@pushOnce(\'foo\', \'bar\')
test
@@ -45,7 +45,7 @@ public function testPushOnceIsCompiled()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testPushOnceIsCompiledWhenIdIsMissing()
+ public function testPushOnceIsCompiledWhenIdIsMissing(): void
{
Str::createUuidsUsing(fn () => Uuid::fromString('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'));
@@ -61,12 +61,31 @@ public function testPushOnceIsCompiledWhenIdIsMissing()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testPushIfIsCompiled()
+ public function testPushOnceCompilesCommaBearingStackWithExplicitId(): void
+ {
+ $expected = "hasRenderedOnce('id')): \$__env->markAsRenderedOnce('id');\n"
+ . "\$__env->startPush('body,end'); ?>";
+
+ $this->assertSame($expected, $this->compiler->compileString("@pushOnce('body,end', 'id')"));
+ }
+
+ public function testPushOnceCompilesNestedCommaExpressionWithGeneratedId(): void
+ {
+ Str::createUuidsUsing(fn () => Uuid::fromString('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f'));
+
+ $expected = "hasRenderedOnce('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f')): "
+ . "\$__env->markAsRenderedOnce('e60e8f77-9ac3-4f71-9f8e-a044ef481d7f');\n"
+ . "\$__env->startPush(config('view.stack', 'fallback')); ?>";
+
+ $this->assertSame($expected, $this->compiler->compileString("@pushOnce(config('view.stack', 'fallback'))"));
+ }
+
+ public function testPushIfIsCompiled(): void
{
$string = '@pushIf(true, \'foo\')
test
@endPushIf';
- $expected = 'startPush( \'foo\'); ?>
+ $expected = 'startPush(\'foo\'); ?>
test
stopPush(); endif; ?>';
@@ -107,7 +126,7 @@ public function testElsePushIfWithMoreThanOneCommaIsCompiled(): void
elseif
@endPushIf';
- $expected = 'startPush( \'body-end\'); ?>
+ $expected = 'startPush(\'body-end\'); ?>
if
stopPush(); elseif(Str::startsWith(\'abc\', \'a\')): $__env->startPush(\'body-end\'); ?>
elseif
@@ -116,7 +135,7 @@ public function testElsePushIfWithMoreThanOneCommaIsCompiled(): void
$this->assertEquals($expected, $this->compiler->compileString($string));
}
- public function testPushIfElseIsCompiled()
+ public function testPushIfElseIsCompiled(): void
{
$string = '@pushIf(true, \'stack\')
if
@@ -125,9 +144,9 @@ public function testPushIfElseIsCompiled()
@elsePush(\'stack\')
else
@endPushIf';
- $expected = 'startPush( \'stack\'); ?>
+ $expected = 'startPush(\'stack\'); ?>
if
-stopPush(); elseif(false): $__env->startPush( \'stack\'); ?>
+stopPush(); elseif(false): $__env->startPush(\'stack\'); ?>
elseif
stopPush(); else: $__env->startPush(\'stack\'); ?>
else
@@ -135,4 +154,25 @@ public function testPushIfElseIsCompiled()
$this->assertEquals($expected, $this->compiler->compileString($string));
}
+
+ public function testPushIfCompilesCommaBearingStackExpressions(): void
+ {
+ $this->assertSame(
+ "startPush('body,end'); ?>",
+ $this->compiler->compileString("@pushIf(true, 'body,end')")
+ );
+
+ $this->assertSame(
+ "stopPush(); elseif(true): \$__env->startPush(config('view.stack', 'body,end')); ?>",
+ $this->compiler->compileString("@elsePushIf(true, config('view.stack', 'body,end'))")
+ );
+ }
+
+ public function testPushIfCompilesInterpolatedStringsBeforeTheStackArgument(): void
+ {
+ $this->assertSame(
+ 'startPush(\'stack\'); ?>',
+ $this->compiler->compileString('@pushIf($x === "{$b}", \'stack\')')
+ );
+ }
}
From 6645bc99761c142585c5c790a5718211850eec49 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Thu, 6 Aug 2026 13:44:17 +0000
Subject: [PATCH 13/16] fix(view): make static cleanup complete and explicit
Give Component and CompilerEngine standard flushState hooks and route framework test cleanup through them. Component cache cleanup now covers ignored parameter metadata as well as reflection and Blade caches while retaining the narrower production reset APIs.
Keep internal cleanup and metadata methods out of component template data, synchronize the documented reserved names, and pin the exact public component data surface. Complete the touched component/compiler method metadata and slot-context return type.
Add focused coverage for every static field, both CompilerEngine reset entry points, and the component exposure boundary so future worker-global additions cannot silently escape cleanup or become callable from templates.
---
src/boost/docs/blade.md | 2 +
.../src/PHPUnit/AfterEachTestSubscriber.php | 6 +--
.../src/Compilers/ComponentTagCompiler.php | 3 ++
src/view/src/Component.php | 13 ++++++
src/view/src/Concerns/ManagesComponents.php | 23 ++++++++++-
src/view/src/Engines/CompilerEngine.php | 14 +++++++
tests/View/ViewCompilerEngineTest.php | 8 +++-
tests/View/ViewComponentTest.php | 14 ++++++-
tests/View/ViewStaticStateTest.php | 41 +++++++++++++++++++
9 files changed, 116 insertions(+), 8 deletions(-)
diff --git a/src/boost/docs/blade.md b/src/boost/docs/blade.md
index 74b18b640..a1590f4f1 100644
--- a/src/boost/docs/blade.md
+++ b/src/boost/docs/blade.md
@@ -1255,8 +1255,10 @@ By default, some keywords are reserved for Blade's internal use in order to rend
- `data`
- `flushCache`
+- `flushState`
- `forgetComponentsResolver`
- `forgetFactory`
+- `ignoredParameterNames`
- `render`
- `resolve`
- `resolveComponentsUsing`
diff --git a/src/testing/src/PHPUnit/AfterEachTestSubscriber.php b/src/testing/src/PHPUnit/AfterEachTestSubscriber.php
index 3d59c8f09..6316863eb 100644
--- a/src/testing/src/PHPUnit/AfterEachTestSubscriber.php
+++ b/src/testing/src/PHPUnit/AfterEachTestSubscriber.php
@@ -307,12 +307,10 @@ protected function flushFrameworkState(): void
\Hypervel\Validation\RulePlanCache::flushState();
\Hypervel\Validation\ValidationRuleParser::flushState();
\Hypervel\Validation\Validator::flushState();
- \Hypervel\View\Component::flushCache();
- \Hypervel\View\Component::forgetComponentsResolver();
- \Hypervel\View\Component::forgetFactory();
+ \Hypervel\View\Component::flushState();
\Hypervel\View\ComponentAttributeBag::flushState();
\Hypervel\View\DynamicComponent::flushState();
- \Hypervel\View\Engines\CompilerEngine::forgetCompiledOrNotExpired();
+ \Hypervel\View\Engines\CompilerEngine::flushState();
\Hypervel\View\Factory::flushMacros();
\Hypervel\View\View::flushState();
\Hypervel\WebSocketServer\Collector\FdCollector::flushState();
diff --git a/src/view/src/Compilers/ComponentTagCompiler.php b/src/view/src/Compilers/ComponentTagCompiler.php
index 27ab89ffe..036e59068 100644
--- a/src/view/src/Compilers/ComponentTagCompiler.php
+++ b/src/view/src/Compilers/ComponentTagCompiler.php
@@ -409,6 +409,9 @@ public function guessClassName(string $component): string
return $namespace . 'View\Components\\' . $class;
}
+ /**
+ * Get the application namespace.
+ */
protected function getNamespace(): string
{
if (isset($this->namespace)) {
diff --git a/src/view/src/Component.php b/src/view/src/Component.php
index b5be477e7..8cc36c603 100644
--- a/src/view/src/Component.php
+++ b/src/view/src/Component.php
@@ -289,8 +289,10 @@ protected function ignoredMethods(): array
'withName',
'withAttributes',
'flushCache',
+ 'flushState',
'forgetFactory',
'forgetComponentsResolver',
+ 'ignoredParameterNames',
'resolveComponentsUsing',
], $this->except);
}
@@ -387,6 +389,7 @@ public static function flushCache(): void
static::$constructorParametersCache = [];
static::$methodCache = [];
static::$propertyCache = [];
+ static::$ignoredParameterNames = [];
}
/**
@@ -427,4 +430,14 @@ public static function resolveComponentsUsing(Closure $resolver): void
{
static::$componentsResolver = $resolver;
}
+
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::flushCache();
+ static::forgetFactory();
+ static::forgetComponentsResolver();
+ }
}
diff --git a/src/view/src/Concerns/ManagesComponents.php b/src/view/src/Concerns/ManagesComponents.php
index ccedc3dc1..12c130be9 100644
--- a/src/view/src/Concerns/ManagesComponents.php
+++ b/src/view/src/Concerns/ManagesComponents.php
@@ -52,6 +52,9 @@ public function startComponent(View|Htmlable|Closure|string $view, array $data =
}
}
+ /**
+ * Push a component onto the stack.
+ */
protected function pushComponentStack(View|Htmlable|Closure|string $view): int
{
$componentStack = CoroutineContext::get(static::COMPONENT_STACK_CONTEXT_KEY, []);
@@ -61,6 +64,9 @@ protected function pushComponentStack(View|Htmlable|Closure|string $view): int
return count($componentStack);
}
+ /**
+ * Pop the current component from the stack.
+ */
protected function popComponentStack(): View|Htmlable|Closure|string|null
{
$componentStack = CoroutineContext::get(static::COMPONENT_STACK_CONTEXT_KEY, []);
@@ -70,6 +76,9 @@ protected function popComponentStack(): View|Htmlable|Closure|string|null
return $view;
}
+ /**
+ * Append data for the current component.
+ */
protected function appendComponentData(array $data): void
{
$componentData = CoroutineContext::get(static::COMPONENT_DATA_CONTEXT_KEY, []);
@@ -77,7 +86,10 @@ protected function appendComponentData(array $data): void
CoroutineContext::set(static::COMPONENT_DATA_CONTEXT_KEY, $componentData);
}
- protected function createSlotContext()
+ /**
+ * Create the slot context for the current component.
+ */
+ protected function createSlotContext(): void
{
$slots = CoroutineContext::get(static::SLOTS_CONTEXT_KEY, []);
$slots[$this->currentComponent()] = [];
@@ -194,6 +206,9 @@ public function slot(string $name, ?string $content = null, array $attributes =
}
}
+ /**
+ * Set slot data for the current component.
+ */
protected function setSlotData(string $name, string|ComponentSlot|null $content): void
{
$currentComponent = $this->currentComponent();
@@ -203,6 +218,9 @@ protected function setSlotData(string $name, string|ComponentSlot|null $content)
CoroutineContext::set(static::SLOTS_CONTEXT_KEY, $slots);
}
+ /**
+ * Push a slot onto the stack.
+ */
protected function pushSlotStack(array $value): void
{
$currentComponent = $this->currentComponent();
@@ -212,6 +230,9 @@ protected function pushSlotStack(array $value): void
CoroutineContext::set(static::SLOT_STACK_CONTEXT_KEY, $slotStack);
}
+ /**
+ * Pop the current slot from the stack.
+ */
protected function popSlotStack(): array
{
$currentComponent = $this->currentComponent();
diff --git a/src/view/src/Engines/CompilerEngine.php b/src/view/src/Engines/CompilerEngine.php
index 84bf02228..ec8a64cc5 100755
--- a/src/view/src/Engines/CompilerEngine.php
+++ b/src/view/src/Engines/CompilerEngine.php
@@ -83,6 +83,9 @@ public function get(string $path, array $data = []): string
}
}
+ /**
+ * Push a compiled view path onto the stack.
+ */
protected function pushCompiledPath(string $path): void
{
$stack = CoroutineContext::get(static::COMPILED_PATH_CONTEXT_KEY, []);
@@ -90,6 +93,9 @@ protected function pushCompiledPath(string $path): void
CoroutineContext::set(static::COMPILED_PATH_CONTEXT_KEY, $stack);
}
+ /**
+ * Pop the current compiled view path from the stack.
+ */
protected function popCompiledPath(): void
{
$stack = CoroutineContext::get(static::COMPILED_PATH_CONTEXT_KEY, []);
@@ -145,4 +151,12 @@ public static function forgetCompiledOrNotExpired(): void
{
static::$compiledOrNotExpired = [];
}
+
+ /**
+ * Flush all static state.
+ */
+ public static function flushState(): void
+ {
+ static::forgetCompiledOrNotExpired();
+ }
}
diff --git a/tests/View/ViewCompilerEngineTest.php b/tests/View/ViewCompilerEngineTest.php
index 41cb55a9d..7cc97c30f 100755
--- a/tests/View/ViewCompilerEngineTest.php
+++ b/tests/View/ViewCompilerEngineTest.php
@@ -66,11 +66,11 @@ public function testHttpExceptionsAreNotReThrownAsViewExceptions()
$engine->get(__DIR__ . '/Fixtures/foo.php');
}
- public function testThatViewsAreNotAskTwiceIfTheyAreExpired()
+ public function testThatViewsAreNotAskTwiceIfTheyAreExpired(): void
{
$engine = $this->getEngine();
$engine->getCompiler()->shouldReceive('getCompiledPath')->with(__DIR__ . '/Fixtures/foo.php')->andReturn(__DIR__ . '/Fixtures/basic.php');
- $engine->getCompiler()->shouldReceive('isExpired')->twice()->andReturn(false);
+ $engine->getCompiler()->shouldReceive('isExpired')->times(3)->andReturn(false);
$engine->getCompiler()->shouldReceive('compile')->never();
$engine->get(__DIR__ . '/Fixtures/foo.php');
@@ -80,6 +80,10 @@ public function testThatViewsAreNotAskTwiceIfTheyAreExpired()
CompilerEngine::forgetCompiledOrNotExpired();
$engine->get(__DIR__ . '/Fixtures/foo.php');
+
+ CompilerEngine::flushState();
+
+ $engine->get(__DIR__ . '/Fixtures/foo.php');
}
public function testViewsAreRecompiledWhenCompiledViewIsMissingViaFileNotFoundException(): void
diff --git a/tests/View/ViewComponentTest.php b/tests/View/ViewComponentTest.php
index 386c69006..e30eba71a 100644
--- a/tests/View/ViewComponentTest.php
+++ b/tests/View/ViewComponentTest.php
@@ -26,7 +26,7 @@ public function testDataExposure()
$this->assertSame('taylor', $variables['hello']('taylor'));
}
- public function testIgnoredMethodsAreNotExposedToViewData()
+ public function testIgnoredMethodsAreNotExposedToViewData(): void
{
$component = new class extends Component {
protected array $except = ['goodbye'];
@@ -61,6 +61,18 @@ public function goodbye()
}
}
+ public function testFrameworkMethodsAreNotExposedToViewData(): void
+ {
+ $component = new class extends Component {
+ public function render(): ViewContract|Htmlable|Closure|string
+ {
+ return 'test';
+ }
+ };
+
+ $this->assertSame(['componentName', 'attributes'], array_keys($component->data()));
+ }
+
public function testAttributeParentInheritance(): void
{
$component = new TestViewComponent;
diff --git a/tests/View/ViewStaticStateTest.php b/tests/View/ViewStaticStateTest.php
index 31afad089..c1e7d976f 100644
--- a/tests/View/ViewStaticStateTest.php
+++ b/tests/View/ViewStaticStateTest.php
@@ -4,12 +4,15 @@
namespace Hypervel\Tests\View;
+use Hypervel\Contracts\View\Factory as FactoryContract;
use Hypervel\Tests\TestCase;
use Hypervel\View\Compilers\ComponentTagCompiler;
+use Hypervel\View\Component;
use Hypervel\View\ComponentAttributeBag;
use Hypervel\View\DynamicComponent;
use Hypervel\View\Factory;
use Hypervel\View\View;
+use Mockery as m;
use ReflectionProperty;
class ViewStaticStateTest extends TestCase
@@ -63,6 +66,44 @@ public function testDynamicComponentFlushStateClearsStaticCaches(): void
$this->assertNull($compiler->getValue());
$this->assertSame([], $componentClasses->getValue());
}
+
+ public function testComponentFlushCacheClearsEveryCache(): void
+ {
+ foreach ([
+ 'bladeViewCache',
+ 'constructorParametersCache',
+ 'methodCache',
+ 'propertyCache',
+ 'ignoredParameterNames',
+ ] as $property) {
+ (new ReflectionProperty(Component::class, $property))->setValue(null, ['cached']);
+ }
+
+ Component::flushCache();
+
+ foreach ([
+ 'bladeViewCache',
+ 'constructorParametersCache',
+ 'methodCache',
+ 'propertyCache',
+ 'ignoredParameterNames',
+ ] as $property) {
+ $this->assertSame([], (new ReflectionProperty(Component::class, $property))->getValue());
+ }
+ }
+
+ public function testComponentFlushStateClearsAllStaticState(): void
+ {
+ (new ReflectionProperty(Component::class, 'factory'))->setValue(null, m::mock(FactoryContract::class));
+ (new ReflectionProperty(Component::class, 'componentsResolver'))->setValue(null, static fn (): null => null);
+ (new ReflectionProperty(Component::class, 'bladeViewCache'))->setValue(null, ['cached']);
+
+ Component::flushState();
+
+ $this->assertNull((new ReflectionProperty(Component::class, 'factory'))->getValue());
+ $this->assertNull((new ReflectionProperty(Component::class, 'componentsResolver'))->getValue());
+ $this->assertSame([], (new ReflectionProperty(Component::class, 'bladeViewCache'))->getValue());
+ }
}
class ViewStaticStateComponent
From cdd5d82a30d9e7ead2cf5a897363142a0159b6ab Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Thu, 6 Aug 2026 13:44:28 +0000
Subject: [PATCH 14/16] fix(testbench): own exception cleanup in the base case
Remove Testbench Foundation Application's duplicate registry of framework static resets. The authoritative after-test subscriber already owns those resets, and keeping a second list allowed the two registries to drift.
Move the one caller-dependent reset into the raw Testbench PHPUnit base: exception-handler restoration requires the active test case. Its teardown now retains the earliest failure while still performing Mockery verification and cleanup, matching the Components base lifecycle.
Remove redundant test teardown calls, retain only test-owned resource cleanup, and extend the shared failure regression to prove both base cases still verify and close Mockery when handler restoration throws.
---
.../src/Bootstrap/HandleExceptions.php | 3 +
src/testbench/src/Foundation/Application.php | 70 -------------------
src/testbench/src/PHPUnit/TestCase.php | 27 ++++++-
tests/Testbench/CreatesApplicationTest.php | 10 ---
.../Testbench/Foundation/ApplicationTest.php | 9 ---
.../Bootstrap/CreateVendorSymlinkTest.php | 2 -
tests/Testbench/TestCaseTest.php | 10 ---
.../Concerns/InteractsWithMockeryTest.php | 28 +++++++-
8 files changed, 55 insertions(+), 104 deletions(-)
diff --git a/src/foundation/src/Bootstrap/HandleExceptions.php b/src/foundation/src/Bootstrap/HandleExceptions.php
index 7ad68dc2c..3c14fd27b 100644
--- a/src/foundation/src/Bootstrap/HandleExceptions.php
+++ b/src/foundation/src/Bootstrap/HandleExceptions.php
@@ -253,6 +253,9 @@ protected function getExceptionHandler(): ExceptionHandler
*/
public static function flushState(?TestCase $testCase = null): void
{
+ // AfterEachTestSubscriber resets framework static state after each test.
+ // This reset remains caller-driven because restoring PHPUnit's error
+ // handler requires the active test case.
if (is_null(static::$app)) {
return;
}
diff --git a/src/testbench/src/Foundation/Application.php b/src/testbench/src/Foundation/Application.php
index 907700d4a..eaf4238cd 100644
--- a/src/testbench/src/Foundation/Application.php
+++ b/src/testbench/src/Foundation/Application.php
@@ -5,40 +5,13 @@
namespace Hypervel\Testbench\Foundation;
use Closure;
-use Hypervel\Console\Application as Artisan;
-use Hypervel\Console\Commands\ScheduleListCommand;
use Hypervel\Contracts\Foundation\Application as ApplicationContract;
-use Hypervel\Database\Eloquent\Factories\Factory;
-use Hypervel\Database\Eloquent\Model;
-use Hypervel\Database\Migrations\Migrator;
-use Hypervel\Database\Schema\Builder as SchemaBuilder;
-use Hypervel\Foundation\Bootstrap\HandleExceptions;
use Hypervel\Foundation\Bootstrap\LoadEnvironmentVariables;
-use Hypervel\Foundation\Console\AboutCommand;
-use Hypervel\Foundation\Console\RouteListCommand;
-use Hypervel\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
-use Hypervel\Foundation\Http\Middleware\PreventRequestForgery;
-use Hypervel\Foundation\Http\Middleware\PreventRequestsDuringMaintenance;
-use Hypervel\Foundation\Http\Middleware\TrimStrings;
-use Hypervel\Http\Middleware\TrustHosts;
-use Hypervel\Http\Middleware\TrustProxies;
-use Hypervel\Http\Resources\Json\JsonResource;
-use Hypervel\Http\Resources\JsonApi\JsonApiResource;
-use Hypervel\Mail\Markdown;
-use Hypervel\Queue\Console\WorkCommand;
-use Hypervel\Queue\Queue;
-use Hypervel\Routing\Middleware\ThrottleRequests;
use Hypervel\Support\Arr;
-use Hypervel\Support\EncodedHtmlString;
-use Hypervel\Support\Sleep;
-use Hypervel\Support\Str;
-use Hypervel\Testbench\Bootstrap\RegisterProviders;
use Hypervel\Testbench\Concerns\CreatesApplication;
use Hypervel\Testbench\Contracts\Config as ConfigContract;
use Hypervel\Testbench\Foundation\Bootstrap\EnsuresDefaultConfiguration;
use Hypervel\Testbench\Foundation\Bootstrap\LoadEnvironmentVariablesFromArray;
-use Hypervel\Validation\Validator;
-use Hypervel\View\Component;
class Application
{
@@ -362,47 +335,4 @@ protected function resolveApplicationConfiguration(ApplicationContract $app): vo
$this->resolveApplicationConfigurationFromTrait($app);
(new EnsuresDefaultConfiguration)->bootstrap($app);
}
-
- /**
- * Flush static state touched by Testbench application bootstrap tests.
- *
- * IMPORTANT: This is NOT the global framework static-state cleanup list.
- * Do not add general framework static-state resets here. Add them to
- * src/testing/src/PHPUnit/AfterEachTestSubscriber.php instead.
- *
- * @param object $instance active test instance, used to thread the running TestCase through to HandleExceptions::flushState()
- */
- public static function flushState(object $instance): void
- {
- AboutCommand::flushState();
- Artisan::forgetBootstrappers();
- Component::flushCache();
- Component::forgetComponentsResolver();
- Component::forgetFactory();
- ConvertEmptyStringsToNull::flushState();
- EncodedHtmlString::flushState();
- Factory::flushState();
- HandleExceptions::flushState($instance instanceof \PHPUnit\Framework\TestCase ? $instance : null);
- Env::flushState();
- JsonResource::flushState();
- JsonApiResource::flushState();
- Markdown::flushState();
- Migrator::flushState();
- Model::flushState();
- PreventRequestForgery::flushState();
- PreventRequestsDuringMaintenance::flushState();
- Queue::flushState();
- RegisterProviders::flushState();
- RouteListCommand::flushState();
- ScheduleListCommand::flushState();
- SchemaBuilder::flushState();
- Sleep::flushState();
- Str::flushState();
- ThrottleRequests::flushState();
- TrimStrings::flushState();
- TrustProxies::flushState();
- TrustHosts::flushState();
- Validator::flushState();
- WorkCommand::flushState();
- }
}
diff --git a/src/testbench/src/PHPUnit/TestCase.php b/src/testbench/src/PHPUnit/TestCase.php
index 32c77e0e6..32dea205a 100644
--- a/src/testbench/src/PHPUnit/TestCase.php
+++ b/src/testbench/src/PHPUnit/TestCase.php
@@ -4,6 +4,7 @@
namespace Hypervel\Testbench\PHPUnit;
+use Hypervel\Foundation\Bootstrap\HandleExceptions;
use Hypervel\Testbench\Concerns\HandlesAssertions;
use Hypervel\Testing\Concerns\InteractsWithMockery;
use Override;
@@ -24,7 +25,31 @@ class TestCase extends \PHPUnit\Framework\TestCase
#[Override]
protected function tearDown(): void
{
- $this->tearDownTheTestEnvironmentUsingMockery();
+ $exception = null;
+
+ try {
+ $this->flushExceptionHandlerState();
+ } catch (Throwable $throwable) {
+ $exception = $throwable;
+ }
+
+ try {
+ $this->tearDownTheTestEnvironmentUsingMockery();
+ } catch (Throwable $throwable) {
+ $exception ??= $throwable;
+ }
+
+ if ($exception !== null) {
+ throw $exception;
+ }
+ }
+
+ /**
+ * Flush the global exception-handler state.
+ */
+ protected function flushExceptionHandlerState(): void
+ {
+ HandleExceptions::flushState($this);
}
/**
diff --git a/tests/Testbench/CreatesApplicationTest.php b/tests/Testbench/CreatesApplicationTest.php
index 3d0b4c94c..0a3041fab 100644
--- a/tests/Testbench/CreatesApplicationTest.php
+++ b/tests/Testbench/CreatesApplicationTest.php
@@ -6,23 +6,13 @@
use Hypervel\Foundation\Application;
use Hypervel\Testbench\Concerns\CreatesApplication;
-use Hypervel\Testbench\Foundation\Application as TestbenchApplication;
use Hypervel\Testbench\PHPUnit\TestCase;
-use Override;
use PHPUnit\Framework\Attributes\Test;
class CreatesApplicationTest extends TestCase
{
use CreatesApplication;
- #[Override]
- protected function tearDown(): void
- {
- TestbenchApplication::flushState($this);
-
- parent::tearDown();
- }
-
#[Test]
public function itProperlyLoadsHypervelApplication()
{
diff --git a/tests/Testbench/Foundation/ApplicationTest.php b/tests/Testbench/Foundation/ApplicationTest.php
index 16e822533..4bb466ff4 100644
--- a/tests/Testbench/Foundation/ApplicationTest.php
+++ b/tests/Testbench/Foundation/ApplicationTest.php
@@ -9,21 +9,12 @@
use Hypervel\Testbench\Foundation\Config;
use Hypervel\Testbench\Foundation\Env;
use Hypervel\Testbench\PHPUnit\TestCase;
-use Override;
use PHPUnit\Framework\Attributes\Test;
use function Hypervel\Testbench\default_skeleton_path;
class ApplicationTest extends TestCase
{
- #[Override]
- protected function tearDown(): void
- {
- TestbenchApplication::flushState($this);
-
- parent::tearDown();
- }
-
#[Test]
public function itCanCreateAnApplication()
{
diff --git a/tests/Testbench/Foundation/Bootstrap/CreateVendorSymlinkTest.php b/tests/Testbench/Foundation/Bootstrap/CreateVendorSymlinkTest.php
index 2679bec6f..361754327 100644
--- a/tests/Testbench/Foundation/Bootstrap/CreateVendorSymlinkTest.php
+++ b/tests/Testbench/Foundation/Bootstrap/CreateVendorSymlinkTest.php
@@ -29,8 +29,6 @@ protected function tearDown(): void
$this->application->flush();
}
- TestbenchApplication::flushState($this);
-
parent::tearDown();
}
diff --git a/tests/Testbench/TestCaseTest.php b/tests/Testbench/TestCaseTest.php
index 806a3d801..834dc15ed 100644
--- a/tests/Testbench/TestCaseTest.php
+++ b/tests/Testbench/TestCaseTest.php
@@ -7,11 +7,9 @@
use Hypervel\Config\Repository as ConfigRepository;
use Hypervel\Foundation\Application;
use Hypervel\Testbench\Contracts\TestCase as TestCaseContract;
-use Hypervel\Testbench\Foundation\Application as Testbench;
use Hypervel\Testbench\Foundation\Env;
use Hypervel\Testbench\Pest\WithPest;
use Hypervel\Testbench\PHPUnit\TestCase;
-use Override;
use PHPUnit\Framework\Attributes\Test;
use RuntimeException;
use Throwable;
@@ -20,14 +18,6 @@
class TestCaseTest extends TestCase
{
- #[Override]
- protected function tearDown(): void
- {
- Testbench::flushState($this);
-
- parent::tearDown();
- }
-
#[Test]
public function itCanCreateTheTestcase(): void
{
diff --git a/tests/Testing/Concerns/InteractsWithMockeryTest.php b/tests/Testing/Concerns/InteractsWithMockeryTest.php
index 625407d65..c7ed2299f 100644
--- a/tests/Testing/Concerns/InteractsWithMockeryTest.php
+++ b/tests/Testing/Concerns/InteractsWithMockeryTest.php
@@ -60,9 +60,13 @@ public static function caseProvider(): iterable
yield 'foundation' => [FoundationMockeryTestCase::class];
}
- public function testComponentsBaseCaseStillVerifiesMockeryWhenExceptionCleanupFails(): void
+ /**
+ * @param class-string $testCaseClass
+ */
+ #[DataProvider('exceptionCleanupCaseProvider')]
+ public function testBaseCasesStillVerifyMockeryWhenExceptionCleanupFails(string $testCaseClass): void
{
- $testCase = new ComponentsMockeryTestCase('placeholder');
+ $testCase = new $testCaseClass('placeholder');
$testCase->failExceptionCleanup = true;
m::mock()->shouldReceive('expected')->once();
@@ -75,6 +79,15 @@ public function testComponentsBaseCaseStillVerifiesMockeryWhenExceptionCleanupFa
$this->assertNull((new ReflectionProperty(m::class, '_container'))->getValue());
}
+
+ /**
+ * @return iterable}>
+ */
+ public static function exceptionCleanupCaseProvider(): iterable
+ {
+ yield 'components' => [ComponentsMockeryTestCase::class];
+ yield 'testbench' => [TestbenchMockeryTestCase::class];
+ }
}
interface MockeryLifecycleTestCase
@@ -114,6 +127,8 @@ protected function flushExceptionHandlerState(): void
class TestbenchMockeryTestCase extends TestbenchTestCase implements MockeryLifecycleTestCase
{
+ public bool $failExceptionCleanup = false;
+
public function placeholder(): void
{
}
@@ -127,6 +142,15 @@ public function assertionCount(): int
{
return $this->numberOfAssertionsPerformed();
}
+
+ protected function flushExceptionHandlerState(): void
+ {
+ if ($this->failExceptionCleanup) {
+ throw new RuntimeException('exception cleanup failed');
+ }
+
+ parent::flushExceptionHandlerState();
+ }
}
class FoundationMockeryTestCase extends FoundationTestCase implements MockeryLifecycleTestCase
From af9929feba2045610fd10ab3497bc55ec9624a61 Mon Sep 17 00:00:00 2001
From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com>
Date: Thu, 6 Aug 2026 13:44:38 +0000
Subject: [PATCH 15/16] docs(audit): record the View review follow-up
Extend the View findings through view-40 for directive parsing, static cleanup, internal component exposure, and touched method metadata. Record the final ownership model, rejected parser and lifecycle machinery, compile-time-only cost, and upstream-ready Blade defects.
Route the genuine Boost, Foundation, Testbench, and Testing implications through the dependency index and completed-package records. Mark implementation, validation, self-review, and independent code review complete with no deferred View issue or stale in-progress wording.
---
...amework-coroutine-state-lifecycle-audit.md | 2 +
...-coroutine-state-lifecycle-audit-ledger.md | 21 ++++---
...orrectness-lifecycle-and-current-parity.md | 58 ++++++++++++++-----
3 files changed, 58 insertions(+), 23 deletions(-)
diff --git a/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md b/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
index ea7abfbe9..07aeabcf5 100644
--- a/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
+++ b/docs/plans/2026-07-12-0900-framework-coroutine-state-lifecycle-audit.md
@@ -1177,6 +1177,8 @@ Add one row only for a shared finding or changed lower-level assumption that ano
| `validation-18` | `validation` | `validation` and `support` (revalidation complete) | `Complete Validation correctness, parity, and compiled lifecycles`; finding `validation-18` |
| `view-09` | `foundation` | `foundation` and `view` (revalidation complete) | `Complete View correctness, lifecycle, and current parity`; finding `view-09` |
| `view-24` | `foundation` | `foundation` and `view` (revalidation complete) | `Complete View correctness, lifecycle, and current parity`; finding `view-24` |
+| `view-37` | `view` | `view` (revalidation complete), `foundation`, `testbench`, and `testing` (targeted corrections complete); later full `testbench` and `testing` audits | `Complete View correctness, lifecycle, and current parity`; finding `view-37` |
+| `view-38` | `view` | `view` (revalidation complete), `boost` (targeted correction complete); later full `boost` audit | `Complete View correctness, lifecycle, and current parity`; finding `view-38` |
| `translation-10` | `translation` | `view` (sibling revalidation complete); later full `translation` audit | `Complete View correctness, lifecycle, and current parity`; finding `translation-10` |
## Package checklist
diff --git a/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
index 63d1409c7..d33b30edf 100644
--- a/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -1032,7 +1032,7 @@ Append package entries in checklist order. Keep each entry compact but complete
- **Approved owner gates:** The owner approved the process-local/testing-only array maintenance driver, nullable redirect and current Laravel API additions, the truthful `Http\Kernel` contract expansion required by middleware configuration, the `0600` default for newly decrypted plaintext, and correcting verified upstream defects rather than preserving parity bugs. The Kernel contract change is documented for custom implementations. The existing worker maintenance wrapper remains a periodic same-process snapshot and does not make the array driver cross-process.
- **Important rejected concerns:** Do not add shutdown-callback registries, PHPUnit meta-fixtures, a generic finalizer, request-scoped Application/Vite clones, Carbon managers, locks, watchers, retry loops, PID incarnation tracking, publication/delete transaction services, arbitrary-stream copy transactions, SQL parsers, TTY emulation, or broad PHPStan impurity annotations. The supported paths are covered by existing lifecycle, coroutine-context, Filesystem replacement, Process, and typed-reflection primitives. Retain `Filesystem::delete()` cache clearing because the full caller set and PHP stat cache require it; use targeted cache invalidation only where raw command/native postconditions demand it.
-- **Cross-package implications and revalidation:** Foundation revalidated all carried `view-01`, `testbench-01`, `reflection-01`, `reflection-02`, `config-01`, `config-02`, `context-01`, `context-04`, `coroutine-06`, `foundation-02`, `concurrency-01`, `concurrency-03`, `di-02`, `http-02`, `filesystem-07`, `foundation-04`, `events-01`, `events-04`, `events-06`, `foundation-01`, `support-02`, `encryption-03`, `server-process-10`, `bus-03`, `bus-17`, `bus-18`, `core-01`, and `core-05` assumptions. The later Queue work completed `queue-14` and revalidated Foundation's canonical base queue configuration under `queue-29`. The Reverb work completed `reverb-24` by adding the supported server path to Foundation's canonical broadcasting client options with focused config coverage; no Foundation runtime assumption changed. The Scout work adds `foundation-17` and `foundation-18`: Scout delegates to Foundation's truthful Meilisearch waiter, deletes its duplicate Support implementation, uses a service-valid custom key with exact remote precondition coverage, and inherits exact Meilisearch and Algolia cleanup-task ownership. The later HTTP work completed `http-03` and Foundation's helper side of `routing-01`, restoring open URL parameter forwarding without adding conversion at the helper boundary. The View work completes Foundation-owned `view-09` and `view-24` by publishing the canonical compiler defaults and correcting `view:cache` root deduplication with focused Foundation coverage. `foundation-06` requires later full Testbench revalidation; `auth-02` requires the Auth audit; and `database-03` requires the Database and Testbench audits.
+- **Cross-package implications and revalidation:** Foundation revalidated all carried `view-01`, `testbench-01`, `reflection-01`, `reflection-02`, `config-01`, `config-02`, `context-01`, `context-04`, `coroutine-06`, `foundation-02`, `concurrency-01`, `concurrency-03`, `di-02`, `http-02`, `filesystem-07`, `foundation-04`, `events-01`, `events-04`, `events-06`, `foundation-01`, `support-02`, `encryption-03`, `server-process-10`, `bus-03`, `bus-17`, `bus-18`, `core-01`, and `core-05` assumptions. The later Queue work completed `queue-14` and revalidated Foundation's canonical base queue configuration under `queue-29`. The Reverb work completed `reverb-24` by adding the supported server path to Foundation's canonical broadcasting client options with focused config coverage; no Foundation runtime assumption changed. The Scout work adds `foundation-17` and `foundation-18`: Scout delegates to Foundation's truthful Meilisearch waiter, deletes its duplicate Support implementation, uses a service-valid custom key with exact remote precondition coverage, and inherits exact Meilisearch and Algolia cleanup-task ownership. The later HTTP work completed `http-03` and Foundation's helper side of `routing-01`, restoring open URL parameter forwarding without adding conversion at the helper boundary. The View work completes Foundation-owned `view-09` and `view-24` by publishing the canonical compiler defaults and correcting `view:cache` root deduplication with focused Foundation coverage. Its `view-37` follow-up documents why exception-handler restoration remains caller-driven while the global subscriber owns ordinary framework static-state cleanup. `foundation-06` requires later full Testbench revalidation; `auth-02` requires the Auth audit; and `database-03` requires the Database and Testbench audits.
- **Upstream and documentation:** Originating Laravel implementation and documentation pull requests supplied discovery history; current local Laravel default-branch source, tests, metadata, and docs supplied the porting reference. Hypervel deliberately retains its Swoole exception renderer, coroutine-aware test lifecycle, worker-cached maintenance model, and checked TTY behavior. Authentication, configuration, database testing, deployment, HTTP testing, middleware, and queue documentation now covers every new public call shape and important runtime limitation without exposing internal choreography.
- **Implementation:** Foundation/Testbench teardown now exhausts every independent owner and clears state exactly once. External-service clients, global clocks/environment, and dump recursion are restored at their actual lifetimes. Maintenance drivers, middleware, commands, Vite, renderers, clear/link/cache/environment commands, generators, publishers, and installers check native failures and publish only validated state. Current Laravel dispatch, retry-stopping, preferred-JSON, health, HTTP testing, route-list, nullable redirect, and database-testing APIs are complete; the narrow retry callback upstream defect is corrected. Dead duplicate Scout setup, stale helpers/properties/imports, unsafe raw boundaries, false success paths, and obsolete comments are removed.
- **Regression tests:** Deterministic coverage injects failure into every teardown/termination phase, setup wrapper shape, SDK/clock/dumper/environment restoration, maintenance/cache/file publication, generator/installer/publisher operation, reflection/Blade/native read, health state, retry callback typing, and lazy database refresh boundary. It also covers coroutine isolation, process-local array semantics, current Laravel public API call shapes, custom dispatch construction, Kernel contract consumers, mode preservation, old-artifact survival, and exact throwable precedence.
@@ -1824,14 +1824,19 @@ Append package entries in checklist order. Keep each entry compact but complete
| `view-24` | Deduplicate deployment-time cache roots by canonical directory boundary while preserving prefix siblings and filesystem roots. |
| `view-30`, `view-35` | Memoize only verified-fresh compiled views and exhaustively release compiled-path ownership. |
| `view-33`, `view-34` | Render mutable View section content before storage and retain Laravel's lazy yield-default behavior. |
+| `view-37` | Standardize `Component` and `CompilerEngine` static cleanup, complete the component cache reset, and keep the new worker-global cleanup method out of template scope. |
+| `view-38` | Remove `Component::ignoredParameterNames()` from template scope as internal metadata, completing the base Component method filter without claiming a separate runtime defect. |
+| `view-39` | Split top-level arguments across conditional stacks, once-only stacks, and `@json`, preserving nested expressions and HTML-safe JSON flags. |
+| `view-40` | Complete concise method metadata and the slot-context return type in touched View source. |
- **Architecture and worker ownership:** Factory, BladeCompiler, EngineResolver, component metadata, compiled-template names, and verified freshness remain worker-lived. Render state and transient compiler state remain coroutine-local. Every compile pass initializes its current-section owner, so sequential or cross-instance compilers in one coroutine cannot leak a prior section into standalone `@parent`; no compiler stack or clone was added. Inline publication stays lock-free at the existing atomic Filesystem replacement boundary.
-- **Correctness and parity:** Failed renders now clear loop frames before they can corrupt later loop metadata. Slot cleanup closes a retained-state leak—no later render reads stale slot state, but rendered slot HTML and `ComponentSlot` objects no longer remain in coroutine context for the rest of the request. Components resolve current nested, slot, enum, attribute, and directive forms. Inline templates recognize legitimate empty files and repair incomplete files. Compiled views honor disabled caching, recover after verified-file deletion, and always pop diagnostic path state. Provider visibility, named arguments, protected compiler contracts, facades, dependencies, and current integration scenarios are restored. `@elsePushIf`, exact inline publication, loop cleanup, directory-boundary roots, worker/coroutine echo ownership, render-before-store section content, and exhaustive path cleanup intentionally correct behavior beyond current Laravel without removing a useful Laravel API.
-- **Important rejected concerns and closed limitations:** Do not add publication locks, retries, polling, file watchers, cache eviction, a section stack, compiler/Factory clones, request-scoped services, render-state snapshots, placeholder registries, or compatibility aliases. Dynamic inline templates still retain one fixed-size key and compiled file per distinct template; bounding cardinality would require unjustified eviction machinery. The opaque parent-placeholder salt is immutable worker state and needs no test reset because stored sections are already cleared. Compiled output deliberately names the base Factory, so overriding the protected salt on a Factory subclass is not a complete cached-template extension point. `@parent` inside a separately compiled include no longer depends on cold-cache ordering; that unsupported pattern consistently uses the include's empty compile-pass placeholder instead of freezing whichever outer section happened to compile first.
-- **Cross-package revalidation:** The complete View surface retains `view-01` request overlay precedence/restoration and revalidates `reflection-02` through closure inference and component/compiler reflection. Foundation owns `view-09` and `view-24`; its canonical config and command tests cover both. View removes its false Foundation/Validation requirements while retaining optional Foundation directives through Composer `suggest`. The byte-identical Translation stringable boundary is separately owned as `translation-10`; View's sibling boundary is complete under `view-28` without editing the active Translation worktree.
+- **Correctness and parity:** Failed renders now clear loop frames before they can corrupt later loop metadata. Slot cleanup closes a retained-state leak—no later render reads stale slot state, but rendered slot HTML and `ComponentSlot` objects no longer remain in coroutine context for the rest of the request. Components resolve current nested, slot, enum, attribute, and directive forms. Inline templates recognize legitimate empty files and repair incomplete files. Compiled views honor disabled caching, recover after verified-file deletion, and always pop diagnostic path state. Top-level directive parsing preserves commas in conditions, stack names, nested expressions, and inline JSON arrays; `@json` no longer silently replaces its `JSON_HEX_*` defaults and weakens attribute-context escaping. Standard cleanup resets all Component and CompilerEngine static state without exposing the new Component mutator to templates. Provider visibility, named arguments, protected compiler contracts, facades, dependencies, and current integration scenarios are restored. `@elsePushIf`, exact inline publication, loop cleanup, directory-boundary roots, worker/coroutine echo ownership, render-before-store section content, exhaustive path cleanup, and complete directive splitting intentionally correct behavior beyond current Laravel without removing a useful Laravel API.
+- **Important rejected concerns and closed limitations:** Do not add publication locks, retries, polling, file watchers, cache eviction, a section stack, compiler/Factory clones, request-scoped services, render-state snapshots, placeholder registries, parsed-expression caches, directive grammar objects, or compatibility aliases. Directive splitting uses one compile-time PHP-token pass whenever an expression contains a comma; comma counts cannot safely identify top-level separators. Class components still require their explicit alias before data, so no undocumented two-argument class grammar or synthesized alias was added. Dynamic inline templates still retain one fixed-size key and compiled file per distinct template; bounding cardinality would require unjustified eviction machinery. The opaque parent-placeholder salt is immutable worker state and needs no test reset because stored sections are already cleared. Compiled output deliberately names the base Factory, so overriding the protected salt on a Factory subclass is not a complete cached-template extension point. `@parent` inside a separately compiled include no longer depends on cold-cache ordering; that unsupported pattern consistently uses the include's empty compile-pass placeholder instead of freezing whichever outer section happened to compile first.
+- **Cross-package revalidation:** The complete View surface retains `view-01` request overlay precedence/restoration and revalidates `reflection-02` through closure inference and component/compiler reflection. Foundation owns `view-09` and `view-24`; its canonical config and command tests cover both. Testing's authoritative subscriber invokes View's standardized `view-37` cleanup hooks. Testbench's duplicate framework-reset registry is deleted; its raw PHPUnit base owns exception-handler restoration through the same protected seam and exhaustive failure ordering as the Components base. Foundation documents that caller-driven exception because it uniquely needs the active test case, and Testing covers both base classes' cleanup-failure path. Boost's reserved-keyword list matches the complete base Component filter. The later full Boost, Testbench, and Testing audits are indexed. View removes its false Foundation/Validation requirements while retaining optional Foundation directives through Composer `suggest`. The byte-identical Translation stringable boundary is separately owned as `translation-10`; View's sibling boundary is complete under `view-28` without editing the active Translation worktree.
+- **Upstream-ready defects:** Current Laravel shares the naive comma splitting in `@json`, conditional stacks, and once-only push/prepend directives. Multi-key inline JSON can silently lose the default `JSON_HEX_*` flags; the stack forms emit malformed argument boundaries for literal or nested commas. These sites are ready for an owner-coordinated upstream report; no external report is part of this branch.
- **Implementation and cleanup:** Compiler, layout, component, engine, provider, Factory, finder, and package boundaries now use their final ownership model. The obsolete footer property, parent-placeholder context map/getter, validation middleware, duplicate defaults, stale dependencies, superseded compiled files through marker `v3`, and inaccurate docs/comments are removed. Facades are generated from the corrected concrete methods rather than edited by hand.
-- **Regression tests:** Counterfactual coverage proves slot/loop cleanup, exact inline publication—including the complete-file no-write branch—and bounded keys, nested/unnamed/enum components, conditional-stack parsing, sequential and concurrent compile ownership, nested/failing/concurrent echo overrides including Mail, footer/end-directive contracts, lazy defaults, verified-fresh cache behavior, exhaustive compiled-path cleanup, provider/config/metadata/facade contracts, root-safe cache traversal, and current full-app View behavior. The protected slot-context assertion pins retained-state cleanup because no output path rereads stale slots. The Xdebug-only `ParseError` branch is documented at the source boundary without adding a synthetic test seam; ordinary incomplete expressions cover the trailing-token guard.
-- **Performance and complexity:** The component path adds one xxh128 digest per uncached lookup and filesystem checks only before the existing static name cache is populated. Compiler isolation adds constant-time context operations only at compilation boundaries; echo overrides add one context read per escaped echo during compilation, not rendering. Parent placeholders use a fast digest instead of retaining a per-section map. Fresh compiled views retain their one-check-per-worker fast path; only the render immediately after a stale compile performs one additional freshness check next time. Cache-root work is deployment-only. No request path gains a lock, retry, yield, poll, watcher, container loop, network call, serialization layer, or unbounded new state.
-- **Laravel-facing result:** Supported View and Blade method names, named arguments, provider extension points, protected compiler hooks, component syntax, facades, configuration, and render behavior are compatible or restored to current Laravel. Alias-first `Blade::component()` and render-before-store View section content remain the two documented public Hypervel differences. The removed `getParentPlaceholder()` was a superseded Hypervel divergence, not a current Laravel API.
-- **Validation and review:** Changed test files and the combined View, Integration/View, Foundation View, Mail View, facade, and metadata coverage are green. Facades and split metadata have been regenerated and checked. After the final self-review corrections, the authoritative `composer fix` gate passed formatting, both PHPStan configurations, the complete parallel suite, Testbench package mode, and dogfood. Review-amendment coverage, both PHPStan configurations, formatting, and `git diff --check` are green; independent review verified the final tree and signed off with no remaining finding.
+- **Regression tests:** Counterfactual coverage proves slot/loop cleanup, exact inline publication—including the complete-file no-write branch—and bounded keys, nested/unnamed/enum components, conditional and once-only stack parsing with literal/nested commas and generated IDs, HTML-safe JSON options and depth, complete static resets and bare-component data keys, sequential and concurrent compile ownership, nested/failing/concurrent echo overrides including Mail, footer/end-directive contracts, lazy defaults, verified-fresh cache behavior, exhaustive compiled-path cleanup, provider/config/metadata/facade contracts, root-safe cache traversal, and current full-app View behavior. The protected slot-context assertion pins retained-state cleanup because no output path rereads stale slots. The Xdebug-only `ParseError` branches are contained at their source boundaries without adding a synthetic test seam; ordinary incomplete expressions cover the trailing-token guard.
+- **Performance and complexity:** The component path adds one xxh128 digest per uncached lookup and filesystem checks only before the existing static name cache is populated. Compiler isolation adds constant-time context operations only at compilation boundaries; echo overrides add one context read per escaped echo during compilation, not rendering. Comma-free affected directives return immediately; comma-bearing directives pay one bounded `token_get_all()` pass during template compilation only, with no render-time work. Parent placeholders use a fast digest instead of retaining a per-section map. Fresh compiled views retain their one-check-per-worker fast path; only the render immediately after a stale compile performs one additional freshness check next time. Cache-root work is deployment-only. Static cleanup and metadata changes run only at explicit reset/test boundaries. No request path gains a lock, retry, yield, poll, watcher, container loop, network call, serialization layer, or unbounded new state.
+- **Laravel-facing result:** Supported View and Blade method names, named arguments, provider extension points, protected compiler hooks, component syntax, facades, configuration, and render behavior are compatible or restored to current Laravel. Comma-bearing supported directives now compile by top-level arguments, and multi-key inline `@json` arrays retain HTML-safe default flags. Alias-first `Blade::component()` and render-before-store View section content remain the two documented public Hypervel differences. The removed `getParentPlaceholder()` was a superseded Hypervel divergence, not a current Laravel API.
+- **Validation and review:** Changed test files and the combined View, Integration/View, Foundation View, Mail View, facade, and metadata coverage are green. Facades and split metadata have been regenerated and checked. After the final self-review corrections, the authoritative `composer fix` gate passed formatting, both PHPStan configurations, the complete parallel suite, Testbench package mode, and dogfood. Review-amendment coverage, both PHPStan configurations, formatting, and `git diff --check` are green; independent review verified the final follow-up tree and signed off with no remaining finding.
- **Assessment:** Every accepted View finding is implemented at its owning boundary without a local workaround, speculative abstraction, stale compatibility path, meaningful hot-path regression, or unintended Laravel API break. No View TODO, deferred defect, or open workflow step remains.
diff --git a/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md b/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
index 4d9eb6c5c..59c331598 100644
--- a/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
+++ b/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
@@ -2,7 +2,7 @@
## Status and scope
-**Status:** Complete; implementation, validation, self-review, and code review signed off.
+**Status:** Complete; follow-up implementation, validation, self-review, and code review signed off.
Complete the View audit against:
@@ -135,7 +135,7 @@ The package owns filesystem publication and output-buffer cleanup, but no persis
### Upstream and compatibility
-The accepted maintenance set was checked against current Laravel source, unit tests, `tests/Integration/View`, and the originating changes listed in the audit evidence. Preserve Hypervel's worker-lived freshness/component metadata, coroutine-local request-shared-data overlay (`view-01`), alias-first component registration, and strict string compiler paths. The intentional implementation divergences relevant to this work are exact-size inline publication (`view-04`), corrected `@elsePushIf` parsing (`view-05`), loop cleanup (`view-08`), direct placeholder hashing without Laravel's memo map (`view-15`), directory-boundary cache-root comparison (`view-24`), reserved-namespace replacement (`view-27`), split worker/coroutine echo formats (`view-29`), verified-fresh-only memoization (`view-30`), render-before-store section content (`view-33`), exhaustive compiled-path cleanup (`view-35`), and strict loop/token identity (`view-36`). `view-34` removes a divergence by restoring Laravel's lazy default rendering. These are internal correctness or lifecycle differences and do not remove a useful Laravel-facing API.
+The accepted maintenance set was checked against current Laravel source, unit tests, `tests/Integration/View`, and the originating changes listed in the audit evidence. Preserve Hypervel's worker-lived freshness/component metadata, coroutine-local request-shared-data overlay (`view-01`), alias-first component registration, and strict string compiler paths. The intentional implementation divergences relevant to this work are exact-size inline publication (`view-04`), corrected conditional-stack parsing (`view-05`), loop cleanup (`view-08`), direct placeholder hashing without Laravel's memo map (`view-15`), directory-boundary cache-root comparison (`view-24`), reserved-namespace replacement (`view-27`), split worker/coroutine echo formats (`view-29`), verified-fresh-only memoization (`view-30`), render-before-store section content (`view-33`), exhaustive compiled-path cleanup (`view-35`), strict loop/token identity (`view-36`), standardized static cleanup (`view-37`), internal component-method filtering (`view-38`), and top-level directive argument parsing (`view-39`). `view-34` removes a divergence by restoring Laravel's lazy default rendering. These are internal correctness or lifecycle differences and do not remove a useful Laravel-facing API.
The inherited compiled Factory FQCN means overriding `parentPlaceholderSalt()` on a Factory subclass is not complete for cached `@parent` output. Do not claim otherwise and do not invent a dynamic compiler indirection in this work.
@@ -147,7 +147,7 @@ The inherited compiled Factory FQCN means overriding `parentPlaceholderSalt()` o
| `view-02` | Flush slot and slot-stack state with component state. |
| `view-03` | Terminate a nonempty default `style` before concatenation. |
| `view-04` | Publish missing, empty, or partial inline templates with `Filesystem::replace()`. |
-| `view-05` | Parse comma-bearing `@pushIf` and `@elsePushIf` expressions like Laravel while preserving ordinary two-argument output. |
+| `view-05` | Parse comma-bearing conditions in `@pushIf` and `@elsePushIf`; superseded parsing is consolidated by `view-39`. |
| `view-06` | Resolve `Namespace\Accordion\Accordion` for nested default components. |
| `view-07` | Scope the compiler's current section name to one coroutine and compile pass, and define standalone `@parent` as the empty-section placeholder. |
| `view-08` | Flush loop frames through `Factory::flushState()`. |
@@ -179,6 +179,10 @@ The inherited compiled Factory FQCN means overriding `parentPlaceholderSalt()` o
| `view-34` | Do not render a default View when `yieldContent()` finds an existing section. |
| `view-35` | Pop the compiled-path stack in `finally` across success and failure. |
| `view-36` | Use strict identity for the six loose loop/token comparisons in View source. |
+| `view-37` | Give `Component` and `CompilerEngine` complete standardized static cleanup hooks without exposing the new worker-global mutator to templates. |
+| `view-38` | Remove `Component::ignoredParameterNames()` from template scope as internal metadata. |
+| `view-39` | Split top-level arguments correctly across conditional stacks, once-only stacks, and `@json`, preserving HTML-safe JSON flags. |
+| `view-40` | Complete concise method metadata and the slot-context return type in touched View source. |
## Implementation design
@@ -220,24 +224,30 @@ if ($key === 'style') {
}
```
-Share only the parsing rule genuinely used twice:
+Split only top-level directive arguments in the shared compiler. A no-comma expression returns immediately; comma-bearing expressions use PHP's tokenizer so strings and nested `()`, `[]`, and `{}` remain intact:
```php
-protected function parseConditionalStackExpression(string $expression): array
+protected function splitTopLevel(string $expression, int $limit): array
{
- $segments = explode(',', $this->stripParentheses($expression));
+ $expression = $this->stripParentheses($expression);
- if (count($segments) > 2) {
- $stack = array_pop($segments);
+ if (! str_contains($expression, ',')) {
+ return [$expression];
+ }
- return [implode(',', $segments), trim($stack)];
+ try {
+ $tokens = token_get_all('
Date: Thu, 6 Aug 2026 14:40:05 +0000
Subject: [PATCH 16/16] fix(view): clear compile-pass state on failure
Initialize raw Blade blocks at the start of each compilation and release raw-block and component-hash state in a finally block. This prevents caught compiler failures and incomplete component compilations from retaining template bytes or hash frames in coroutine or fallback process context.\n\nPreserve Laravel's public component-hash seed contract by leaving entry-time hashes intact for the immediately following compile pass, while ensuring they cannot survive that pass. Add load-bearing success and failure regressions that preserve the original exception, prove fresh subsequent compilation, and retain the seed-then-compile sequence.\n\nRecord view-41, the bounded lifecycle contract, validation, and final review status in the View plan and audit ledger.
---
...-coroutine-state-lifecycle-audit-ledger.md | 19 ++--
...orrectness-lifecycle-and-current-parity.md | 29 +++++-
src/view/src/Compilers/BladeCompiler.php | 93 +++++++++++--------
tests/View/Blade/BladeComponentsTest.php | 75 +++++++++++----
tests/View/ViewBladeCompilerTest.php | 23 +++++
5 files changed, 171 insertions(+), 68 deletions(-)
diff --git a/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md b/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
index d33b30edf..d66fdda72 100644
--- a/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
+++ b/docs/plans/2026-07-12-0915-framework-coroutine-state-lifecycle-audit-ledger.md
@@ -1809,7 +1809,7 @@ Append package entries in checklist order. Keep each entry compact but complete
### Complete View correctness, lifecycle, and current parity
-- **Status and inspected surface:** Complete; implementation, focused validation, the authoritative gate, fresh self-review, and independent code review are signed off. The audit covered every View source and test file; Foundation configuration and `view:cache`; Support facade metadata; Mail's Markdown consumer; Boost documentation; split metadata; current Laravel View source, tests, documentation, and full-app fixtures; and carried `view-01` and `reflection-02`. The detailed design is recorded in [`2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md`](2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md).
+- **Status and inspected surface:** Complete; implementation, focused validation, the authoritative gate, fresh self-review, and independent code review—including the compile-pass failure cleanup follow-up—are signed off. The audit covered every View source and test file; Foundation configuration and `view:cache`; Support facade metadata; Mail's Markdown consumer; Boost documentation; split metadata; current Laravel View source, tests, documentation, and full-app fixtures; and carried `view-01` and `reflection-02`. The detailed design is recorded in [`2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md`](2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md).
| Findings | Final decision |
|---|---|
@@ -1828,15 +1828,16 @@ Append package entries in checklist order. Keep each entry compact but complete
| `view-38` | Remove `Component::ignoredParameterNames()` from template scope as internal metadata, completing the base Component method filter without claiming a separate runtime defect. |
| `view-39` | Split top-level arguments across conditional stacks, once-only stacks, and `@json`, preserving nested expressions and HTML-safe JSON flags. |
| `view-40` | Complete concise method metadata and the slot-context return type in touched View source. |
+| `view-41` | Clear raw-block and component-hash state after every compile pass while preserving public component-hash seeds for the immediately following pass. |
-- **Architecture and worker ownership:** Factory, BladeCompiler, EngineResolver, component metadata, compiled-template names, and verified freshness remain worker-lived. Render state and transient compiler state remain coroutine-local. Every compile pass initializes its current-section owner, so sequential or cross-instance compilers in one coroutine cannot leak a prior section into standalone `@parent`; no compiler stack or clone was added. Inline publication stays lock-free at the existing atomic Filesystem replacement boundary.
-- **Correctness and parity:** Failed renders now clear loop frames before they can corrupt later loop metadata. Slot cleanup closes a retained-state leak—no later render reads stale slot state, but rendered slot HTML and `ComponentSlot` objects no longer remain in coroutine context for the rest of the request. Components resolve current nested, slot, enum, attribute, and directive forms. Inline templates recognize legitimate empty files and repair incomplete files. Compiled views honor disabled caching, recover after verified-file deletion, and always pop diagnostic path state. Top-level directive parsing preserves commas in conditions, stack names, nested expressions, and inline JSON arrays; `@json` no longer silently replaces its `JSON_HEX_*` defaults and weakens attribute-context escaping. Standard cleanup resets all Component and CompilerEngine static state without exposing the new Component mutator to templates. Provider visibility, named arguments, protected compiler contracts, facades, dependencies, and current integration scenarios are restored. `@elsePushIf`, exact inline publication, loop cleanup, directory-boundary roots, worker/coroutine echo ownership, render-before-store section content, exhaustive path cleanup, and complete directive splitting intentionally correct behavior beyond current Laravel without removing a useful Laravel API.
-- **Important rejected concerns and closed limitations:** Do not add publication locks, retries, polling, file watchers, cache eviction, a section stack, compiler/Factory clones, request-scoped services, render-state snapshots, placeholder registries, parsed-expression caches, directive grammar objects, or compatibility aliases. Directive splitting uses one compile-time PHP-token pass whenever an expression contains a comma; comma counts cannot safely identify top-level separators. Class components still require their explicit alias before data, so no undocumented two-argument class grammar or synthesized alias was added. Dynamic inline templates still retain one fixed-size key and compiled file per distinct template; bounding cardinality would require unjustified eviction machinery. The opaque parent-placeholder salt is immutable worker state and needs no test reset because stored sections are already cleared. Compiled output deliberately names the base Factory, so overriding the protected salt on a Factory subclass is not a complete cached-template extension point. `@parent` inside a separately compiled include no longer depends on cold-cache ordering; that unsupported pattern consistently uses the include's empty compile-pass placeholder instead of freezing whichever outer section happened to compile first.
+- **Architecture and worker ownership:** Factory, BladeCompiler, EngineResolver, component metadata, compiled-template names, and verified freshness remain worker-lived. Render state and transient compiler state remain coroutine-local. Every compile pass initializes its current-section, footer, and protected raw-block owners, then clears raw blocks and component hashes on exit. Component hashes are not reset at entry because their public facade API may seed the immediately following pass. No compiler stack, snapshot, or clone was added. Inline publication stays lock-free at the existing atomic Filesystem replacement boundary.
+- **Correctness and parity:** Failed renders now clear loop frames before they can corrupt later loop metadata. Slot cleanup closes a retained-state leak—no later render reads stale slot state, but rendered slot HTML and `ComponentSlot` objects no longer remain in coroutine context for the rest of the request. Failed or incomplete compilations no longer retain raw template bytes or component hashes in the active context, and successful opening-only component compilations are bounded as well. Components resolve current nested, slot, enum, attribute, and directive forms. Inline templates recognize legitimate empty files and repair incomplete files. Compiled views honor disabled caching, recover after verified-file deletion, and always pop diagnostic path state. Top-level directive parsing preserves commas in conditions, stack names, nested expressions, and inline JSON arrays; `@json` no longer silently replaces its `JSON_HEX_*` defaults and weakens attribute-context escaping. Standard cleanup resets all Component and CompilerEngine static state without exposing the new Component mutator to templates. Provider visibility, named arguments, protected compiler contracts, facades, dependencies, and current integration scenarios are restored. `@elsePushIf`, exact inline publication, loop cleanup, directory-boundary roots, worker/coroutine echo ownership, render-before-store section content, exhaustive path cleanup, complete directive splitting, and compile-state failure cleanup intentionally correct behavior beyond current Laravel without removing a useful Laravel API.
+- **Important rejected concerns and closed limitations:** Do not add publication locks, retries, polling, file watchers, cache eviction, a section stack, compiler/Factory clones, request-scoped services, render-state snapshots, compile-state snapshots, placeholder registries, parsed-expression caches, directive grammar objects, or compatibility aliases. A component hash seeded through the public API must be consumed by the immediately following compile pass; arbitrary intervening passes are not preserved because doing so would retain stale stack state. Directive splitting uses one compile-time PHP-token pass whenever an expression contains a comma; comma counts cannot safely identify top-level separators. Class components still require their explicit alias before data, so no undocumented two-argument class grammar or synthesized alias was added. Dynamic inline templates still retain one fixed-size key and compiled file per distinct template; bounding cardinality would require unjustified eviction machinery. The opaque parent-placeholder salt is immutable worker state and needs no test reset because stored sections are already cleared. Compiled output deliberately names the base Factory, so overriding the protected salt on a Factory subclass is not a complete cached-template extension point. `@parent` inside a separately compiled include no longer depends on cold-cache ordering; that unsupported pattern consistently uses the include's empty compile-pass placeholder instead of freezing whichever outer section happened to compile first.
- **Cross-package revalidation:** The complete View surface retains `view-01` request overlay precedence/restoration and revalidates `reflection-02` through closure inference and component/compiler reflection. Foundation owns `view-09` and `view-24`; its canonical config and command tests cover both. Testing's authoritative subscriber invokes View's standardized `view-37` cleanup hooks. Testbench's duplicate framework-reset registry is deleted; its raw PHPUnit base owns exception-handler restoration through the same protected seam and exhaustive failure ordering as the Components base. Foundation documents that caller-driven exception because it uniquely needs the active test case, and Testing covers both base classes' cleanup-failure path. Boost's reserved-keyword list matches the complete base Component filter. The later full Boost, Testbench, and Testing audits are indexed. View removes its false Foundation/Validation requirements while retaining optional Foundation directives through Composer `suggest`. The byte-identical Translation stringable boundary is separately owned as `translation-10`; View's sibling boundary is complete under `view-28` without editing the active Translation worktree.
- **Upstream-ready defects:** Current Laravel shares the naive comma splitting in `@json`, conditional stacks, and once-only push/prepend directives. Multi-key inline JSON can silently lose the default `JSON_HEX_*` flags; the stack forms emit malformed argument boundaries for literal or nested commas. These sites are ready for an owner-coordinated upstream report; no external report is part of this branch.
- **Implementation and cleanup:** Compiler, layout, component, engine, provider, Factory, finder, and package boundaries now use their final ownership model. The obsolete footer property, parent-placeholder context map/getter, validation middleware, duplicate defaults, stale dependencies, superseded compiled files through marker `v3`, and inaccurate docs/comments are removed. Facades are generated from the corrected concrete methods rather than edited by hand.
-- **Regression tests:** Counterfactual coverage proves slot/loop cleanup, exact inline publication—including the complete-file no-write branch—and bounded keys, nested/unnamed/enum components, conditional and once-only stack parsing with literal/nested commas and generated IDs, HTML-safe JSON options and depth, complete static resets and bare-component data keys, sequential and concurrent compile ownership, nested/failing/concurrent echo overrides including Mail, footer/end-directive contracts, lazy defaults, verified-fresh cache behavior, exhaustive compiled-path cleanup, provider/config/metadata/facade contracts, root-safe cache traversal, and current full-app View behavior. The protected slot-context assertion pins retained-state cleanup because no output path rereads stale slots. The Xdebug-only `ParseError` branches are contained at their source boundaries without adding a synthetic test seam; ordinary incomplete expressions cover the trailing-token guard.
-- **Performance and complexity:** The component path adds one xxh128 digest per uncached lookup and filesystem checks only before the existing static name cache is populated. Compiler isolation adds constant-time context operations only at compilation boundaries; echo overrides add one context read per escaped echo during compilation, not rendering. Comma-free affected directives return immediately; comma-bearing directives pay one bounded `token_get_all()` pass during template compilation only, with no render-time work. Parent placeholders use a fast digest instead of retaining a per-section map. Fresh compiled views retain their one-check-per-worker fast path; only the render immediately after a stale compile performs one additional freshness check next time. Cache-root work is deployment-only. Static cleanup and metadata changes run only at explicit reset/test boundaries. No request path gains a lock, retry, yield, poll, watcher, container loop, network call, serialization layer, or unbounded new state.
-- **Laravel-facing result:** Supported View and Blade method names, named arguments, provider extension points, protected compiler hooks, component syntax, facades, configuration, and render behavior are compatible or restored to current Laravel. Comma-bearing supported directives now compile by top-level arguments, and multi-key inline `@json` arrays retain HTML-safe default flags. Alias-first `Blade::component()` and render-before-store View section content remain the two documented public Hypervel differences. The removed `getParentPlaceholder()` was a superseded Hypervel divergence, not a current Laravel API.
-- **Validation and review:** Changed test files and the combined View, Integration/View, Foundation View, Mail View, facade, and metadata coverage are green. Facades and split metadata have been regenerated and checked. After the final self-review corrections, the authoritative `composer fix` gate passed formatting, both PHPStan configurations, the complete parallel suite, Testbench package mode, and dogfood. Review-amendment coverage, both PHPStan configurations, formatting, and `git diff --check` are green; independent review verified the final follow-up tree and signed off with no remaining finding.
-- **Assessment:** Every accepted View finding is implemented at its owning boundary without a local workaround, speculative abstraction, stale compatibility path, meaningful hot-path regression, or unintended Laravel API break. No View TODO, deferred defect, or open workflow step remains.
+- **Regression tests:** Counterfactual coverage proves slot/loop cleanup, exact inline publication—including the complete-file no-write branch—and bounded keys, nested/unnamed/enum components, conditional and once-only stack parsing with literal/nested commas and generated IDs, HTML-safe JSON options and depth, complete static resets and bare-component data keys, sequential and concurrent compile ownership, raw-block and component-hash cleanup after caught failures, successful opening-only hash cleanup, the public immediate seed-then-compile sequence, nested/failing/concurrent echo overrides including Mail, footer/end-directive contracts, lazy defaults, verified-fresh cache behavior, exhaustive compiled-path cleanup, provider/config/metadata/facade contracts, root-safe cache traversal, and current full-app View behavior. The protected slot-context assertion pins retained-state cleanup because no output path rereads stale slots. The Xdebug-only `ParseError` branches are contained at their source boundaries without adding a synthetic test seam; ordinary incomplete expressions cover the trailing-token guard.
+- **Performance and complexity:** The component path adds one xxh128 digest per uncached lookup and filesystem checks only before the existing static name cache is populated. Compiler isolation adds constant-time context operations only at compilation boundaries; compile-state initialization and cleanup each use one batched context resolution. Echo overrides add one context read per escaped echo during compilation, not rendering. Comma-free affected directives return immediately; comma-bearing directives pay one bounded `token_get_all()` pass during template compilation only, with no render-time work. Parent placeholders use a fast digest instead of retaining a per-section map. Fresh compiled views retain their one-check-per-worker fast path; only the render immediately after a stale compile performs one additional freshness check next time. Cache-root work is deployment-only. Static cleanup and metadata changes run only at explicit reset/test boundaries. No request path gains a lock, retry, yield, poll, watcher, container loop, network call, serialization layer, or unbounded new state.
+- **Laravel-facing result:** Supported View and Blade method names, named arguments, provider extension points, protected compiler hooks, component syntax, facades, configuration, and render behavior are compatible or restored to current Laravel. `newComponentHash()` remains facade-exposed and supports Laravel's immediate seed-then-compile sequence; a seed no longer survives an unrelated intervening compile pass. Comma-bearing supported directives now compile by top-level arguments, and multi-key inline `@json` arrays retain HTML-safe default flags. Alias-first `Blade::component()` and render-before-store View section content remain the two documented public Hypervel differences. The removed `getParentPlaceholder()` was a superseded Hypervel divergence, not a current Laravel API.
+- **Validation and review:** Changed test files and the combined View, Integration/View, Foundation View, Mail View, facade, and metadata coverage are green. Facades and split metadata have been regenerated and checked. The prior authoritative `composer fix` gate passed formatting, both PHPStan configurations, the complete parallel suite, Testbench package mode, and dogfood. The compile-state follow-up passes the complete View group, both PHPStan configurations, targeted formatting, and `git diff --check`; independent follow-up review verified the final tree and signed off with no remaining finding.
+- **Assessment:** Every accepted View finding is implemented at its owning boundary without a local workaround, speculative abstraction, stale compatibility path, meaningful hot-path regression, or unintended Laravel API removal. No View TODO, deferred defect, or open workflow step remains.
diff --git a/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md b/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
index 59c331598..f9f5d03df 100644
--- a/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
+++ b/docs/plans/2026-08-06-0916-view-correctness-lifecycle-and-current-parity.md
@@ -13,7 +13,7 @@ Complete the View audit against:
This is a correction and parity pass, not a redesign. Preserve Hypervel's worker-singleton Factory/compiler architecture, coroutine-local render and compile state, worker-lived immutable metadata and freshness caches, lock-free component creation, alias-first `Blade::component()` registration, strict string compiler paths, and request-shared-data overlay. No accepted change adds a lock, watcher, retry, registry, request-scoped Factory/compiler, render-state snapshot, eviction policy, or compatibility shim.
-No useful Laravel API is removed or narrowed. The restored provider, compiler, layout, dynamic-component, named-argument, and PHPDoc surfaces improve parity. The intentional alias-first API and strict path model remain documented Hypervel differences. The `@elsePushIf` repair and failed-render loop cleanup correct defects that current Laravel also carries.
+No useful Laravel API is removed. The public component-hash seed path remains supported when the immediately following compile pass consumes it; retaining a seed across unrelated intervening passes is removed to bound stale state. The restored provider, compiler, layout, dynamic-component, named-argument, and PHPDoc surfaces improve parity. The intentional alias-first API and strict path model remain documented Hypervel differences. The `@elsePushIf` repair and failed-render loop cleanup correct defects that current Laravel also carries.
### Approved tradeoffs
@@ -139,6 +139,8 @@ The accepted maintenance set was checked against current Laravel source, unit te
The inherited compiled Factory FQCN means overriding `parentPlaceholderSalt()` on a Factory subclass is not complete for cached `@parent` output. Do not claim otherwise and do not invent a dynamic compiler indirection in this work.
+`stringable()` accepts only the two registration shapes its string-keyed handler map can represent: a class string plus handler, or a typed closure from which the class is inferred. Other callable shapes cannot be array keys, so the corrected `Closure|string` type does not remove working behavior.
+
### Findings
| ID | Result |
@@ -183,6 +185,7 @@ The inherited compiled Factory FQCN means overriding `parentPlaceholderSalt()` o
| `view-38` | Remove `Component::ignoredParameterNames()` from template scope as internal metadata. |
| `view-39` | Split top-level arguments correctly across conditional stacks, once-only stacks, and `@json`, preserving HTML-safe JSON flags. |
| `view-40` | Complete concise method metadata and the slot-context return type in touched View source. |
+| `view-41` | Clear raw-block and component-hash compile state after success or failure while preserving the public immediate seed-then-compile component contract. |
## Implementation design
@@ -396,6 +399,27 @@ Keep the two supported registration forms and regenerate facade metadata from th
Port the bounded upstream maintenance in place: direct empty-array checks, explicit `implode('', ...)`, direct `Stringable` use, current component reflection/filtering, alias derivation, and the two missing uncountable-loop tests. Make the complete loose-comparison inventory strict: `ManagesLoops` uses `===` for initial `last`, incremented `first`, and incremented `last`; `BladeCompiler::parseToken()` uses `=== T_INLINE_HTML`; and both parenthesis-token comparisons use `===`. Catch only `ParseError` around `token_get_all()` with a concise WHY naming Xdebug. Do not add a synthetic runtime seam.
+Raw blocks are wholly protected compile-pass state, so initialize their context entry at `compileString()` entry. Component hashes differ: `newComponentHash()` and `compileEndComponentClass()` are public and facade-exposed, and Laravel's tests seed a hash immediately before the consuming compile pass. Preserve a hash present at entry, then clear both stores in `finally` after success or failure:
+
+```php
+CoroutineContext::setMany([
+ static::LAST_SECTION_CONTEXT_KEY => '',
+ static::FOOTER_CONTEXT_KEY => [],
+ static::RAW_BLOCKS_CONTEXT_KEY => [],
+]);
+
+try {
+ // Compile the complete template.
+} finally {
+ CoroutineContext::setMany([
+ static::RAW_BLOCKS_CONTEXT_KEY => [],
+ static::COMPONENT_HASH_STACK_CONTEXT_KEY => [],
+ ]);
+}
+```
+
+A public hash seed therefore belongs to the immediately following compile pass; unrelated intervening passes do not preserve it. Do not snapshot or restore arbitrary stack contents: well-formed class component tags open and close in one pass, and retaining pass state would preserve the verified worker-memory leak. Keep `restoreRawContent()` unchanged and retain the fail-fast typed empty-pop behavior.
+
### 5. Restore the parent-placeholder and section-content model (`view-15`, `view-33`, `view-34`)
Use one immutable worker salt and no section map:
@@ -553,6 +577,7 @@ Preserve siblings such as `/views` and `/views-admin`, collapse trailing-separat
- `tests/View/Blade/BladeJsonTest.php`: multi-key inline arrays and interpolated strings retain default HTML-safe flags and depth.
- `tests/View/Blade/BladeComponentTagCompilerTest.php`: nested custom namespace, unnamed slot, and backed-enum paths where owned.
- `tests/View/ViewBladeCompilerTest.php` and focused Blade suites: compiler marker `v3`, coroutine section isolation, echo-format ownership, footer signature, end-directive overrides, Xdebug-only `ParseError` source behavior without a synthetic seam, supported stringable forms, strict maintenance, and loop cases.
+- `tests/View/ViewBladeCompilerTest.php` and `tests/View/Blade/BladeComponentsTest.php`: clear raw blocks and component hashes after caught compile failures, preserve the original exception, preserve fresh subsequent compilation, and retain the public immediate seed-then-compile component sequence.
- `tests/View/ViewCompilerEngineTest.php`: cache true/false, deletion recovery, compiled-path cleanup, and both cache-reset entry points.
- `tests/View/ViewComponentTest.php` and `ViewStaticStateTest.php`: internal methods stay out of component data and standardized cleanup resets every static field.
- Raw Testbench and shared Mockery-lifecycle tests: central exception-handler ownership, exhaustive teardown ordering, and first-failure preservation without a second framework-reset registry.
@@ -577,7 +602,7 @@ Before implementation, set all three core routing-index bullets to this View wor
- replace the “later full `view` audit” marker inside the `view-01` and `reflection-02` rows of the cross-package dependency index with complete revalidation;
- tick the core package checklist's `view` entry only in the final bookkeeping commit;
-- add one complete View ledger entry covering `view-02` through `view-40`, rejected machinery, performance, validation, and the detail-plan link;
+- add one complete View ledger entry covering `view-02` through `view-41`, rejected machinery, performance, validation, and the detail-plan link;
- amend the earlier `view-01` and `reflection-02` entries with View revalidation;
- add only genuine cross-package rows: Foundation-owned config/command and exception-handler changes, the Boost reserved-name documentation, Testbench and Testing cleanup ownership, and the separately routed `translation-10` twin;
- record the inherited parent-placeholder subclass limitation without presenting speculative machinery as unresolved work;
diff --git a/src/view/src/Compilers/BladeCompiler.php b/src/view/src/Compilers/BladeCompiler.php
index 156015072..f1261f81d 100644
--- a/src/view/src/Compilers/BladeCompiler.php
+++ b/src/view/src/Compilers/BladeCompiler.php
@@ -249,55 +249,68 @@ public function setPath(string $path): void
*/
public function compileString(string $value): string
{
- CoroutineContext::set(static::LAST_SECTION_CONTEXT_KEY, '');
- CoroutineContext::set(static::FOOTER_CONTEXT_KEY, []);
- $result = '';
+ // Raw blocks are pass-owned; component hashes may be publicly seeded for this pass.
+ CoroutineContext::setMany([
+ static::LAST_SECTION_CONTEXT_KEY => '',
+ static::FOOTER_CONTEXT_KEY => [],
+ static::RAW_BLOCKS_CONTEXT_KEY => [],
+ ]);
- foreach ($this->prepareStringsForCompilationUsing as $callback) {
- $value = $callback($value);
- }
+ try {
+ $result = '';
- $value = $this->storeUncompiledBlocks($value);
+ foreach ($this->prepareStringsForCompilationUsing as $callback) {
+ $value = $callback($value);
+ }
- // First we will compile the Blade component tags. This is a precompile style
- // step which compiles the component Blade tags into @component directives
- // that may be used by Blade. Then we should call any other precompilers.
- $value = $this->compileComponentTags(
- $this->compileComments($value)
- );
+ $value = $this->storeUncompiledBlocks($value);
- foreach ($this->precompilers as $precompiler) {
- $value = $precompiler($value);
- }
+ // First we will compile the Blade component tags. This is a precompile style
+ // step which compiles the component Blade tags into @component directives
+ // that may be used by Blade. Then we should call any other precompilers.
+ $value = $this->compileComponentTags(
+ $this->compileComments($value)
+ );
- // Here we will loop through all of the tokens returned by the Zend lexer and
- // parse each one into the corresponding valid PHP. We will then have this
- // template as the correctly rendered PHP that can be rendered natively.
- foreach (token_get_all($value) as $token) {
- $result .= is_array($token) ? $this->parseToken($token) : $token;
- }
+ foreach ($this->precompilers as $precompiler) {
+ $value = $precompiler($value);
+ }
- if (CoroutineContext::get(static::RAW_BLOCKS_CONTEXT_KEY, []) !== []) {
- $result = $this->restoreRawContent($result);
- }
+ // Here we will loop through all of the tokens returned by the Zend lexer and
+ // parse each one into the corresponding valid PHP. We will then have this
+ // template as the correctly rendered PHP that can be rendered natively.
+ foreach (token_get_all($value) as $token) {
+ $result .= is_array($token) ? $this->parseToken($token) : $token;
+ }
- // If there are any footer lines that need to get added to a template we will
- // add them here at the end of the template. This gets used mainly for the
- // template inheritance via the extends keyword that should be appended.
- $footers = CoroutineContext::get(static::FOOTER_CONTEXT_KEY, []);
- if (count($footers) > 0) {
- $result = $this->addFooters($result);
- }
+ if (CoroutineContext::get(static::RAW_BLOCKS_CONTEXT_KEY, []) !== []) {
+ $result = $this->restoreRawContent($result);
+ }
- if (! empty($this->echoHandlers)) {
- $result = $this->addBladeCompilerVariable($result);
- }
+ // If there are any footer lines that need to get added to a template we will
+ // add them here at the end of the template. This gets used mainly for the
+ // template inheritance via the extends keyword that should be appended.
+ $footers = CoroutineContext::get(static::FOOTER_CONTEXT_KEY, []);
+ if (count($footers) > 0) {
+ $result = $this->addFooters($result);
+ }
- return str_replace(
- ['##BEGIN-COMPONENT-CLASS##', '##END-COMPONENT-CLASS##'],
- '',
- $result
- );
+ if (! empty($this->echoHandlers)) {
+ $result = $this->addBladeCompilerVariable($result);
+ }
+
+ return str_replace(
+ ['##BEGIN-COMPONENT-CLASS##', '##END-COMPONENT-CLASS##'],
+ '',
+ $result
+ );
+ } finally {
+ // Caught failures must not accumulate pass state in coroutine or process-global context.
+ CoroutineContext::setMany([
+ static::RAW_BLOCKS_CONTEXT_KEY => [],
+ static::COMPONENT_HASH_STACK_CONTEXT_KEY => [],
+ ]);
+ }
}
/**
diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php
index a5b08fcc0..bfa92e362 100644
--- a/tests/View/Blade/BladeComponentsTest.php
+++ b/tests/View/Blade/BladeComponentsTest.php
@@ -5,20 +5,24 @@
namespace Hypervel\Tests\View\Blade;
use Closure;
+use Hypervel\Context\CoroutineContext;
use Hypervel\Contracts\Support\Htmlable;
use Hypervel\Contracts\View\View as ViewContract;
+use Hypervel\View\Compilers\BladeCompiler;
use Hypervel\View\Component;
use Mockery as m;
+use ReflectionClassConstant;
+use RuntimeException;
class BladeComponentsTest extends AbstractBladeTestCase
{
- public function testComponentsAreCompiled()
+ public function testComponentsAreCompiled(): void
{
$this->assertSame('startComponent(\'foo\', ["foo" => "bar"]); ?>', $this->compiler->compileString('@component(\'foo\', ["foo" => "bar"])'));
$this->assertSame('startComponent(\'foo\'); ?>', $this->compiler->compileString('@component(\'foo\')'));
}
- public function testClassComponentsAreCompiled()
+ public function testClassComponentsAreCompiled(): void
{
$this->assertSame(str_replace("\r\n", "\n", '
@@ -28,41 +32,64 @@ public function testClassComponentsAreCompiled()
startComponent($component->resolveView(), $component->data()); ?>'), $this->compiler->compileString('@component(\'Hypervel\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])'));
}
- public function testEndComponentsAreCompiled()
+ public function testClassComponentHashesAreClearedAfterSuccessfulCompilation(): void
+ {
+ $this->compiler->compileString('@component(\'Hypervel\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])');
+
+ $contextKey = (new ReflectionClassConstant(BladeCompiler::class, 'COMPONENT_HASH_STACK_CONTEXT_KEY'))->getValue();
+
+ $this->assertSame([], CoroutineContext::get($contextKey, []));
+ }
+
+ public function testEndComponentsAreCompiled(): void
{
$this->compiler->newComponentHash('foo');
$this->assertSame('renderComponent(); ?>', $this->compiler->compileString('@endcomponent'));
}
- public function testEndComponentClassesAreCompiled()
+ public function testEndComponentClassesAreCompiled(): void
{
$this->compiler->newComponentHash('foo');
- $this->assertSame(str_replace("\r\n", "\n", 'renderComponent(); ?>
-
-
-
-
-
-
-
-
-'), $this->compiler->compileString('@endcomponentClass'));
+ $this->assertSame($this->expectedEndComponentClass(), $this->compiler->compileString('@endcomponentClass'));
+ }
+
+ public function testComponentHashesAreClearedAfterCompilationFailure(): void
+ {
+ $failure = new RuntimeException('failed');
+ $caught = null;
+
+ $this->compiler->directive('fail', fn (): never => throw $failure);
+
+ try {
+ $this->compiler->compileString("@component('Hypervel\\Tests\\View\\Blade\\ComponentStub::class', 'test') @fail");
+ } catch (RuntimeException $exception) {
+ $caught = $exception;
+ }
+
+ $contextKey = (new ReflectionClassConstant(BladeCompiler::class, 'COMPONENT_HASH_STACK_CONTEXT_KEY'))->getValue();
+
+ $this->assertSame($failure, $caught);
+ $this->assertSame([], CoroutineContext::get($contextKey, []));
+
+ $this->compiler->newComponentHash('foo');
+
+ $this->assertSame($this->expectedEndComponentClass(), $this->compiler->compileString('@endcomponentClass'));
}
- public function testSlotsAreCompiled()
+ public function testSlotsAreCompiled(): void
{
$this->assertSame('slot(\'foo\', null, ["foo" => "bar"]); ?>', $this->compiler->compileString('@slot(\'foo\', null, ["foo" => "bar"])'));
$this->assertSame('slot(\'foo\'); ?>', $this->compiler->compileString('@slot(\'foo\')'));
}
- public function testEndSlotsAreCompiled()
+ public function testEndSlotsAreCompiled(): void
{
$this->assertSame('endSlot(); ?>', $this->compiler->compileString('@endslot'));
}
- public function testPropsAreExtractedFromParentAttributesCorrectlyForClassComponents()
+ public function testPropsAreExtractedFromParentAttributesCorrectlyForClassComponents(): void
{
$component = m::mock(ComponentStub::class);
$component->shouldReceive('withName', 'test');
@@ -77,6 +104,20 @@ public function testPropsAreExtractedFromParentAttributesCorrectlyForClassCompon
$this->assertSame('', trim((string) ob_get_clean()));
}
+
+ private function expectedEndComponentClass(): string
+ {
+ return str_replace("\r\n", "\n", 'renderComponent(); ?>
+
+
+
+
+
+
+
+
+');
+ }
}
class ComponentStub extends Component
diff --git a/tests/View/ViewBladeCompilerTest.php b/tests/View/ViewBladeCompilerTest.php
index 538a03f5e..130d022e8 100644
--- a/tests/View/ViewBladeCompilerTest.php
+++ b/tests/View/ViewBladeCompilerTest.php
@@ -4,6 +4,7 @@
namespace Hypervel\Tests\View;
+use Hypervel\Context\CoroutineContext;
use Hypervel\Engine\Channel;
use Hypervel\Filesystem\Filesystem;
use Hypervel\Testing\ParallelTesting;
@@ -12,6 +13,7 @@
use InvalidArgumentException;
use Mockery as m;
use PHPUnit\Framework\Attributes\DataProvider;
+use ReflectionClassConstant;
use RuntimeException;
use function Hypervel\Coroutine\parallel;
@@ -255,6 +257,27 @@ public function testStandaloneParentUsesTheEmptySectionPlaceholder(): void
);
}
+ public function testRawBlocksAreClearedAfterCompilationFailure(): void
+ {
+ $compiler = new BladeCompiler($this->getFiles(), __DIR__);
+ $failure = new RuntimeException('failed');
+ $caught = null;
+
+ $compiler->directive('fail', fn (): never => throw $failure);
+
+ try {
+ $compiler->compileString('@verbatim {{ $retained }} @endverbatim @fail');
+ } catch (RuntimeException $exception) {
+ $caught = $exception;
+ }
+
+ $contextKey = (new ReflectionClassConstant(BladeCompiler::class, 'RAW_BLOCKS_CONTEXT_KEY'))->getValue();
+
+ $this->assertSame($failure, $caught);
+ $this->assertSame([], CoroutineContext::get($contextKey, []));
+ $this->assertSame(' {{ $fresh }} ', $compiler->compileString('@verbatim {{ $fresh }} @endverbatim'));
+ }
+
public function testEchoFormatDefaultIsVisibleInsideSiblingCoroutines(): void
{
$compiler = new BladeCompiler($this->getFiles(), __DIR__);