Skip to content

[ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync#153

Merged
jodavis merged 5 commits intofeature/ADR-162-client-side-layout-updatesfrom
claude/implement-adr-179-1qaNg
Apr 22, 2026
Merged

[ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync#153
jodavis merged 5 commits intofeature/ADR-162-client-side-layout-updatesfrom
claude/implement-adr-179-1qaNg

Conversation

@jodavis
Copy link
Copy Markdown
Owner

@jodavis jodavis commented Apr 20, 2026

Convert ApplicationLifecycle.ExecuteAsync to a recycle loop and implement BlazorAppScope.RecycleAsync().

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 20, 2026

Test Results

452 tests  +452   452 ✅ +452   2m 14s ⏱️ + 2m 14s
 12 suites + 12     0 💤 ±  0 
 12 files   + 12     0 ❌ ±  0 

Results for commit 8d6e511. ± Comparison against base commit b7b6252.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces signal-driven DI scope recycling in the Lifecycle subsystem (including Blazor scope reload support), and adds Playwright browser setup guidance/scripts for restricted sandbox environments.

Changes:

  • Convert ApplicationLifecycle.ExecuteAsync into a recycle loop that reacts to an IApplicationRecycleSignal.
  • Implement BlazorAppScope.RecycleAsync() via JS location.reload and register a concrete ApplicationRecycleSignal in DI.
  • Add/expand lifecycle documentation + unit tests for recycle behavior, and add a sandbox Playwright browser setup script + updated E2E instructions.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/AdaptiveRemote.App.Tests/Services/Lifecycle/ApplicationLifecycleTests.cs Adds recycle-signal unit tests and updates constructor usage to include the signal.
src/AdaptiveRemote.App/Services/Lifecycle/_doc_Lifecycle.md Documents the new recycle loop, pre-initializer behavior, and recycle signal contract.
src/AdaptiveRemote.App/Services/Lifecycle/IApplicationRecycleSignal.cs Adds a lifecycle-internal interface to request and reset recycle signaling.
src/AdaptiveRemote.App/Services/Lifecycle/ApplicationRecycleSignal.cs Adds the concrete recycle signal implementation (CTS-based).
src/AdaptiveRemote.App/Services/Lifecycle/ApplicationLifecycle.cs Implements the recycle loop, linking stoppingToken with the recycle signal token.
src/AdaptiveRemote.App/Logging/MessageLogger.cs Adds a new log event for “Recycling application scope”.
src/AdaptiveRemote.App/Configuration/HostBuilderExtensions.cs Registers IApplicationRecycleSignal as a singleton.
src/AdaptiveRemote.App/Components/BlazorAppScope.cs Implements scope recycle via JS runtime reload.
scripts/setup-playwright-sandbox.sh Adds a sandbox helper to symlink preinstalled Playwright browsers to expected revisions.
CLAUDE.md Updates E2E Playwright setup instructions for dev machines vs sandbox environments.

Comment thread scripts/setup-playwright-sandbox.sh Outdated
Comment thread scripts/setup-playwright-sandbox.sh Outdated
Comment thread test/AdaptiveRemote.App.Tests/Services/Lifecycle/ApplicationLifecycleTests.cs Outdated
Comment thread src/AdaptiveRemote.App/Services/Lifecycle/ApplicationLifecycle.cs Outdated
Comment thread src/AdaptiveRemote.App/Services/Lifecycle/ApplicationLifecycle.cs Outdated
Comment thread src/AdaptiveRemote.App/Components/BlazorAppScope.cs Outdated
Comment thread src/AdaptiveRemote.App/Components/BlazorAppScope.cs
Comment thread src/AdaptiveRemote.App/Services/Lifecycle/ApplicationRecycleSignal.cs Outdated
@jodavis jodavis changed the title Add sandbox setup script and update E2E test docs for Playwright browsers [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync Apr 21, 2026
claude added 2 commits April 20, 2026 20:29
…eAsync

Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync:
- IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service
  interface backed by a CancellationTokenSource; RequestRecycle() cancels it,
  Token is linked into the scope work item, Reset() creates a fresh CTS
- ExecuteAsync refactored to a while loop; linked token from stoppingToken +
  signal.Token passed into InvokeInScopeAsync each iteration
- Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup
  → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop
- Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup
  → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid)
- IPreScopeInitializer services awaited only before the first scope (before
  the while loop), not re-awaited on recycles
- BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload")
- Registered IApplicationRecycleSignal as singleton in DI
- Added EventId 712 ApplicationLifecycle_RecyclingScope log message
- Added 5 unit tests covering both recycle paths, loop continuation,
  pre-initializer not re-awaited, and signal.Reset() called after recycle
- Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the
  implemented recycle loop, signal, and two recycle paths

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s
…sers

Claude Code cloud sandbox environments block cdn.playwright.dev, so the
standard `playwright.ps1 install` fails. Browsers are pre-installed at
/opt/pw-browsers but under a different revision (1194) than the current
Playwright package expects (1208).

- scripts/setup-playwright-sandbox.sh: auto-detects the expected version
  from the Playwright registry JS, finds the highest installed version in
  /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers
- CLAUDE.md: updated E2E test instructions to document both the developer
  machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s
@jodavis jodavis force-pushed the feature/ADR-162-client-side-layout-updates branch from 49676e4 to b7b6252 Compare April 21, 2026 03:36
@jodavis jodavis force-pushed the claude/implement-adr-179-1qaNg branch from 143c6db to eccef82 Compare April 21, 2026 03:36
claude and others added 3 commits April 21, 2026 14:29
… add tests

- ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based
  control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions)
- Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure
- Cleanup now runs inside the loop before ShuttingDown, rather than after the loop
- BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using
- ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable
- IApplicationRecycleSignal: add XML doc comments to all interface methods
- HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets
- CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment
- _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design
- ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests
  (second signal during cleanup, blocks until cleanup, delay during init after recycle,
   error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op)

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s
@jodavis jodavis merged commit 8102476 into feature/ADR-162-client-side-layout-updates Apr 22, 2026
2 checks passed
@jodavis jodavis deleted the claude/implement-adr-179-1qaNg branch April 22, 2026 19:42
jodavis added a commit that referenced this pull request Apr 23, 2026
…leAsync (#153)

* [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync

Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync:
- IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service
  interface backed by a CancellationTokenSource; RequestRecycle() cancels it,
  Token is linked into the scope work item, Reset() creates a fresh CTS
- ExecuteAsync refactored to a while loop; linked token from stoppingToken +
  signal.Token passed into InvokeInScopeAsync each iteration
- Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup
  → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop
- Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup
  → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid)
- IPreScopeInitializer services awaited only before the first scope (before
  the while loop), not re-awaited on recycles
- BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload")
- Registered IApplicationRecycleSignal as singleton in DI
- Added EventId 712 ApplicationLifecycle_RecyclingScope log message
- Added 5 unit tests covering both recycle paths, loop continuation,
  pre-initializer not re-awaited, and signal.Reset() called after recycle
- Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the
  implemented recycle loop, signal, and two recycle paths

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Add sandbox setup script and update E2E test docs for Playwright browsers

Claude Code cloud sandbox environments block cdn.playwright.dev, so the
standard `playwright.ps1 install` fails. Browsers are pre-installed at
/opt/pw-browsers but under a different revision (1194) than the current
Playwright package expects (1208).

- scripts/setup-playwright-sandbox.sh: auto-detects the expected version
  from the Playwright registry JS, finds the highest installed version in
  /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers
- CLAUDE.md: updated E2E test instructions to document both the developer
  machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Address PR review comments: refactor recycle loop, fix thread safety, add tests

- ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based
  control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions)
- Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure
- Cleanup now runs inside the loop before ShuttingDown, rather than after the loop
- BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using
- ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable
- IApplicationRecycleSignal: add XML doc comments to all interface methods
- HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets
- CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment
- _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design
- ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests
  (second signal during cleanup, blocks until cleanup, delay during init after recycle,
   error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op)

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Improve ApplicationLifecycle unit tests, behavior, and logging.

* Delete the playwright workaround script since it shouldn't be necessary

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
jodavis added a commit that referenced this pull request Apr 23, 2026
…leAsync (#153)

* [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync

Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync:
- IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service
  interface backed by a CancellationTokenSource; RequestRecycle() cancels it,
  Token is linked into the scope work item, Reset() creates a fresh CTS
- ExecuteAsync refactored to a while loop; linked token from stoppingToken +
  signal.Token passed into InvokeInScopeAsync each iteration
- Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup
  → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop
- Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup
  → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid)
- IPreScopeInitializer services awaited only before the first scope (before
  the while loop), not re-awaited on recycles
- BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload")
- Registered IApplicationRecycleSignal as singleton in DI
- Added EventId 712 ApplicationLifecycle_RecyclingScope log message
- Added 5 unit tests covering both recycle paths, loop continuation,
  pre-initializer not re-awaited, and signal.Reset() called after recycle
- Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the
  implemented recycle loop, signal, and two recycle paths

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Add sandbox setup script and update E2E test docs for Playwright browsers

Claude Code cloud sandbox environments block cdn.playwright.dev, so the
standard `playwright.ps1 install` fails. Browsers are pre-installed at
/opt/pw-browsers but under a different revision (1194) than the current
Playwright package expects (1208).

- scripts/setup-playwright-sandbox.sh: auto-detects the expected version
  from the Playwright registry JS, finds the highest installed version in
  /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers
- CLAUDE.md: updated E2E test instructions to document both the developer
  machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Address PR review comments: refactor recycle loop, fix thread safety, add tests

- ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based
  control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions)
- Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure
- Cleanup now runs inside the loop before ShuttingDown, rather than after the loop
- BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using
- ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable
- IApplicationRecycleSignal: add XML doc comments to all interface methods
- HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets
- CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment
- _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design
- ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests
  (second signal during cleanup, blocks until cleanup, delay during init after recycle,
   error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op)

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Improve ApplicationLifecycle unit tests, behavior, and logging.

* Delete the playwright workaround script since it shouldn't be necessary

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
jodavis added a commit that referenced this pull request Apr 29, 2026
…leAsync (#153)

* [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync

Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync:
- IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service
  interface backed by a CancellationTokenSource; RequestRecycle() cancels it,
  Token is linked into the scope work item, Reset() creates a fresh CTS
- ExecuteAsync refactored to a while loop; linked token from stoppingToken +
  signal.Token passed into InvokeInScopeAsync each iteration
- Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup
  → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop
- Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup
  → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid)
- IPreScopeInitializer services awaited only before the first scope (before
  the while loop), not re-awaited on recycles
- BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload")
- Registered IApplicationRecycleSignal as singleton in DI
- Added EventId 712 ApplicationLifecycle_RecyclingScope log message
- Added 5 unit tests covering both recycle paths, loop continuation,
  pre-initializer not re-awaited, and signal.Reset() called after recycle
- Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the
  implemented recycle loop, signal, and two recycle paths

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Add sandbox setup script and update E2E test docs for Playwright browsers

Claude Code cloud sandbox environments block cdn.playwright.dev, so the
standard `playwright.ps1 install` fails. Browsers are pre-installed at
/opt/pw-browsers but under a different revision (1194) than the current
Playwright package expects (1208).

- scripts/setup-playwright-sandbox.sh: auto-detects the expected version
  from the Playwright registry JS, finds the highest installed version in
  /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers
- CLAUDE.md: updated E2E test instructions to document both the developer
  machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Address PR review comments: refactor recycle loop, fix thread safety, add tests

- ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based
  control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions)
- Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure
- Cleanup now runs inside the loop before ShuttingDown, rather than after the loop
- BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using
- ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable
- IApplicationRecycleSignal: add XML doc comments to all interface methods
- HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets
- CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment
- _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design
- ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests
  (second signal during cleanup, blocks until cleanup, delay during init after recycle,
   error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op)

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Improve ApplicationLifecycle unit tests, behavior, and logging.

* Delete the playwright workaround script since it shouldn't be necessary

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
jodavis added a commit that referenced this pull request Apr 29, 2026
…leAsync (#153)

* [ADR-179] ApplicationLifecycle recycle loop and BlazorAppScope.RecycleAsync

Implements the scope recycle loop in ApplicationLifecycle.ExecuteAsync:
- IApplicationRecycleSignal / ApplicationRecycleSignal: new cross-service
  interface backed by a CancellationTokenSource; RequestRecycle() cancels it,
  Token is linked into the scope work item, Reset() creates a fresh CTS
- ExecuteAsync refactored to a while loop; linked token from stoppingToken +
  signal.Token passed into InvokeInScopeAsync each iteration
- Steady-state path: signal fires during Task.Delay(Infinite) → OCE → cleanup
  → log RecyclingScope → RecycleScopeAsync() → signal.Reset() → loop
- Init-phase path: signal fires during InitializeAllAsync → cancel → cleanup
  → signal.Reset() → loop without RecycleScopeAsync (scope TCS still valid)
- IPreScopeInitializer services awaited only before the first scope (before
  the while loop), not re-awaited on recycles
- BlazorAppScope.RecycleAsync: now calls IJSRuntime.InvokeVoidAsync("location.reload")
- Registered IApplicationRecycleSignal as singleton in DI
- Added EventId 712 ApplicationLifecycle_RecyclingScope log message
- Added 5 unit tests covering both recycle paths, loop continuation,
  pre-initializer not re-awaited, and signal.Reset() called after recycle
- Updated _doc_Lifecycle.md: removed "Future Plans" stub, documented the
  implemented recycle loop, signal, and two recycle paths

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Add sandbox setup script and update E2E test docs for Playwright browsers

Claude Code cloud sandbox environments block cdn.playwright.dev, so the
standard `playwright.ps1 install` fails. Browsers are pre-installed at
/opt/pw-browsers but under a different revision (1194) than the current
Playwright package expects (1208).

- scripts/setup-playwright-sandbox.sh: auto-detects the expected version
  from the Playwright registry JS, finds the highest installed version in
  /opt/pw-browsers, and creates symlinks + INSTALLATION_COMPLETE markers
- CLAUDE.md: updated E2E test instructions to document both the developer
  machine path and the sandbox workaround, with PLAYWRIGHT_BROWSERS_PATH

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Address PR review comments: refactor recycle loop, fix thread safety, add tests

- ApplicationLifecycle.ExecuteAsync: replace Task.Delay(Timeout.Infinite) / exception-based
  control flow with WaitForCancelledAsync (returns normally, no OCE for steady-state transitions)
- Add ScopeReady log message (EventId 713); ScopeReleased now only logged on init/construction failure
- Cleanup now runs inside the loop before ShuttingDown, rather than after the loop
- BlazorAppScope.RecycleAsync: use GetRequiredService<IJSRuntime>(); remove unused using
- ApplicationRecycleSignal: full lock-based thread safety; implement IDisposable
- IApplicationRecycleSignal: add XML doc comments to all interface methods
- HostBuilderExtensions: restore missing using AdaptiveRemote.Services.CloudAssets
- CLAUDE.md: update cloud sandbox E2E guidance — don't fall back to setup script; report broken environment
- _doc_Lifecycle.md: update recycle loop description to match current WaitForCancelledAsync design
- ApplicationLifecycleTests: fix 3 tests for new log ordering; add 6 recycle scenario tests
  (second signal during cleanup, blocks until cleanup, delay during init after recycle,
   error during cleanup continues recycle, error during init exits loop, signal during pre-init is no-op)

https://claude.ai/code/session_01VENkux7qyWvUsKWEzgNC3s

* Improve ApplicationLifecycle unit tests, behavior, and logging.

* Delete the playwright workaround script since it shouldn't be necessary

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joe Davis <ElwoodMoves@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants