Skip to content

fix: improve Yjs reconnect recovery after file rename#255

Open
YASHcode-IIITV wants to merge 6 commits into
piyushdotcomm:mainfrom
YASHcode-IIITV:fix/yjs-reconnect-recovery
Open

fix: improve Yjs reconnect recovery after file rename#255
YASHcode-IIITV wants to merge 6 commits into
piyushdotcomm:mainfrom
YASHcode-IIITV:fix/yjs-reconnect-recovery

Conversation

@YASHcode-IIITV
Copy link
Copy Markdown
Contributor

@YASHcode-IIITV YASHcode-IIITV commented May 23, 2026

Summary

  • improved Yjs reconnect recovery handling after imported-file renames
  • added preview refresh triggering after successful Yjs reconnection
  • improved resilience of collaborative sync behavior during file rename operations

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Test or CI improvement
  • Starter template change

Related issue

Closes #237

Validation

  • npm run lint
  • npm test
  • npm run build

List any additional manual verification you performed:

  • reproduced the preview freeze issue after imported-file rename
  • verified Yjs websocket reconnection behavior
  • verified preview refresh triggering after reconnect
  • verified Fast Refresh rebuilds continue successfully after reconnect

Screenshots or recordings

Screenshot 2026-05-23 202207 Screenshot 2026-05-23 205652
  • Yjs reconnect behavior
  • Fast Refresh rebuild completion
  • preview recovery investigation after imported-file rename

Checklist

  • I kept this PR focused on one primary change
  • I updated documentation if behavior changed
  • I did not commit secrets, local logs, or scratch files
  • I am requesting review on the correct scope

Summary by CodeRabbit

  • Bug Fixes

    • Live preview now automatically refreshes after collaboration reconnects.
    • Preview remounts and updates reliably when the development server becomes ready.
    • Additional logging improves visibility into reconnect and server-ready events.
  • Chores

    • Minor import cleanup to tidy component modules.

Review Change Stack

@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions
Copy link
Copy Markdown

👋 Thanks for opening a PR, @YASHcode-IIITV!

Your PR has entered the 🚦 PR Review Pipeline.

Standard PR detected — your PR will follow the standard review pipeline.


What happens next

Stage Reviewer Checks
Stage 1 — Automated Validation 🤖 Bot DCO · Format · AI/Slop · Duplicate
Stage 2 — Human Review 👥 Maintainer Code + Quality Review
Stage 3 — PA / Maintainer Review 🔑 Project Admin Final Merge Decision

A pipeline status comment will appear below and update automatically as your PR progresses.


While you wait

  • Sign all commits (git commit -s)
  • Link your issue (Closes #123)
  • Use a feature branch (not main)
  • Avoid unrelated changes

This comment is posted only once.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Yjs provider dispatches a yjs-reconnected CustomEvent on successful connect; the webcontainer preview logs server-ready events, increments refreshKey to remount the iframe, and registers a window listener that increments refreshKey on a Yjs connection event. A duplicate import was removed.

Changes

Yjs Reconnection Preview Refresh

Layer / File(s) Summary
Yjs reconnection event emission
lib/yjs.ts
The status event handler in the Yjs provider now extracts the connection status and dispatches a yjs-reconnected CustomEvent on window when the status becomes "connected".
Preview remount on server-ready and Yjs event
modules/webcontainers/components/webcontainer-preview.tsx
The preview component logs server-ready events and increments refreshKey in that handler. A mount-only useEffect registers a window listener for the Yjs connection event and increments refreshKey (cleanup on unmount). bindServerReady was reformatted/typed and a // @ts-expect-error`` before transformToWebContainerFormat was removed.
Import cleanup
modules/dashboard/components/template-selecting-modal.tsx
Removed a duplicated getTemplateSummaries import so the module imports it only once; no behavioral changes.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested labels

mentor:piyushdotcomm, type:bug, type:refactor, level:advanced

Suggested reviewers

  • piyushdotcomm

Poem

🐰 I nibble keys and watch the thread,
When Yjs hops back from being dead,
A little bell across the pane,
The preview wakes and loads again,
Happy hops — the editor's fed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: improved Yjs reconnect recovery after file rename, which directly relates to the core changes in lib/yjs.ts and webcontainer-preview.tsx.
Description check ✅ Passed The description covers the summary of changes, type of change (Bug fix and Refactor), related issue (#237), validation performed, screenshots, and checklist items. Most sections are filled out appropriately.
Linked Issues check ✅ Passed The PR addresses issue #237 by implementing Yjs reconnect event emission in lib/yjs.ts and triggering preview refresh in webcontainer-preview.tsx when reconnection occurs, meeting the core requirements.
Out of Scope Changes check ✅ Passed The cleanup of an unused import in template-selecting-modal.tsx is minor and related to code quality; all changes focus on fixing the reconnect/preview sync issue described in #237.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
modules/webcontainers/components/webcontainer-preview.tsx (2)

373-373: 💤 Low value

Remove or reduce verbosity of debug logs.

The all-caps console.log statements appear to be temporary debug logging. Consider removing them or converting to a proper logging utility before merging.

🧹 Optional cleanup
-              console.log("SERVER READY EVENT", port, url);
+              // Server ready event handled
-      console.log("FORCING PREVIEW REFRESH AFTER YJS RECONNECT");
-
       setRefreshKey((k) => k + 1);

Also applies to: 784-784

🤖 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 `@modules/webcontainers/components/webcontainer-preview.tsx` at line 373, The
console.log call printing "SERVER READY EVENT" with variables port and url is a
noisy debug statement; remove it or replace it with the project's logging
utility at an appropriate level (e.g., debug/info) inside the handler where the
server-ready event is emitted (the spot referencing port and url in
webcontainer-preview component). Ensure any retained log includes structured
context (port and url) and uses the centralized logger API rather than
console.log to control verbosity.

378-378: Preview refreshes on both server-ready and yjs-reconnected.

Note that refreshKey is now incremented in two scenarios:

  1. Line 378: When WebContainer server becomes ready
  2. Line 786: When Yjs reconnects (dispatched from lib/yjs.ts)

This means if both events fire in quick succession (e.g., during reconnection after a file rename), the iframe will remount twice. This is likely intentional to handle different reconnection scenarios, but be aware of potential brief flickering if events overlap.

🤖 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 `@modules/webcontainers/components/webcontainer-preview.tsx` at line 378, The
refreshKey is incremented in two separate handlers (the server-ready handler
calling setRefreshKey and the Yjs reconnection dispatch from lib/yjs.ts),
causing duplicate iframe remounts when both fire quickly; fix it by
centralizing/coalescing the increments: create a single helper (e.g.,
incrementRefreshKeyDebounced) in the webcontainer-preview component that
debounces or coalesces multiple calls within a short window (or checks a recent
timestamp flag) and use that helper instead of calling setRefreshKey directly
from the server-ready handler and the Yjs reconnection listener; replace direct
calls to setRefreshKey with calls to incrementRefreshKeyDebounced so overlapping
events only trigger one remount.
lib/yjs.ts (2)

69-69: 💤 Low value

Consider removing debug console.logs or use proper logging.

The console.log statements are helpful during development but should either be removed for production or replaced with a proper logging framework that supports log levels.

Also applies to: 73-73

🤖 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 `@lib/yjs.ts` at line 69, Replace the debug console.log calls in lib/yjs.ts
(e.g., the statement printing `[Yjs] Room ${roomId} status:` using roomId and
status, and the similar call at the nearby line) with the project's logging
utility or remove them for production; specifically, locate the console.log(...)
usages that reference roomId and status and either call logger.debug/info with
the same message and structured context (logger.debug("Yjs room status", {
roomId, status })) or delete the lines entirely so no console output remains in
production code. Ensure you import/use the existing logger instance used
elsewhere in the codebase rather than creating a new console-based log.

76-78: 💤 Low value

Add defensive window check for SSR safety.

While the provider is typically instantiated client-side, adding a typeof window !== "undefined" check before dispatching the event would make this code more resilient to future refactoring or SSR scenarios.

🛡️ Optional defensive check
         // Attempt recovery after reconnect
         if (status === "connected") {
             console.log("[Yjs] Reconnected successfully");

             // Force preview refresh after reconnect
-            window.dispatchEvent(
-                new CustomEvent("yjs-reconnected")
-            );
+            if (typeof window !== "undefined") {
+                window.dispatchEvent(
+                    new CustomEvent("yjs-reconnected")
+                );
+            }
         }
🤖 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 `@lib/yjs.ts` around lines 76 - 78, The dispatch of the "yjs-reconnected" event
should be guarded for SSR safety: wrap the window.dispatchEvent(new
CustomEvent("yjs-reconnected")) call in a runtime check such as typeof window
!== "undefined" (and optionally typeof window.dispatchEvent === "function") so
the event is only dispatched when window exists; update the code around the
CustomEvent creation/dispatch (the new CustomEvent("yjs-reconnected") call site)
to perform this defensive check before invoking dispatchEvent.
🤖 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 `@lib/yjs.ts`:
- Around line 71-79: The dispatched CustomEvent name "yjs-reconnected" is
misleading because the code in the (status === "connected") block fires on
initial connect as well (see the status variable and window.dispatchEvent(new
CustomEvent("yjs-reconnected"))), so fix by either renaming the event to
"yjs-connected" (and update any listeners) or add a persisted connection flag
(e.g., module/closure-level hasConnectedOnce) and only dispatch
"yjs-reconnected" when hasConnectedOnce is true, otherwise set the flag (and
optionally dispatch "yjs-connected" on first connect); update the corresponding
event listeners to match the chosen name/behavior.

---

Nitpick comments:
In `@lib/yjs.ts`:
- Line 69: Replace the debug console.log calls in lib/yjs.ts (e.g., the
statement printing `[Yjs] Room ${roomId} status:` using roomId and status, and
the similar call at the nearby line) with the project's logging utility or
remove them for production; specifically, locate the console.log(...) usages
that reference roomId and status and either call logger.debug/info with the same
message and structured context (logger.debug("Yjs room status", { roomId, status
})) or delete the lines entirely so no console output remains in production
code. Ensure you import/use the existing logger instance used elsewhere in the
codebase rather than creating a new console-based log.
- Around line 76-78: The dispatch of the "yjs-reconnected" event should be
guarded for SSR safety: wrap the window.dispatchEvent(new
CustomEvent("yjs-reconnected")) call in a runtime check such as typeof window
!== "undefined" (and optionally typeof window.dispatchEvent === "function") so
the event is only dispatched when window exists; update the code around the
CustomEvent creation/dispatch (the new CustomEvent("yjs-reconnected") call site)
to perform this defensive check before invoking dispatchEvent.

In `@modules/webcontainers/components/webcontainer-preview.tsx`:
- Line 373: The console.log call printing "SERVER READY EVENT" with variables
port and url is a noisy debug statement; remove it or replace it with the
project's logging utility at an appropriate level (e.g., debug/info) inside the
handler where the server-ready event is emitted (the spot referencing port and
url in webcontainer-preview component). Ensure any retained log includes
structured context (port and url) and uses the centralized logger API rather
than console.log to control verbosity.
- Line 378: The refreshKey is incremented in two separate handlers (the
server-ready handler calling setRefreshKey and the Yjs reconnection dispatch
from lib/yjs.ts), causing duplicate iframe remounts when both fire quickly; fix
it by centralizing/coalescing the increments: create a single helper (e.g.,
incrementRefreshKeyDebounced) in the webcontainer-preview component that
debounces or coalesces multiple calls within a short window (or checks a recent
timestamp flag) and use that helper instead of calling setRefreshKey directly
from the server-ready handler and the Yjs reconnection listener; replace direct
calls to setRefreshKey with calls to incrementRefreshKeyDebounced so overlapping
events only trigger one remount.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c6675905-2e70-4af0-ba07-ded72713618d

📥 Commits

Reviewing files that changed from the base of the PR and between 19a3136 and f0de59d.

📒 Files selected for processing (2)
  • lib/yjs.ts
  • modules/webcontainers/components/webcontainer-preview.tsx

Comment thread lib/yjs.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@lib/yjs.ts`:
- Line 77: The emitted event name was changed to "yjs-connected"
(CustomEvent("yjs-connected")) but downstream still listens for
"yjs-reconnected" (see webcontainer-preview.tsx listener), breaking reconnect
behavior; fix by restoring the original event name or emit both for
compatibility: dispatch CustomEvent("yjs-reconnected") (or dispatch both
"yjs-reconnected" and "yjs-connected") and update any listeners accordingly so
emitter/listener names stay in sync.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7824490f-fb84-454d-af31-f2186880eb75

📥 Commits

Reviewing files that changed from the base of the PR and between e851050 and 1187009.

📒 Files selected for processing (1)
  • lib/yjs.ts

Comment thread lib/yjs.ts Outdated
@YASHcode-IIITV
Copy link
Copy Markdown
Contributor Author

@piyushdotcomm plz review and if possible could you give me the critical tag in this one .

@github-actions github-actions Bot added the bug Something isn't working label May 23, 2026
Comment thread lib/yjs.ts
@YASHcode-IIITV
Copy link
Copy Markdown
Contributor Author

now no issues i think it is ready for merge

Comment thread modules/webcontainers/components/webcontainer-preview.tsx
@YASHcode-IIITV
Copy link
Copy Markdown
Contributor Author

every thing is done now , ready to merge

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
modules/webcontainers/components/webcontainer-preview.tsx (1)

113-123: ⚡ Quick win

Remove the unnecessary (inst as any) cast in bindServerReady: @webcontainer/api’s TypeScript types for WebContainer.on('server-ready', ...) expect listener: (port: number, url: string) => void and return an Unsubscribe function (() => void), which matches serverReadyCleanupRef—so inst.on("server-ready", handler) should type-check and keep compile-time event/listener checking in modules/webcontainers/components/webcontainer-preview.tsx (lines 113-123).

🤖 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 `@modules/webcontainers/components/webcontainer-preview.tsx` around lines 113 -
123, Remove the unnecessary cast by calling inst.on directly: in
bindServerReady, replace (inst as any).on("server-ready", handler) with
inst.on("server-ready", handler) and assign its return to
serverReadyCleanupRef.current (since WebContainer.on returns an Unsubscribe ()
=> void), ensuring serverReadyCleanupRef and the handler signature (port:
number, url: string) remain unchanged.
🤖 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.

Nitpick comments:
In `@modules/webcontainers/components/webcontainer-preview.tsx`:
- Around line 113-123: Remove the unnecessary cast by calling inst.on directly:
in bindServerReady, replace (inst as any).on("server-ready", handler) with
inst.on("server-ready", handler) and assign its return to
serverReadyCleanupRef.current (since WebContainer.on returns an Unsubscribe ()
=> void), ensuring serverReadyCleanupRef and the handler signature (port:
number, url: string) remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 888d5e7c-2ce5-49dd-9d0d-aa09eb55df03

📥 Commits

Reviewing files that changed from the base of the PR and between 663f55c and f77022d.

📒 Files selected for processing (1)
  • modules/webcontainers/components/webcontainer-preview.tsx

@YASHcode-IIITV
Copy link
Copy Markdown
Contributor Author

🧹 Nitpick comments (1)

modules/webcontainers/components/webcontainer-preview.tsx (1)> 113-123: ⚡ Quick win

Remove the unnecessary (inst as any) cast in bindServerReady: @webcontainer/api’s TypeScript types for WebContainer.on('server-ready', ...) expect listener: (port: number, url: string) => void and return an Unsubscribe function (() => void), which matches serverReadyCleanupRef—so inst.on("server-ready", handler) should type-check and keep compile-time event/listener checking in modules/webcontainers/components/webcontainer-preview.tsx (lines 113-123).

🤖 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 `@modules/webcontainers/components/webcontainer-preview.tsx` around lines 113 -
123, Remove the unnecessary cast by calling inst.on directly: in
bindServerReady, replace (inst as any).on("server-ready", handler) with
inst.on("server-ready", handler) and assign its return to
serverReadyCleanupRef.current (since WebContainer.on returns an Unsubscribe ()
=> void), ensuring serverReadyCleanupRef and the handler signature (port:
number, url: string) remain unchanged.

🤖 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.

Nitpick comments:
In `@modules/webcontainers/components/webcontainer-preview.tsx`:
- Around line 113-123: Remove the unnecessary cast by calling inst.on directly:
in bindServerReady, replace (inst as any).on("server-ready", handler) with
inst.on("server-ready", handler) and assign its return to
serverReadyCleanupRef.current (since WebContainer.on returns an Unsubscribe ()
=> void), ensuring serverReadyCleanupRef and the handler signature (port:
number, url: string) remain unchanged.

ℹ️ Review info

this is the outdated comment this is already fixed ,
plz review once again if anything is missing .

@YASHcode-IIITV
Copy link
Copy Markdown
Contributor Author

@piyushdotcomm plz merge this pr bro

Copy link
Copy Markdown
Owner

@piyushdotcomm piyushdotcomm left a comment

Choose a reason for hiding this comment

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

Breaking compilation and risking HMR loops. Fixes required.

import { Label } from "@/components/ui/label";
import type { TemplateCategory } from "@/lib/templates/types";
import { getTemplateSummaries } from "@/lib/templates/actions";
import type { TemplateKey } from "@/lib/template";
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

You removed the import but the function is still called on line 68. This breaks compilation.

serverReadyCleanupRef.current?.();
serverReadyCleanupRef.current = inst.on("server-ready", handler);

serverReadyCleanupRef.current = (inst as any).on(
Copy link
Copy Markdown
Owner

@piyushdotcomm piyushdotcomm May 26, 2026

Choose a reason for hiding this comment

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

Drop the \as any\ cast. Use @ts-expect-error\ if the types from @webcontainer/api\ are truly mismatched.


const isCommonFrontendPort = [
3000, 5173, 8080, 4200, 8000,
].includes(port);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Unconditional iframe remounts on \server-ready\ will destroy HMR. Only increment
efreshKey\ if the URL actually changed.

Comment thread lib/yjs.ts

// Force preview refresh after reconnect
window.dispatchEvent(
new CustomEvent("yjs-reconnected")
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Bypassing React state with global \window\ events is brittle. Pass a callback or use Zustand for connection state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug][Design][Performance] Playground preview desynchronizes after file rename and breaks hot-reload pipeline

2 participants