Skip to content

fix: avoid JS interop errors during board teardown - #182

Merged
dkorecko merged 2 commits into
mainfrom
fix/js-disconnected
Jul 1, 2026
Merged

dkorecko merged 2 commits into
mainfrom
fix/js-disconnected

Conversation

@dkorecko

@dkorecko dkorecko commented Jul 1, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes
    • Improved board page stability by stopping UI updates and real-time handling after the page is closed.
    • Prevented browser-disconnect errors from surfacing during preference loading/saving.
    • Cleaned up real-time hub subscriptions more reliably when leaving the board, reducing lingering background activity.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

BoardView now tracks disposal state, stops post-disposal UI and hub work, handles protected storage disconnects separately, and disposes its SignalR connection asynchronously with debug logging on failure.

Changes

BoardView lifecycle and disconnect handling

Layer / File(s) Summary
Disposal contract and state
Ticky.Web/Components/Pages/BoardView.razor
BoardView implements IAsyncDisposable and adds _isDisposed tracking.
Protected storage disconnect handling
Ticky.Web/Components/Pages/BoardView.razor
Board and filter preference loads reset local models when JSDisconnectedException occurs.
Hub and card-loading guards
Ticky.Web/Components/Pages/BoardView.razor
Hub startup returns early after disposal, and card loading skips StateHasChanged when disposed.
Preference saves and async disposal
Ticky.Web/Components/Pages/BoardView.razor
Preference saves ignore disconnected browser storage, and DisposeAsync() disposes the hub connection with debug logging on failure.

Sequence Diagram(s)

sequenceDiagram
  participant BlazorRuntime
  participant BoardView
  participant ProtectedLocalStorage
  participant HubConnection

  BlazorRuntime->>BoardView: DisposeAsync()
  BoardView->>BoardView: set _isDisposed = true
  BoardView->>HubConnection: DisposeAsync()
  HubConnection-->>BoardView: success or failure

  BoardView->>ProtectedLocalStorage: load/save preferences
  alt JSDisconnectedException
    BoardView->>BoardView: reset model or ignore save
  end

  BoardView->>BoardView: skip StateHasChanged when disposed
Loading

Estimated code review effort: High

Related issues: None specified

Related PRs: None specified

Suggested labels: blazor, lifecycle, bug

Suggested reviewers: None specified

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preventing JS interop errors during BoardView teardown.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/js-disconnected

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Ticky.Web/Components/Pages/BoardView.razor`:
- Around line 545-548: LoadBoardAsync still invokes a final StateHasChanged
after LoadCardsAsync may have already exited because _isDisposed is true. Add
the same _isDisposed guard in LoadBoardAsync immediately after awaiting
LoadCardsAsync, before the final InvokeAsync(StateHasChanged), so the component
does not attempt a render after disposal. Use the existing _isDisposed check
pattern already present in LoadCardsAsync and keep the render call guarded in
BoardView.razor.
- Around line 554-556: Update the initial preference-loading logic in
BoardView’s protected local storage reads so they handle JS disconnects the same
way as the save paths. In the code around the GetAsync calls in the BoardView
component, add JSDisconnectedException handling alongside the existing
CryptographicException catch blocks, using the same try/catch pattern already
used for the SetAsync paths, and keep the relevant load methods in BoardView
consistent.
- Around line 563-579: Suppress hub startup failures after disposal in
BoardView’s connection flow: when `HandleConnectionAsync()` is running
fire-and-forget, disposal can race with `StartAsync()` or `SendAsync()` and
incorrectly trigger the “Real-time updates unavailable” notification. Update the
exception handling around the hub startup/send path to check `_isDisposed` and
return early from the `catch` when the component has already been disposed,
while keeping normal error handling for non-disposal failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 97d3a7a8-0c5b-480b-b63b-faf00cdae92c

📥 Commits

Reviewing files that changed from the base of the PR and between 971fd24 and 2e6f46f.

📒 Files selected for processing (1)
  • Ticky.Web/Components/Pages/BoardView.razor

Comment thread Ticky.Web/Components/Pages/BoardView.razor
Comment thread Ticky.Web/Components/Pages/BoardView.razor
Comment thread Ticky.Web/Components/Pages/BoardView.razor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Ticky.Web/Components/Pages/BoardView.razor (1)

511-521: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop caller-side work after disposal, not only renders.

The new guard exits LoadBoardAsync, but HandleUpdate() still continues to OnDataChanged(), and OnAfterRenderAsync() still continues after HandleUpdate(). If disposal starts while loading is awaiting, teardown can still send hub messages or update layout/visit state after the component is disposed.

🐛 Proposed fix
 protected override async Task HandleUpdate()
 {
+    if (_isDisposed)
+        return;
+
     await LoadBoardAsync();
+
+    if (_isDisposed)
+        return;
+
     await OnDataChanged();
 }
         await HandleUpdate();
+
+        if (_isDisposed)
+            return;
 
         if (_board is null)
             return;
 private async Task OnDataChanged()
 {
-    if(_hubConnection is null || _hubConnection.State != HubConnectionState.Connected)
+    if(_isDisposed || _hubConnection is null || _hubConnection.State != HubConnectionState.Connected)
         return;
 
     await _hubConnection.SendAsync(nameof(UpdateHub.BoardChange), Id, _pageId);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Ticky.Web/Components/Pages/BoardView.razor` around lines 511 - 521, The
disposal guard only stops rendering, but caller-side work can still continue in
HandleUpdate and OnAfterRenderAsync after the component is disposed. Add early
_isDisposed checks after awaited calls and before invoking OnDataChanged or any
follow-up work in HandleUpdate, and likewise after awaiting HandleUpdate in
OnAfterRenderAsync. Use the existing _isDisposed guard pattern in BoardView to
ensure teardown stops hub/layout/visit-state updates as well as renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Ticky.Web/Components/Pages/BoardView.razor`:
- Around line 511-521: The disposal guard only stops rendering, but caller-side
work can still continue in HandleUpdate and OnAfterRenderAsync after the
component is disposed. Add early _isDisposed checks after awaited calls and
before invoking OnDataChanged or any follow-up work in HandleUpdate, and
likewise after awaiting HandleUpdate in OnAfterRenderAsync. Use the existing
_isDisposed guard pattern in BoardView to ensure teardown stops
hub/layout/visit-state updates as well as renders.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 82bc1acb-07dc-4b53-aee8-6174accf399d

📥 Commits

Reviewing files that changed from the base of the PR and between 2e6f46f and 00d8edc.

📒 Files selected for processing (1)
  • Ticky.Web/Components/Pages/BoardView.razor

@dkorecko
dkorecko merged commit 35724d1 into main Jul 1, 2026
6 checks passed
@dkorecko
dkorecko deleted the fix/js-disconnected branch July 1, 2026 11:26
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.

2 participants