Skip to content

fix(copilot): persist thinking blocks on page refresh#3194

Open
waleedlatif1 wants to merge 1 commit intostagingfrom
feat/streamdown
Open

fix(copilot): persist thinking blocks on page refresh#3194
waleedlatif1 wants to merge 1 commit intostagingfrom
feat/streamdown

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@waleedlatif1 waleedlatif1 commented Feb 11, 2026

Summary

  • Use navigator.sendBeacon in beforeunload to persist in-progress messages (including thinking blocks) during page refresh/close
  • Flush batched streaming updates before beacon persistence so contentBlocks are up to date
  • abortMessage uses beacon instead of fire-and-forget fetch when page is unloading
  • Fix double-digit ordered list clipping in thinking block (pl-6pl-8)

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Feb 11, 2026 6:46am

Request Review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 11, 2026

Greptile Summary

This PR fixes the loss of thinking blocks and in-progress copilot messages during page refresh/close. It uses navigator.sendBeacon — which the browser guarantees to dispatch even during page teardown — to persist the current conversation state. A secondary cosmetic fix corrects ordered-list padding in the thinking block UI.

  • persist.ts: Extracts a shared buildPersistBody helper and adds persistMessagesBeacon, which wraps navigator.sendBeacon with a Blob payload. Using a Blob with type: 'application/json' is the correct way to set Content-Type since sendBeacon does not accept custom request headers
  • store.ts: Registers a beforeunload listener at module load time that sets _isPageUnloading = true, flushes any RAF-batched streaming updates via flushStreamingUpdates, then immediately calls persistMessagesBeacon. The abortMessage action is extended to switch from async fetch to sendBeacon when unloading, and to preserve activeStream in session storage (so the stream can be resumed after the refresh)
  • thinking-block.tsx: Changes [&_ol]:!pl-6[&_ol]:!pl-8 in both the streaming (SmoothThinkingText) and static post-stream content divs so that double-digit ordered-list markers are no longer clipped

Confidence Score: 5/5

Safe to merge — no blocking issues found; all changes are focused bug fixes

The sendBeacon approach is the correct browser-native pattern for page-unload persistence. The beforeunload handler is synchronous, wrapped in a no-throw try/catch, and only fires when there is an active streaming message. flushStreamingUpdates correctly drains the RAF queue before persisting, ensuring thinking blocks are captured. The abortMessage path correctly suppresses the continue option and preserves the active stream on unload. The pl-6 to pl-8 change is a simple, safe cosmetic fix. No P0 or P1 issues were identified.

No files require special attention

Important Files Changed

Filename Overview
apps/sim/stores/panel/copilot/store.ts Adds beforeunload handler that flushes batched streaming updates and persists in-progress messages via sendBeacon; extends abortMessage to use beacon and preserve activeStream during page unload
apps/sim/lib/copilot/messages/persist.ts Adds persistMessagesBeacon using navigator.sendBeacon with a JSON Blob; extracts shared buildPersistBody helper used by both the fetch and beacon paths
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/thinking-block/thinking-block.tsx Fixes double-digit ordered list clipping in thinking block by changing pl-6 to pl-8 in both streaming and static content views

Sequence Diagram

sequenceDiagram
    participant Browser
    participant CopilotStore
    participant Server as Server (update-messages)

    Note over Browser,CopilotStore: User refreshes or closes tab while streaming

    Browser->>CopilotStore: beforeunload fires
    CopilotStore->>CopilotStore: _isPageUnloading = true
    alt isSendingMessage && currentChat
        CopilotStore->>CopilotStore: flushStreamingUpdates(setState)
        Note over CopilotStore: Drains RAF-batched contentBlocks into store
        CopilotStore->>Server: navigator.sendBeacon(blob)
        Note over Server: Browser queues beacon — delivered after teardown
    end

    Note over Browser,CopilotStore: abortMessage() called (e.g. via cleanup on unmount)

    CopilotStore->>CopilotStore: isPageUnloading() === true
    CopilotStore->>CopilotStore: suppressContinueOption = true
    CopilotStore->>CopilotStore: flushStreamingUpdates(set) — no-op if already flushed
    CopilotStore->>CopilotStore: Preserve activeStream in sessionStorage
    Note over CopilotStore: Stream can be resumed after page reload
    CopilotStore->>Server: navigator.sendBeacon(blob)
    Note over Server: Persists thinking blocks + partial content
Loading

Reviews (2): Last reviewed commit: "fix(copilot): persist thinking blocks on..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

<Streamdown
components={{
h2: ({ children, ...props }) =>
h2: ({ children }: any) =>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Using any type violates TypeScript conventions - consider using proper types from Streamdown's component prop types

Suggested change
h2: ({ children }: any) =>
h2: ({ children }: { children?: React.ReactNode }) =>

Context Used: Context from dashboard - TypeScript conventions and type safety (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

- Use navigator.sendBeacon in beforeunload handler to reliably persist
  in-progress messages (including thinking blocks) during page teardown
- Flush batched streaming updates before beacon persistence
- Fall back to sendBeacon in abortMessage when page is unloading
- Fix double-digit ordered list clipping in thinking block (pl-6 → pl-8)
@waleedlatif1 waleedlatif1 changed the title feat(markdown): migrate from react-markdown to streamdown fix(copilot): persist thinking blocks on page refresh Feb 11, 2026
@waleedlatif1 waleedlatif1 deleted the branch staging April 3, 2026 23:01
@waleedlatif1 waleedlatif1 reopened this Apr 7, 2026
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.

1 participant