Skip to content

[WIKI-844] fix: realtime sync post vite migration with title editor sync and indexed db access#8294

Merged
sriramveeraghanta merged 14 commits intopreviewfrom
fix/realtime-sync-refactor
Dec 10, 2025
Merged

[WIKI-844] fix: realtime sync post vite migration with title editor sync and indexed db access#8294
sriramveeraghanta merged 14 commits intopreviewfrom
fix/realtime-sync-refactor

Conversation

@Palanikannan1437
Copy link
Member

@Palanikannan1437 Palanikannan1437 commented Dec 10, 2025

Description

This PR fixes real-time document sync issues introduced after the Vite migration. It adds a dedicated title editor with debounced updates, ensures reliable syncing between title and body, and restores IndexedDB persistence for offline/rehydration scenarios. Collaboration behaviors have been improved with stable connection state handling, highlight/scroll-to-node support, and safer fallback fetch logic.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

N/A

Test Scenarios

  • Verified title-body sync with fast edits, slow edits, and disconnect/reconnect flows.
  • Tested IndexedDB persistence on reload, offline mode, and format conversion paths.
  • Confirmed collaborative editing: cursor highlights, node scrolling, connection fallback.
  • Tested auto-save debounce and sync status badge across multiple browser tabs.
  • Validated no regressions in editor formatting or initial load performance.

References

Related: realtime sync regressions after Vite migration, collaborative editing stability reports.

Summary by CodeRabbit

  • New Features

    • Real-time title synchronization with automatic, debounced saves
    • Dedicated title editor with keyboard navigation between title and content
    • Collaborative editing plumbing and Yjs-based collaboration setup
    • Sync status badge and content limit warning banner in the UI
  • Bug Fixes

    • Improved document format conversion and automatic persistence for fetched content
  • Style/Assets

    • New title editor styles and editor UI refinements

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Warning

Rate limit exceeded

@Palanikannan1437 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 21 minutes and 40 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 92aa3f7 and 253297f.

📒 Files selected for processing (1)
  • packages/editor/src/core/components/editors/editor-container.tsx (3 hunks)

Walkthrough

Adds server-side title synchronization with debounced, abortable persistence; converts and persists missing document binary formats on fetch; introduces Yjs/Hocuspocus collaboration plumbing and a dedicated title editor/navigation; refactors editor components to use a CollaborationProvider; and adds UI sync status and content-limit handling.

Changes

Cohort / File(s) Summary
Server: Title sync + debounce
apps/live/src/extensions/title-sync.ts, apps/live/src/extensions/title-update/debounce.ts, apps/live/src/extensions/title-update/title-update-manager.ts, apps/live/src/extensions/title-update/title-utils.ts
New TitleSyncExtension, DebounceManager, TitleUpdateManager and extractTextFromHTML to observe title fragment changes, extract plain text, debounce async updates with AbortSignal support, schedule/force-save/cancel updates, and migrate legacy title data on load.
Server: Document binary handling
apps/live/src/extensions/database.ts
fetchDocument now converts HTML→binary when derived binary is empty and persists converted fields back to the database; storeDocument calls conversion with an added updateTitle flag.
Client: Page store syncing state
apps/web/core/store/pages/base-page.ts
Adds observable `isSyncingWithServer: "syncing"
Client: Page editor surface & root
apps/web/core/components/pages/editor/editor-body.tsx, apps/web/core/components/pages/editor/page-root.tsx
Rewires realtime handling to onStateChange, passes collaborationState and merged customRealtimeEventHandlers, forwards isFetchingFallbackBinary, and integrates content-limit error handling and ContentLimitBanner UI.
Client: UI components
apps/web/core/components/pages/header/syncing-badge.tsx, apps/web/core/components/pages/editor/content-limit-banner.tsx, apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx
New PageSyncingBadge (syncing/synced/error with tooltip) and ContentLimitBanner; header now renders PageSyncingBadge.
Client: Fallback hook
apps/web/core/hooks/use-page-fallback.ts
Replaces boolean flag with CollaborationState, adds isFetchingFallbackBinary and one-time notification logic, and makes fallback fetch behavior reconnection-aware.
Editor: collaboration context & Yjs setup
packages/editor/src/core/contexts/collaboration-context.tsx, packages/editor/src/core/contexts/index.ts, packages/editor/src/core/hooks/use-yjs-setup.ts
New CollaborationProvider/useCollaboration and useYjsSetup: HocuspocusProvider lifecycle, IndexedDB caching, collab stages/state/actions and onStateChange callbacks.
Editor: title editor & navigation
packages/editor/src/core/hooks/use-title-editor.ts, packages/editor/src/core/hooks/use-editor-navigation.ts, packages/editor/src/core/extensions/title-extension.ts, packages/editor/src/styles/title-editor.css, packages/editor/src/styles/index.css
New useTitleEditor hook, keyboard navigation extensions between title and main editor, TipTap title extensions, and title-editor styles.
Editor: renderer & container refactor
packages/editor/src/core/components/editors/document/collaborative-editor.tsx, packages/editor/src/core/components/editors/document/page-renderer.tsx, packages/editor/src/core/components/editors/editor-container.tsx, packages/editor/src/core/components/editors/editor-content.tsx
Split collaborative editor into provider wrapper and inner component, add provider/state/titleEditor plumbing, scroll-to-node highlight behavior, and className support on editor content.
Editor: hooks/types/helpers
packages/editor/src/core/hooks/use-collaborative-editor.ts, packages/editor/src/core/helpers/yjs-utils.ts, packages/editor/src/core/types/collaboration.ts, packages/editor/src/core/types/editor.ts, packages/editor/src/core/types/hook.ts, packages/editor/src/core/hooks/*
Refactors useCollaborativeEditor to accept external provider and return titleEditor; adds CollaborationState/CollabStage/CollaborationError types and TServerHandler.onStateChange; exposes TITLE_EDITOR_EXTENSIONS and extractTextFromHTML; updates editor-related public types/props.
Editor: plugins
packages/editor/src/core/plugins/highlight.ts
New NodeHighlightPlugin and key to highlight nodes by id via decorations.
Dependencies
packages/editor/package.json
Adds TipTap extensions: @tiptap/extension-document, @tiptap/extension-heading, @tiptap/extension-text.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant TitleEd as Title Editor
    participant TitleSync as TitleSyncExtension
    participant Debounce as DebounceManager
    participant TitleMgr as TitleUpdateManager
    participant PageSvc as PageService
    participant DB as Database

    User->>TitleEd: Edit title
    TitleEd->>TitleSync: Title change event (Yjs)
    TitleSync->>Debounce: schedule(updateTitle, newTitle)
    Note over Debounce: wait (e.g., 5s) / trailing-edge
    Debounce->>TitleMgr: invoke updateTitle(newTitle)
    TitleMgr->>PageSvc: updatePageProperties(id, { name: newTitle }, abortSignal)
    PageSvc->>DB: persist title
    DB-->>PageSvc: ack / error
    PageSvc-->>TitleMgr: result
    alt success
        TitleMgr-->>Debounce: clear lastTitle
        TitleSync->>TitleSync: broadcast realtime title event
    else error
        TitleMgr->>Debounce: schedule retry
    end
Loading
sequenceDiagram
    participant Client as Editor UI
    participant CollabProv as CollaborationProvider
    participant YjsProv as HocuspocusProvider
    participant Store as PageStore
    participant UI as SyncBadge/Banner

    Client->>CollabProv: initialize with docId
    CollabProv->>YjsProv: connect (auth, URL)
    YjsProv-->>CollabProv: onStateChange(collabState)
    CollabProv-->>Client: forward state
    Client->>Store: setSyncingStatus(mappedStatus)
    Store-->>UI: isSyncingWithServer updates
    UI->>UI: render badge / show banner on errors
    alt disconnected
        Client->>Client: trigger usePageFallback (fetch fallback binary)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas needing extra attention:

  • Title sync/debounce/abort semantics and DB persistence paths (apps/live/src/extensions/*).
  • useYjsSetup: provider lifecycle, retry logic, IndexedDB persistence and event cleanup (packages/editor/src/core/hooks/use-yjs-setup.ts).
  • Major refactors changing provider propagation and hook/component signatures (use-collaborative-editor.ts, collaborative-editor.tsx, page-renderer.tsx).
  • Public type and handler signature changes (collaboration types, editor hooks/types) that impact downstream consumers.

Possibly related PRs

Suggested labels

🌐frontend, ✍️editor, 🛠️refactor

Poem

🐇 I nibble titles, soft and keen,

debounce keeps my hops between,
Yjs whispers, providers hum,
badges blink when syncs are done,
tiny hops make pages clean.

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing real-time sync issues post-Vite migration with title editor sync and IndexedDB access. It is specific, relevant, and concise.
Description check ✅ Passed The description covers all required template sections: detailed description of changes, multiple type of change categories checked, test scenarios provided, and references to related issues. All major sections are completed and informative.

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 and usage tips.

Copy link
Contributor

@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: 15

Caution

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

⚠️ Outside diff range comments (2)
packages/editor/src/core/components/editors/document/page-renderer.tsx (1)

29-39: Fix duplicate property declarations in Props type.

The Props type has duplicate declarations that cause TypeScript compilation errors:

  • flaggedExtensions is declared on both lines 29 and 35
  • disabledExtensions is declared on both lines 21 and 36

Remove the duplicate declarations on lines 34-38.

Apply this diff to fix the duplicate declarations:

   tabIndex?: number;
-  extendedEditorProps?: IEditorPropsExtended;
-  flaggedExtensions: IEditorProps["flaggedExtensions"];
-  disabledExtensions: IEditorProps["disabledExtensions"];
   provider?: HocuspocusProvider;
   state?: TCollabValue["state"];
 };
packages/editor/src/core/components/editors/document/collaborative-editor.tsx (1)

57-83: Missing updatePageProperties prop in useCollaborativeEditor call.

The updatePageProperties prop is destructured from props at line 49 but is not passed to useCollaborativeEditor. According to the hook's type definition in use-collaborative-editor.ts, this prop is expected for title editor functionality.

Add the missing prop:

     onTransaction,
     placeholder,
     tabIndex,
     titleRef,
     user,
     actions,
+    updatePageProperties,
   });
🧹 Nitpick comments (17)
apps/live/src/extensions/title-update/title-utils.ts (1)

1-8: Duplicate implementation exists in packages/editor/src/core/helpers/yjs-utils.ts.

This function is identical to extractTextFromHTML at lines 207-211 in yjs-utils.ts. Consider importing from a shared location or consolidating into a single utility module to avoid drift between implementations.

-/**
- * Utility function to extract text from HTML content
- */
-export const extractTextFromHTML = (html: string): string => {
-  // Use a regex to extract text between tags
-  const textMatch = html.replace(/<[^>]*>/g, "");
-  return textMatch || "";
-};
+// Re-export from shared location or import from @plane/editor/helpers
+export { extractTextFromHTML } from "@plane/editor/core/helpers/yjs-utils";

Alternatively, if cross-package import isn't feasible, extract to a shared @plane/utils package.

packages/editor/src/styles/title-editor.css (1)

35-49: Consider consolidating duplicate placeholder styles.

The two placeholder selectors (.is-editor-empty:first-child::before and .is-empty::before) have identical styling. If both are needed for different TipTap states, consider extracting the common styles to reduce duplication.

-.page-title-editor .ProseMirror h1.is-editor-empty:first-child::before {
-  content: attr(data-placeholder);
-  float: left;
-  color: var(--color-placeholder);
-  pointer-events: none;
-  height: 0;
-}
-
-.page-title-editor .ProseMirror h1.is-empty::before {
+.page-title-editor .ProseMirror h1.is-editor-empty:first-child::before,
+.page-title-editor .ProseMirror h1.is-empty::before {
   content: attr(data-placeholder);
   float: left;
   color: var(--color-placeholder);
   pointer-events: none;
   height: 0;
 }
apps/web/core/hooks/use-page-fallback.ts (1)

25-26: Consider optimizing the dependency chain.

The current implementation has hasConnectionFailed (derived from collaborationState) in the handleUpdateDescription dependency array (line 73), which causes the callback to be recreated on every collaboration state change. This then triggers the useEffect (lines 75-82), creating a circular dependency pattern.

While the logic is functionally safe due to the early return check (line 29), consider refactoring to improve efficiency:

-  const handleUpdateDescription = useCallback(async () => {
-    if (!hasConnectionFailed) return;
+  const handleUpdateDescription = useCallback(async (connectionFailed: boolean) => {
+    if (!connectionFailed) return;
     const editor = editorRef.current;
     if (!editor) return;
 
     // ... rest of the function
-  }, [editorRef, fetchPageDescription, hasConnectionFailed, updatePageDescription]);
+  }, [editorRef, fetchPageDescription, updatePageDescription]);
 
   useEffect(() => {
     if (hasConnectionFailed) {
-      handleUpdateDescription();
+      handleUpdateDescription(hasConnectionFailed);
     } else {
       // Reset toast flag when connection is restored
       hasShownFallbackToast.current = false;
     }
   }, [handleUpdateDescription, hasConnectionFailed]);

This way, handleUpdateDescription is only recreated when its actual dependencies change, not on every collaboration state change.

Also applies to: 73-82

packages/editor/src/core/types/collaboration.ts (1)

29-33: Consider adding isDocReady to the public state type.

Based on the useYjsSetup hook context provided, isDocReady is computed and exposed in the returned state object. If consumers rely on this property (as mentioned in the CollaborationProvider comments), consider adding it to CollaborationState for type safety.

 export type CollaborationState = {
   stage: CollabStage;
   isServerSynced: boolean;
   isServerDisconnected: boolean;
+  isDocReady: boolean;
 };
packages/editor/src/core/components/editors/editor-container.tsx (2)

75-103: Effect cleanup may not run for all code paths.

The cleanup function only executes if the if (nodeId && !hasScrolledOnce.current) condition is true. If the condition is false, no cleanup runs, but this appears intentional since no listeners are attached in that case. However, consider restructuring to make the cleanup logic clearer.

Consider restructuring for clarity:

   useEffect(() => {
     const nodeId = window.location.href.split("#")[1];
+    if (!nodeId || hasScrolledOnce.current) return;

     const handleSynced = () => scrollToNode(nodeId);
+    let listenerAttached = false;

-    if (nodeId && !hasScrolledOnce.current) {
       if (provider && state) {
         const { hasCachedContent } = state;
         if (hasCachedContent) {
           const hasScrolled = handleSynced();
           if (!hasScrolled) {
             provider.on("synced", handleSynced);
+            listenerAttached = true;
           }
         } else if (provider.isSynced) {
           handleSynced();
         } else {
           provider.on("synced", handleSynced);
+          listenerAttached = true;
         }
       } else {
         handleSynced();
       }
-      return () => {
-        if (provider) {
-          provider.off("synced", handleSynced);
-        }
-      };
-    }
+
+    return () => {
+      if (listenerAttached && provider) {
+        provider.off("synced", handleSynced);
+      }
+    };
   }, [scrollToNode, provider, state]);

76-76: Consider using URL API for hash extraction.

Using split("#")[1] works but doesn't handle edge cases like encoded characters. Consider using the URL API for more robust parsing.

-    const nodeId = window.location.href.split("#")[1];
+    const nodeId = window.location.hash.slice(1); // removes the leading '#'

Or for full URL parsing:

const nodeId = new URL(window.location.href).hash.slice(1);
packages/editor/src/core/helpers/yjs-utils.ts (1)

208-212: Consider handling HTML entities and consolidating duplicate code.

  1. The regex-based approach doesn't decode HTML entities like &amp;, &lt;, &gt;, &nbsp;. If titles contain these, the output will include raw entity strings.

  2. This function duplicates extractTextFromHTML in apps/live/src/extensions/title-update/title-utils.ts. Consider exporting from a shared location to avoid drift.

packages/editor/src/core/hooks/use-editor-navigation.ts (1)

114-118: Race condition risk with setTimeout for focus after deletion.

Using setTimeout(..., 0) to ensure node deletion completes before focusing is fragile. The deletion is synchronous in ProseMirror, so the setTimeout may not be necessary, or a more robust approach would be to use requestAnimationFrame or chain the focus command.

Consider chaining the operations:

               if (firstNode.content.size === 0) {
-                editor.commands.deleteNode("paragraph");
-                // Use setTimeout to ensure the node is deleted before changing focus
-                setTimeout(() => titleEditor.commands.focus("end"), 0);
+                editor.commands.deleteNode("paragraph");
+                titleEditor.commands.focus("end");
                 return true;
               }
apps/live/src/extensions/title-update/title-update-manager.ts (2)

34-40: Minor: Avoid creating bound function on each call.

this.updateTitle.bind(this) creates a new function instance on every scheduleUpdate call. Consider binding once in the constructor or using an arrow function property.

+  private boundUpdateTitle = this.updateTitle.bind(this);
+
   scheduleUpdate(title: string): void {
     this.lastTitle = title;
-    this.debounceManager.schedule(this.updateTitle.bind(this), title);
+    this.debounceManager.schedule(this.boundUpdateTitle, title);
   }

73-81: Same bound function optimization applies here.

forceSave also creates a new bound function. If implementing the constructor binding suggested earlier, update this call as well.

   async forceSave(): Promise<void> {
     if (!this.lastTitle) {
       return;
     }
-    await this.debounceManager.flush(this.updateTitle.bind(this));
+    await this.debounceManager.flush(this.boundUpdateTitle);
   }
apps/live/src/extensions/title-sync.ts (1)

102-106: Consider simplifying the title extraction logic.

The loop overwrites title on each iteration, effectively only using the last event's title. If this is intentional, consider using the last event directly for clarity:

 private handleTitleChange(documentName: string, events: Y.YEvent<any>[]) {
-    let title = "";
-    events.forEach((event) => {
-      title = extractTextFromHTML(event.currentTarget.toJSON());
-    });
+    const lastEvent = events.at(-1);
+    const title = lastEvent ? extractTextFromHTML(lastEvent.currentTarget.toJSON()) : "";
packages/editor/src/core/hooks/use-yjs-setup.ts (2)

18-30: Unused options parameter in hook arguments.

The options parameter with maxConnectionAttempts is defined in UseYjsSetupArgs but never destructured or used. The code uses DEFAULT_MAX_RETRIES constant instead.

Either remove the unused option or wire it up:

-export const useYjsSetup = ({ docId, serverUrl, authToken, onStateChange }: UseYjsSetupArgs) => {
+export const useYjsSetup = ({ docId, serverUrl, authToken, onStateChange, options }: UseYjsSetupArgs) => {
+  const maxRetries = options?.maxConnectionAttempts ?? DEFAULT_MAX_RETRIES;

Then replace DEFAULT_MAX_RETRIES usages with maxRetries.


234-243: Missing stage transition in handleOnline handler.

Unlike handleVisibilityChange which sets stage to connecting before reconnecting, handleOnline doesn't update the stage. This could leave the UI showing a stale disconnected state during reconnection.

 const handleOnline = () => {
   if (isDisposedRef.current) return;

   const wsProvider = provider.configuration.websocketProvider;
   if (wsProvider) {
+    // Reset retry count and update stage for fresh reconnection
+    retryCountRef.current = 0;
+    const newStage = { kind: "connecting" as const };
+    stageRef.current = newStage;
+    setStage(newStage);
+
     wsProvider.shouldConnect = true;
     wsProvider.disconnect();
     wsProvider.connect();
   }
 };
apps/web/core/components/pages/editor/page-root.tsx (1)

165-165: Consider adding dismiss capability to the content limit banner.

The ContentLimitBanner component supports an onDismiss prop for dismissibility, but it's not provided here. Once the banner appears, it persists until the page is reloaded. Consider whether users should be able to dismiss it:

-        {showContentTooLargeBanner && <ContentLimitBanner className="px-page-x" />}
+        {showContentTooLargeBanner && (
+          <ContentLimitBanner
+            className="px-page-x"
+            onDismiss={() => setShowContentTooLargeBanner(false)}
+          />
+        )}
apps/live/src/extensions/title-update/debounce.ts (1)

193-206: Arbitrary delay after abort is fragile.

The 20ms delay to "ensure the abort has had time to propagate" is a race condition waiting to happen. Abort signals are synchronous—handlers receive them immediately. The double-check afterwards suggests uncertainty about the state machine.

Consider restructuring to avoid the delay:

 private async abortOngoingOperation(): Promise<void> {
   if (this.state.inProgress && this.state.abortController) {
     this.state.abortController.abort();
-
-    // Small delay to ensure the abort has had time to propagate
-    await new Promise((resolve) => setTimeout(resolve, 20));
-
-    // Double-check that state has been reset, force it if not
-    if (this.state.inProgress || this.state.abortController) {
-      this.state.inProgress = false;
-      this.state.abortController = null;
-    }
+    // State will be reset in performFunction's finally block
   }
 }

The finally block in performFunction already resets inProgress and abortController, so this double-reset is unnecessary if you trust the execution flow.

apps/web/core/components/pages/editor/editor-body.tsx (1)

147-155: Consider using ref pattern for onCollaborationStateChange callback.

Including onCollaborationStateChange in the dependency array could cause unnecessary effect runs if the parent recreates the callback. The use-yjs-setup.ts hook uses a ref pattern for this exact reason.

+  // Use ref to avoid effect re-runs when callback changes
+  const onCollaborationStateChangeRef = useRef(onCollaborationStateChange);
+  onCollaborationStateChangeRef.current = onCollaborationStateChange;
+
   // Set syncing status when page changes and reset collaboration state
   useEffect(() => {
     setSyncingStatus("syncing");
-    onCollaborationStateChange?.({
+    onCollaborationStateChangeRef.current?.({
       stage: { kind: "connecting" },
       isServerSynced: false,
       isServerDisconnected: false,
     });
-  }, [pageId, setSyncingStatus, onCollaborationStateChange]);
+  }, [pageId, setSyncingStatus]);
packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

170-191: Avoid type assertion; align types directly.

The type assertion as Parameters<typeof useTitleEditor>[0] at line 191 indicates a type mismatch between the inline type definition (lines 170-178) and what useTitleEditor expects. This bypasses TypeScript's type checking and can hide errors if the hook's signature changes.

Consider importing or reusing the parameter type from useTitleEditor directly:

+import type { TitleEditorConfig } from "./use-title-editor"; // or define shared type
+
-  const titleEditorConfig = useMemo<{
-    id: string;
-    editable: boolean;
-    provider: HocuspocusProvider;
-    titleRef?: React.MutableRefObject<EditorTitleRefApi | null>;
-    updatePageProperties?: ICollaborativeDocumentEditorProps["updatePageProperties"];
-    extensions: Extensions;
-    extendedEditorProps?: IEditorPropsExtended;
-  }>(
+  const titleEditorConfig = useMemo<TitleEditorConfig>(
     () => ({
       ...
     }),
     [...]
   );

-  const titleEditor = useTitleEditor(titleEditorConfig as Parameters<typeof useTitleEditor>[0]);
+  const titleEditor = useTitleEditor(titleEditorConfig);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff544c9 and 625124d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (31)
  • apps/live/src/extensions/database.ts (2 hunks)
  • apps/live/src/extensions/title-sync.ts (1 hunks)
  • apps/live/src/extensions/title-update/debounce.ts (1 hunks)
  • apps/live/src/extensions/title-update/title-update-manager.ts (1 hunks)
  • apps/live/src/extensions/title-update/title-utils.ts (1 hunks)
  • apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx (2 hunks)
  • apps/web/core/components/pages/editor/content-limit-banner.tsx (1 hunks)
  • apps/web/core/components/pages/editor/editor-body.tsx (9 hunks)
  • apps/web/core/components/pages/editor/page-root.tsx (6 hunks)
  • apps/web/core/components/pages/header/syncing-badge.tsx (1 hunks)
  • apps/web/core/hooks/use-page-fallback.ts (3 hunks)
  • apps/web/core/store/pages/base-page.ts (5 hunks)
  • packages/editor/package.json (1 hunks)
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx (4 hunks)
  • packages/editor/src/core/components/editors/document/page-renderer.tsx (5 hunks)
  • packages/editor/src/core/components/editors/editor-container.tsx (2 hunks)
  • packages/editor/src/core/components/editors/editor-content.tsx (1 hunks)
  • packages/editor/src/core/contexts/collaboration-context.tsx (1 hunks)
  • packages/editor/src/core/contexts/index.ts (1 hunks)
  • packages/editor/src/core/extensions/title-extension.ts (1 hunks)
  • packages/editor/src/core/helpers/yjs-utils.ts (7 hunks)
  • packages/editor/src/core/hooks/use-collaborative-editor.ts (3 hunks)
  • packages/editor/src/core/hooks/use-editor-navigation.ts (1 hunks)
  • packages/editor/src/core/hooks/use-title-editor.ts (1 hunks)
  • packages/editor/src/core/hooks/use-yjs-setup.ts (1 hunks)
  • packages/editor/src/core/plugins/highlight.ts (1 hunks)
  • packages/editor/src/core/types/collaboration.ts (1 hunks)
  • packages/editor/src/core/types/editor.ts (4 hunks)
  • packages/editor/src/core/types/hook.ts (1 hunks)
  • packages/editor/src/styles/index.css (1 hunks)
  • packages/editor/src/styles/title-editor.css (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • packages/editor/src/core/contexts/index.ts
  • packages/editor/src/core/types/hook.ts
  • apps/live/src/extensions/title-update/title-utils.ts
  • packages/editor/src/core/plugins/highlight.ts
  • packages/editor/src/core/contexts/collaboration-context.tsx
  • apps/live/src/extensions/title-update/title-update-manager.ts
  • apps/web/core/components/pages/header/syncing-badge.tsx
  • apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx
  • apps/web/core/hooks/use-page-fallback.ts
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx
  • packages/editor/src/core/hooks/use-editor-navigation.ts
  • apps/web/core/store/pages/base-page.ts
  • packages/editor/src/core/hooks/use-title-editor.ts
  • apps/live/src/extensions/title-update/debounce.ts
  • packages/editor/src/core/extensions/title-extension.ts
  • packages/editor/src/core/components/editors/editor-content.tsx
  • packages/editor/src/core/helpers/yjs-utils.ts
  • apps/web/core/components/pages/editor/content-limit-banner.tsx
  • apps/live/src/extensions/title-sync.ts
  • packages/editor/src/core/hooks/use-collaborative-editor.ts
  • packages/editor/src/core/components/editors/editor-container.tsx
  • apps/live/src/extensions/database.ts
  • packages/editor/src/core/hooks/use-yjs-setup.ts
  • packages/editor/src/core/types/editor.ts
  • packages/editor/src/core/types/collaboration.ts
  • apps/web/core/components/pages/editor/page-root.tsx
  • apps/web/core/components/pages/editor/editor-body.tsx
  • packages/editor/src/core/components/editors/document/page-renderer.tsx
🧠 Learnings (7)
📚 Learning: 2025-10-09T20:42:31.843Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.843Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.

Applied to files:

  • apps/web/core/hooks/use-page-fallback.ts
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx
  • packages/editor/src/core/components/editors/editor-content.tsx
  • packages/editor/src/core/components/editors/editor-container.tsx
  • apps/web/core/components/pages/editor/page-root.tsx
  • apps/web/core/components/pages/editor/editor-body.tsx
  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-10-21T17:22:05.204Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7989
File: apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx:45-46
Timestamp: 2025-10-21T17:22:05.204Z
Learning: In the makeplane/plane repository, the refactor from useParams() to params prop is specifically scoped to page.tsx and layout.tsx files in apps/web/app (Next.js App Router pattern). Other components (hooks, regular client components, utilities) should continue using the useParams() hook as that is the correct pattern for non-route components.

Applied to files:

  • apps/web/core/hooks/use-page-fallback.ts
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx
  • apps/web/core/components/pages/editor/page-root.tsx
  • apps/web/core/components/pages/editor/editor-body.tsx
📚 Learning: 2025-10-01T15:30:17.605Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7888
File: packages/propel/src/avatar/avatar.stories.tsx:2-3
Timestamp: 2025-10-01T15:30:17.605Z
Learning: In the makeplane/plane repository, avoid suggesting inline type imports (e.g., `import { Avatar, type TAvatarSize }`) due to bundler compatibility issues. Keep type imports and value imports as separate statements.

Applied to files:

  • apps/web/core/hooks/use-page-fallback.ts
  • apps/web/core/components/pages/editor/editor-body.tsx
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Use `.ts`, `.mts`, `.cts` extensions in `import type` statements (TypeScript 5.2+)

Applied to files:

  • packages/editor/src/core/extensions/title-extension.ts
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Use `with { type: "json" }` for import attributes; avoid deprecated `assert` syntax (TypeScript 5.3/5.8+)

Applied to files:

  • packages/editor/src/core/helpers/yjs-utils.ts
📚 Learning: 2025-06-04T16:22:44.344Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7164
File: packages/ui/.storybook/main.ts:24-47
Timestamp: 2025-06-04T16:22:44.344Z
Learning: In packages/ui/.storybook/main.ts, the webpackFinal function intentionally overrides the CSS loader strategy by finding and replacing existing CSS rules. This is a temporary workaround for a known upstream issue in Storybook's CSS handling that has been communicated to the Storybook maintainers. The current implementation should not be changed until the upstream issue is resolved.

Applied to files:

  • packages/editor/src/styles/index.css
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{js,mjs,cjs} : Use `import` tags in JSDoc for cleaner type imports in JavaScript files when working in a mixed codebase (TypeScript 5.5+)

Applied to files:

  • packages/editor/src/styles/index.css
🧬 Code graph analysis (16)
packages/editor/src/core/types/hook.ts (1)
packages/editor/src/core/types/editor.ts (1)
  • ICollaborativeDocumentEditorProps (187-205)
apps/live/src/extensions/title-update/title-utils.ts (1)
packages/editor/src/core/helpers/yjs-utils.ts (1)
  • extractTextFromHTML (208-212)
packages/editor/src/core/contexts/collaboration-context.tsx (1)
packages/editor/src/core/hooks/use-yjs-setup.ts (1)
  • useYjsSetup (30-369)
apps/live/src/extensions/title-update/title-update-manager.ts (4)
apps/live/src/types/index.ts (1)
  • HocusPocusServerContext (23-29)
apps/live/src/extensions/title-update/debounce.ts (1)
  • DebounceManager (42-277)
apps/live/src/services/page/handler.ts (1)
  • getPageService (6-16)
apps/live/src/lib/errors.ts (1)
  • AppError (14-73)
apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx (1)
apps/web/core/components/pages/header/syncing-badge.tsx (1)
  • PageSyncingBadge (9-72)
apps/web/core/hooks/use-page-fallback.ts (3)
packages/editor/src/core/types/collaboration.ts (1)
  • CollaborationState (29-33)
packages/types/src/page/core.ts (1)
  • TDocumentPayload (66-70)
packages/propel/src/toast/toast.tsx (1)
  • setToast (202-222)
packages/editor/src/core/components/editors/document/collaborative-editor.tsx (4)
packages/editor/src/core/types/editor.ts (1)
  • ICollaborativeDocumentEditorProps (187-205)
packages/editor/src/core/contexts/collaboration-context.tsx (2)
  • useCollaboration (26-32)
  • CollaborationProvider (14-24)
packages/editor/src/core/hooks/use-collaborative-editor.ts (1)
  • useCollaborativeEditor (33-204)
packages/editor/src/core/components/editors/document/page-renderer.tsx (1)
  • PageRenderer (41-114)
packages/editor/src/core/hooks/use-title-editor.ts (5)
packages/editor/src/core/types/editor.ts (2)
  • EditorTitleRefApi (146-146)
  • ICollaborativeDocumentEditorProps (187-205)
packages/editor/src/core/hooks/use-editor.ts (1)
  • useEditor (19-157)
apps/live/src/services/page/core.service.ts (1)
  • updatePageProperties (55-104)
packages/editor/src/core/extensions/title-extension.ts (1)
  • TitleExtensions (6-14)
packages/editor/src/core/helpers/editor-ref.ts (1)
  • getEditorRefHelpers (24-282)
packages/editor/src/core/helpers/yjs-utils.ts (2)
packages/editor/src/core/extensions/title-extension.ts (1)
  • TitleExtensions (6-14)
apps/live/src/extensions/title-update/title-utils.ts (1)
  • extractTextFromHTML (4-8)
apps/live/src/extensions/title-sync.ts (5)
apps/live/src/extensions/title-update/title-update-manager.ts (1)
  • TitleUpdateManager (11-90)
apps/live/src/types/index.ts (2)
  • OnLoadDocumentPayloadWithContext (8-10)
  • HocusPocusServerContext (23-29)
apps/live/src/utils/document.ts (1)
  • generateTitleProsemirrorJson (1-21)
packages/editor/src/core/types/document-collaborative-events.ts (1)
  • createRealtimeEvent (43-60)
apps/live/src/utils/broadcast-message.ts (1)
  • broadcastMessageToPage (7-34)
packages/editor/src/core/components/editors/editor-container.tsx (2)
packages/editor/src/core/contexts/collaboration-context.tsx (1)
  • TCollabValue (5-5)
packages/editor/src/core/plugins/highlight.ts (1)
  • nodeHighlightPluginKey (13-13)
apps/live/src/extensions/database.ts (1)
packages/editor/src/core/helpers/yjs-utils.ts (1)
  • getAllDocumentFormatsFromDocumentEditorBinaryData (120-157)
packages/editor/src/core/types/editor.ts (2)
packages/editor/src/ce/types/editor-extended.ts (1)
  • TExtendedEditorRefApi (11-11)
packages/editor/src/core/types/document-collaborative-events.ts (1)
  • EventToPayloadMap (24-26)
apps/web/core/components/pages/editor/page-root.tsx (4)
apps/web/core/hooks/use-realtime-page-events.tsx (2)
  • TCustomEventHandlers (25-27)
  • PageUpdateHandler (18-22)
packages/editor/src/core/types/collaboration.ts (1)
  • CollaborationState (29-33)
packages/editor/src/core/types/editor.ts (1)
  • EditorRefApi (144-144)
apps/web/core/components/pages/editor/content-limit-banner.tsx (1)
  • ContentLimitBanner (9-35)
apps/web/core/components/pages/editor/editor-body.tsx (4)
apps/web/core/hooks/use-realtime-page-events.tsx (1)
  • TCustomEventHandlers (25-27)
apps/web/ce/hooks/pages/use-extended-editor-extensions.ts (1)
  • TExtendedEditorExtensionsConfig (16-16)
packages/editor/src/core/types/collaboration.ts (2)
  • CollaborationState (29-33)
  • TServerHandler (35-37)
packages/editor/src/core/types/editor.ts (1)
  • EditorTitleRefApi (146-146)
packages/editor/src/core/components/editors/document/page-renderer.tsx (7)
packages/editor/src/core/types/editor.ts (1)
  • IEditorProps (149-179)
packages/editor/src/core/contexts/collaboration-context.tsx (1)
  • TCollabValue (5-5)
packages/editor/src/core/components/editors/editor-container.tsx (1)
  • EditorContainer (29-179)
packages/editor/src/core/components/editors/editor-content.tsx (1)
  • EditorContentWrapper (13-26)
apps/api/plane/tests/unit/models/test_issue_comment_modal.py (1)
  • state (28-35)
packages/editor/src/core/components/menus/bubble-menu/root.tsx (1)
  • EditorBubbleMenu (70-229)
packages/editor/src/core/components/menus/block-menu.tsx (1)
  • BlockMenu (38-249)
🪛 Biome (2.1.2)
apps/live/src/extensions/title-sync.ts

[error] 45-45: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)

packages/editor/src/core/components/editors/document/page-renderer.tsx

[error] 56-56: Declarations inside of a const declaration may not have duplicates

a second declaration of flaggedExtensions is not allowed

flaggedExtensions is first declared here

(parse)


[error] 57-57: Declarations inside of a const declaration may not have duplicates

a second declaration of disabledExtensions is not allowed

disabledExtensions is first declared here

(parse)

🪛 GitHub Actions: Build and lint web apps
packages/editor/src/core/components/editors/document/page-renderer.tsx

[error] 21-21: Duplicate identifier 'disabledExtensions'. TS2300

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (44)
packages/editor/package.json (1)

47-47: TipTap extension additions are correctly versioned and well-integrated.

The three extensions (@tiptap/extension-document, @tiptap/extension-heading, @tiptap/extension-text) are consistently versioned at ^2.22.3, matching all other TipTap packages in the file. v2.22.3 is a stable patch release with full compatibility across the @tiptap ecosystem. Placement maintains alphabetical order and supports the PR's title synchronization feature.

packages/editor/src/core/components/editors/editor-content.tsx (1)

13-26: LGTM!

Clean refactor to add className prop support. The component signature change to FC<Props> and direct prop application to the wrapper div are appropriate for this extensibility enhancement.

packages/editor/src/core/plugins/highlight.ts (1)

53-92: Plugin implementation is well-structured.

The plugin correctly uses PluginKey for state access, handles decoration rebuilding on node ID changes, and exposes decorations via the props.decorations getter. The use of early return in buildDecorations and stopping traversal after finding the target node is efficient.

packages/editor/src/core/types/editor.ts (2)

102-146: LGTM!

The type refactoring cleanly separates core API (CoreEditorRefApi) from extended API, enabling composition via intersection type. The EditorTitleRefApi alias provides semantic clarity for title-editor contexts.


196-204: LGTM!

The new optional props for ICollaborativeDocumentEditorProps are well-typed. The generic constraint on updatePageProperties properly restricts actionType to valid event keys while inferring the correct payload type.

packages/editor/src/styles/index.css (1)

6-6: LGTM!

Import follows the established pattern for CSS modules in this file. The referenced title-editor.css file exists at the expected location.

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/header.tsx (1)

13-13: LGTM! Clean integration of syncing status badge.

The import and usage of PageSyncingBadge are correct. The component is properly placed in the header's right section and receives the syncing status from the page state.

Also applies to: 99-99

packages/editor/src/core/contexts/index.ts (1)

1-1: LGTM! Standard barrel export pattern.

This centralized export enables cleaner imports from the contexts module.

packages/editor/src/core/types/hook.ts (1)

60-63: LGTM! Clean type extension.

The addition of titleRef and updatePageProperties as optional properties correctly extends the collaborative editor hook props to support the new title editing and page property update workflows.

apps/web/core/components/pages/editor/content-limit-banner.tsx (1)

1-35: LGTM! Well-structured banner component.

The component is cleanly implemented with:

  • Proper semantic HTML structure
  • Accessibility label on the dismiss button
  • Optional dismiss functionality via callback prop
  • Appropriate styling with Tailwind utilities

Based on learnings, React.FC usage is correct as React types are globally available in this codebase.

apps/web/core/components/pages/header/syncing-badge.tsx (1)

9-72: LGTM! Solid implementation of sync status indicator.

The component correctly handles three sync states with smooth transitions:

  • Uses state and refs to track visibility and previous status
  • Implements a 300ms delay for exit animations
  • Early returns prevent rendering when not needed
  • Provides helpful tooltips for each status
  • Includes visual indicators (pulsing animation for syncing, icon for error)

The logic is well-structured and defensive.

apps/web/core/hooks/use-page-fallback.ts (1)

34-71: Excellent improvements to error handling and user feedback.

The additions significantly enhance the user experience:

  • Toast notifications inform users when fallback mechanism activates
  • Loading state (isFetchingFallbackBinary) enables UI feedback during fallback operations
  • Single-toast guarantee via hasShownFallbackToast ref prevents notification spam
  • Proper error handling with user-facing error messages
  • Toast reset on reconnection allows proper notification on subsequent disconnections
apps/web/core/store/pages/base-page.ts (1)

16-16: LGTM!

The isSyncingWithServer observable is properly typed as a union of string literals and correctly initialized. The observable.ref annotation is appropriate for a primitive value that doesn't need deep observation.

Also applies to: 78-78

packages/editor/src/core/contexts/collaboration-context.tsx (2)

14-24: LGTM!

The CollaborationProvider correctly gates content rendering until useYjsSetup returns a non-null value, allowing consumers to check state.isDocReady for finer-grained control. The pattern of showing a fallback while setup is initializing is appropriate.


26-32: LGTM!

The useCollaboration hook properly enforces context usage with a clear error message, and the return type is correctly inferred as non-null.

packages/editor/src/core/types/collaboration.ts (2)

1-5: LGTM!

Well-structured discriminated union for collaboration errors with clear type discrimination. The variant payloads appropriately capture the relevant context for each error type.


17-23: LGTM!

The CollabStage discriminated union with kind discriminator is well-designed. The JSDoc clearly documents the state machine invariants and stage transitions.

packages/editor/src/core/hooks/use-title-editor.ts (1)

56-58: Verify dependency array completeness.

The useEditor dependency array includes docKey which is derived from provider?.document?.guid. If provider or its document changes without changing the guid, the editor won't recreate. Ensure this is the intended behavior.

packages/editor/src/core/components/editors/editor-container.tsx (1)

34-73: LGTM!

The scrollToNode implementation correctly traverses the document to find a node by ID, applies a highlight via the plugin, scrolls it into view, and clears the highlight on focus. Using requestAnimationFrame ensures the DOM is ready before scrolling.

packages/editor/src/core/components/editors/document/page-renderer.tsx (1)

70-110: LGTM on the rendering structure.

The conditional title editor rendering and main editor flow are well-structured. The provider and state are properly propagated to EditorContainer, and menus are correctly gated behind editability and touch device checks.

packages/editor/src/core/components/editors/document/collaborative-editor.tsx (2)

133-149: Verify the authToken format.

The authToken is set to JSON.stringify(user), which serializes the entire user object. Ensure the Hocuspocus server expects this format. If only a specific token field is needed, consider passing user.token or similar instead.


151-155: LGTM on the forwardRef pattern.

Using key={props.id} ensures the editor remounts when the document ID changes, which is appropriate for collaborative editing scenarios.

packages/editor/src/core/hooks/use-editor-navigation.ts (2)

18-26: ArrowDown handler behavior doesn't match documentation.

The comment says "Arrow down at end of title - Move to main editor" but the implementation always moves to the main editor regardless of cursor position. This may be intentional for UX, but if the goal is to only navigate at the end, add a position check similar to ArrowRight.

If this is intentional behavior, update the comment:

-        // Arrow down at end of title - Move to main editor
+        // Arrow down anywhere in title - Move to main editor
         ArrowDown: () => {

44-52: Verify insertContentAt(0) behavior.

Position 0 in ProseMirror is before the document's root node. Inserting at position 0 may not produce the expected result. Typically, position 1 is the start of the first block content.

Consider using position 1:

-          mainEditor.chain().focus().insertContentAt(0, { type: "paragraph" }).run();
+          mainEditor.chain().focus().insertContentAt(1, { type: "paragraph" }).run();
apps/live/src/extensions/title-update/title-update-manager.ts (2)

11-29: LGTM on class structure and constructor.

The constructor properly initializes the debounce manager with a reasonable 5-second default wait time and includes a log prefix for debugging.


62-67: Verify silent error handling is intentional.

Errors during title update are logged but not propagated. This means failures are silent to callers. If title persistence is critical, consider exposing error state or implementing retry logic in the debounce manager.

apps/live/src/extensions/title-sync.ts (2)

138-146: LGTM!

The beforeUnloadDocument method correctly forces an immediate save and cleans up the manager before document unload.


151-175: LGTM!

The cleanup in afterUnloadDocument is thorough—it unregisters observers, cleans up observer data, and ensures the update manager is cancelled if beforeUnloadDocument didn't run.

packages/editor/src/core/hooks/use-yjs-setup.ts (3)

265-287: LGTM!

The IndexedDB persistence lifecycle is properly managed with cleanup on unmount.


321-338: LGTM!

Good use of the ref pattern to avoid including the callback in dependencies while keeping it up-to-date.


349-368: LGTM!

The null check before returning ensures consumers receive a valid session or null, making the API predictable.

apps/web/core/components/pages/editor/page-root.tsx (2)

104-120: LGTM!

The error handler correctly checks for the content_too_large error code and preserves the chain by calling the original error handler if it exists.


46-56: LGTM!

The component destructuring and props handling is clean. Good use of the observer HOC with an arrow function.

apps/live/src/extensions/title-update/debounce.ts (3)

270-276: arraysEqual uses reference equality.

This works for primitive values (strings, numbers) but will return false for objects even if they're structurally identical. Given this is used to compare title strings, it's fine for the current use case.


62-95: LGTM!

The schedule method correctly handles the three cases: operation in progress, timer already scheduled, and fresh schedule. Timer management is clean.


227-241: LGTM!

The flush method properly clears pending timeouts, resets timing state, and executes immediately if there are pending args.

apps/web/core/components/pages/editor/editor-body.tsx (3)

157-176: LGTM!

The serverHandler correctly maps collaboration stages to UI syncing status with clear logic: disconnected → error, synced → synced, all others → syncing.


220-222: LGTM!

Good loading guard that shows the loader when pageId is undefined or realtimeConfig couldn't be constructed.


52-69: customRealtimeEventHandlers prop may be unused in the component.

The customRealtimeEventHandlers prop is declared in the Props type but requires verification that it is not destructured or passed to child components elsewhere in the implementation.

packages/editor/src/core/hooks/use-collaborative-editor.ts (4)

33-62: LGTM on the restructured hook signature.

The refactor to accept an external provider and typed configuration improves separation of concerns. The destructuring is clean and comprehensive.


66-102: Extensions memoization looks correct.

The extension array is properly constructed with all necessary extensions including the Collaboration extension configured with the provider's document. The dependency array captures all relevant inputs.


193-198: Editor wiring effect is appropriate.

The effect correctly synchronizes the main and title editors with the navigation context when both are available. The conditional guard ensures both editors exist before setting.


28-31: The actions parameter is not unused—it's part of the public API contract.

The signalForcedClose callback is actively used in use-yjs-setup.ts (line 366) and is expected to be provided by callers of this hook. While the parameter isn't directly used within use-collaborative-editor.ts itself, this is an intentional design where the hook accepts it as part of its configuration. This is not dead code.

Likely an incorrect or invalid review comment.

apps/live/src/extensions/database.ts (1)

30-40: Race condition possible when multiple concurrent fetch requests trigger simultaneous writes.

The code converts HTML to binary formats and persists them during the fetch operation when binaryData.byteLength === 0. However, multiple concurrent fetch requests for the same document would each see the empty binary condition independently and trigger simultaneous updateDescriptionBinary calls without any client-side deduplication or locking.

While the conversion is deterministic (same input yields same output), this write-during-read pattern creates a potential race condition. Address this by either:

  1. Adding client-side deduplication (e.g., tracking in-flight conversions per pageId)
  2. Verifying the backend PATCH operation is idempotent and handles concurrent writes safely
  3. Moving the one-time conversion to an initialization/migration step outside the fetch path

aaryan610
aaryan610 previously approved these changes Dec 10, 2025
Copy link
Contributor

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/editor/src/core/components/editors/document/page-renderer.tsx (1)

18-36: Remove unused aiHandler prop.

The aiHandler prop is defined in the Props interface but is never destructured or used within the component. Based on the AI summary indicating replacement of aiHandler-based rendering logic, this appears to be leftover from the refactoring.

Apply this diff:

 type Props = {
-  aiHandler?: TAIHandler;
   bubbleMenuEnabled: boolean;
   disabledExtensions: IEditorProps["disabledExtensions"];

Also remove the TAIHandler import from line 14 if it's no longer used elsewhere:

   IEditorProps,
   IEditorPropsExtended,
-  TAIHandler,
   TDisplayConfig,
♻️ Duplicate comments (1)
packages/editor/src/core/hooks/use-title-editor.ts (1)

40-44: Stale closure: editor is undefined when onUpdate is first called.

The editor variable is referenced inside the onUpdate callback, but it captures the value before useEditor returns. Use the callback parameter instead:

 const editor = useEditor(
   {
-    onUpdate: () => {
-      updatePageProperties?.(id, "property_updated", { name: editor?.getText() });
+    onUpdate: ({ editor: currentEditor }) => {
+      updatePageProperties?.(id, "property_updated", { name: currentEditor.getText() });
     },
🧹 Nitpick comments (2)
packages/editor/src/core/hooks/use-collaborative-editor.ts (2)

105-156: Dependency array issues in editorConfig memoization.

Two issues:

  1. provider appears twice in the dependency array (lines 132 and 153)
  2. showPlaceholderOnEmpty is in the dependency array (line 152) but not included in the memoized config object
     () => ({
       disabledExtensions,
       extendedEditorProps,
       id,
       editable,
       editorProps,
       editorClassName,
       enableHistory: false,
       extensions: editorExtensions,
       fileHandler,
       flaggedExtensions,
       forwardedRef,
       getEditorMetaData,
       handleEditorReady,
       isTouchDevice,
       mentionHandler,
       onAssetChange,
       onChange,
       onEditorFocus,
       onTransaction,
       placeholder,
       provider,
+      showPlaceholderOnEmpty,
       tabIndex,
     }),
     [
       provider,
       disabledExtensions,
       extendedEditorProps,
       id,
       editable,
       editorProps,
       editorClassName,
       editorExtensions,
       fileHandler,
       flaggedExtensions,
       forwardedRef,
       getEditorMetaData,
       handleEditorReady,
       isTouchDevice,
       mentionHandler,
       onAssetChange,
       onChange,
       onEditorFocus,
       onTransaction,
       placeholder,
       showPlaceholderOnEmpty,
-      provider,
       tabIndex,
     ]

171-194: Type assertion suggests type mismatch between titleEditorConfig and useTitleEditor props.

The explicit type annotation on titleEditorConfig followed by as Parameters<typeof useTitleEditor>[0] indicates the types don't align. Consider aligning the inline type with the Props type from useTitleEditor or importing and reusing that type directly to avoid the cast.

+import type { Props as TitleEditorProps } from "./use-title-editor";
+// Or export the Props type from use-title-editor.ts

-  const titleEditorConfig = useMemo<{
-    id: string;
-    editable: boolean;
-    provider: HocuspocusProvider;
-    titleRef?: React.MutableRefObject<EditorTitleRefApi | null>;
-    updatePageProperties?: ICollaborativeDocumentEditorProps["updatePageProperties"];
-    extensions: Extensions;
-    extendedEditorProps?: IEditorPropsExtended;
-    getEditorMetaData?: IEditorProps["getEditorMetaData"];
-  }>(
+  const titleEditorConfig = useMemo<Parameters<typeof useTitleEditor>[0]>(
     () => ({
       ...
     }),
     [...]
   );

-  const titleEditor = useTitleEditor(titleEditorConfig as Parameters<typeof useTitleEditor>[0]);
+  const titleEditor = useTitleEditor(titleEditorConfig);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 625124d and c8fa092.

📒 Files selected for processing (3)
  • packages/editor/src/core/components/editors/document/page-renderer.tsx (5 hunks)
  • packages/editor/src/core/hooks/use-collaborative-editor.ts (3 hunks)
  • packages/editor/src/core/hooks/use-title-editor.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • packages/editor/src/core/hooks/use-collaborative-editor.ts
  • packages/editor/src/core/hooks/use-title-editor.ts
  • packages/editor/src/core/components/editors/document/page-renderer.tsx
🧠 Learnings (8)
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Ensure variables are initialized before use to avoid errors with never-initialized variables (TypeScript 5.7+)

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Treat legacy `experimentalDecorators`-style behavior as deprecated in favor of standard TC39-compliant decorators

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Avoid `enum`s if the project prefers erasable syntax, using `const` objects or unions instead (TypeScript 5.8+)

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-10-01T15:30:17.605Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7888
File: packages/propel/src/avatar/avatar.stories.tsx:2-3
Timestamp: 2025-10-01T15:30:17.605Z
Learning: In the makeplane/plane repository, avoid suggesting inline type imports (e.g., `import { Avatar, type TAvatarSize }`) due to bundler compatibility issues. Keep type imports and value imports as separate statements.

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Use copying array methods (`toSorted`, `toSpliced`, `with`) for immutable array operations (TypeScript 5.2+)

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Use constant indices to narrow object/array properties (TypeScript 5.5+)

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Use `using` declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
📚 Learning: 2025-10-09T20:42:31.843Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.843Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.

Applied to files:

  • packages/editor/src/core/components/editors/document/page-renderer.tsx
🧬 Code graph analysis (2)
packages/editor/src/core/hooks/use-collaborative-editor.ts (5)
packages/editor/src/core/types/hook.ts (2)
  • TCollaborativeEditorHookProps (42-63)
  • TEditorHookProps (20-40)
packages/editor/src/core/hooks/use-editor-navigation.ts (1)
  • useEditorNavigation (141-169)
packages/editor/src/core/hooks/use-editor.ts (1)
  • useEditor (19-157)
packages/editor/src/core/types/editor.ts (3)
  • EditorTitleRefApi (146-146)
  • ICollaborativeDocumentEditorProps (187-205)
  • IEditorProps (149-179)
packages/editor/src/core/hooks/use-title-editor.ts (1)
  • useTitleEditor (34-82)
packages/editor/src/core/components/editors/document/page-renderer.tsx (5)
packages/editor/src/core/contexts/collaboration-context.tsx (1)
  • TCollabValue (5-5)
packages/editor/src/core/components/editors/editor-container.tsx (1)
  • EditorContainer (29-179)
packages/editor/src/core/components/editors/editor-content.tsx (1)
  • EditorContentWrapper (13-26)
packages/editor/src/core/components/menus/bubble-menu/root.tsx (1)
  • EditorBubbleMenu (70-229)
packages/editor/src/core/components/menus/block-menu.tsx (1)
  • BlockMenu (38-249)
🔇 Additional comments (5)
packages/editor/src/core/hooks/use-title-editor.ts (1)

62-79: LGTM on the imperative handle implementation.

The getEditorMetaData parameter is now correctly provided with a sensible fallback. The clearEditor and setEditorValue implementations properly handle the editor operations.

packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

196-206: LGTM on editor wiring and return shape.

The effect correctly synchronizes both editors with the navigation refs when available, and the return shape properly exposes both editor and titleEditor.

packages/editor/src/core/components/editors/document/page-renderer.tsx (3)

1-16: LGTM!

The import statements correctly use import type for type-only imports and maintain separate statements for types and values, consistent with the project's bundler compatibility requirements.


66-83: Verify collaboration support for the title editor.

The main editor receives provider and state props for real-time synchronization (lines 90-91), but the title editor does not. Given that this PR focuses on realtime sync refactoring and the AI summary mentions title synchronization, please confirm whether the title editor should also receive these props for collaborative editing support.

If the title editor needs collaboration support, apply this diff:

           <EditorContainer
             editor={titleEditor}
             id={id + "-title"}
             isTouchDevice={isTouchDevice}
             editorContainerClassName="page-title-editor bg-transparent py-3 border-none"
             displayConfig={displayConfig}
+            provider={provider}
+            state={state}
           >

94-109: > Likely an incorrect or invalid review comment.

aaryan610
aaryan610 previously approved these changes Dec 10, 2025
@Palanikannan1437 Palanikannan1437 marked this pull request as draft December 10, 2025 12:00
@Palanikannan1437 Palanikannan1437 marked this pull request as ready for review December 10, 2025 12:23
aaryan610
aaryan610 previously approved these changes Dec 10, 2025
Copy link
Contributor

@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: 0

♻️ Duplicate comments (3)
packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

26-32: actions prop is still unused in the hook body.

actions (with signalForcedClose) is part of UseCollaborativeEditorArgs and is destructured but never referenced. Either remove it from the type/destructuring or actually wire it into the hook where a forced-close signal should be emitted/handled (e.g., on specific collaboration state changes or cleanup).

This was also flagged in a previous review and appears still unresolved.

Also applies to: 61-63

apps/live/src/extensions/title-sync.ts (2)

43-47: Fix unsafe optional chaining around fetchDetails and .name access

(await service.fetchDetails?.(documentName)).name will throw when fetchDetails is undefined: the optional call returns undefined, await yields undefined, and accessing .name then raises a TypeError. This is also what Biome is flagging.

Split the call and guard the property access:

-        // const title = await service.fe
-        const title = (await service.fetchDetails?.(documentName)).name;
-        if (title == null) return;
+        const details = await service.fetchDetails?.(documentName);
+        const title = details?.name;
+        if (title == null) return;

25-33: parentId is never populated, breaking title-change broadcasts

titleObserverData includes an optional parentId, but afterLoadDocument only stores userId, workspaceSlug, and instance. Since parentId never exists, data.parentId is always undefined, the guard at line 114 never satisfies, and the broadcast call at line 125 would fail even if the guard passed (the function expects documentName as the second parameter, not parentId).

Remove parentId from the type and fix the broadcast call to use documentName:

-  private titleObserverData: Map<
-    string,
-    {
-      parentId?: string | null;
-      userId: string;
-      workspaceSlug: string | null;
-      instance: Hocuspocus;
-    }
-  > = new Map();
+  private titleObserverData: Map<
+    string,
+    {
+      userId: string;
+      workspaceSlug: string | null;
+      instance: Hocuspocus;
+    }
+  > = new Map();
@@
-    // Broadcast to parent page if it exists
-    if (data?.parentId && data.workspaceSlug && data.instance) {
+    // Broadcast to page if it exists
+    if (data?.workspaceSlug && data.instance) {
@@
-      broadcastMessageToPage(data.instance, data.parentId, event);
+      broadcastMessageToPage(data.instance, documentName, event);
🧹 Nitpick comments (12)
apps/web/core/hooks/use-page-fallback.ts (3)

33-42: Clean up commented-out toast notifications.

The commented-out toast calls with console.log placeholders leave the notification UX incomplete. Consider either uncommenting and activating the toasts, or removing the commented code if this is intentional temporary scaffolding.

Also applies to: 67-71


65-65: Use unknown instead of any for error type.

Modern TypeScript best practices prefer unknown for caught errors to enforce explicit type checking before usage.

Apply this diff:

-  } catch (error: any) {
+  } catch (error: unknown) {
     console.error(error);
     // setToast({
     //   type: TOAST_TYPE.ERROR,
     //   title: "Error",
-    //   message: `Failed to update description using backup mechanism, ${error?.message}`,
+    //   message: `Failed to update description using backup mechanism, ${error instanceof Error ? error.message : String(error)}`,
     // });

28-44: Consider guarding against concurrent fallback fetches.

Although the isFetchingFallbackBinary flag tracks fetch status, there's no guard preventing concurrent executions of handleUpdateDescription. Since both useAutoSave (line 86) and the useEffect (line 78) can trigger this callback, overlapping calls could cause redundant fetches when disconnected.

Add an early return guard:

 const handleUpdateDescription = useCallback(async () => {
   if (!hasConnectionFailed) return;
+  if (isFetchingFallbackBinary) return;
   const editor = editorRef.current;
   if (!editor) return;

And update the dependency array to include isFetchingFallbackBinary:

-}, [editorRef, fetchPageDescription, hasConnectionFailed, updatePageDescription]);
+}, [editorRef, fetchPageDescription, hasConnectionFailed, updatePageDescription, isFetchingFallbackBinary]);
apps/web/core/components/pages/editor/editor-body.tsx (4)

65-69: Avoid exposing an unused customRealtimeEventHandlers prop

customRealtimeEventHandlers is added to Props but never read or forwarded inside PageEditorBody, so passing it from callers currently has no effect. Either wire it into useRealtimePageEvents / the editor as intended, or drop it from Props for now to keep the public surface aligned with actual behavior.

Also applies to: 85-87


132-138: Realtime page events integration is correct; consider handler stability

Hooking useRealtimePageEvents and passing updatePageProperties into CollaborativeDocumentEditorWithRef is a clean separation of concerns. If handlers is not memoized in the parent, you may want to stabilize it (e.g., via useMemo) to avoid unnecessary resubscriptions or effect churn inside useRealtimePageEvents, but this is an optimization, not a blocker.

Also applies to: 280-280


99-99: Sync reset on page change is reasonable; confirm handler-change semantics

Resetting setSyncingStatus("syncing") and emitting a "connecting" collaboration state when pageId changes makes sense to model a fresh load. This will also fire if onCollaborationStateChange’s identity changes; if you ever pass a non‑stable callback from the parent and don’t want that to trigger a reset, consider wrapping the callback in a ref inside this component instead of keeping it in the dependency array.

Also applies to: 140-148


184-208: Consider distinguishing “real error” from “still loading” for realtimeConfig

If realtimeConfig fails to construct (e.g., bad LIVE_BASE_URL or malformed webhookConnectionParams), the catch block logs an error but isPageLoading stays true forever, so the user just sees a loader. It may be worth introducing a separate “fatal error” state (or exposing the failure upward) so configuration issues can surface as an explicit error instead of an infinite spinner.

Also applies to: 226-228

packages/editor/src/core/hooks/use-title-editor.ts (1)

16-28: Wire the placeholder prop into the Placeholder extension (and consider deps for onUpdate).

Props exposes a placeholder?: string, but the hook always renders "Untitled" via Placeholder.configure, so callers can’t customize the title placeholder. You can keep the current default while honoring the prop:

-  const {
-    editable = true,
-    id,
-    initialValue = "",
-    extensions,
-    provider,
-    updatePageProperties,
-    titleRef,
-    getEditorMetaData,
-  } = props;
+  const {
+    editable = true,
+    id,
+    initialValue = "",
+    extensions,
+    provider,
+    updatePageProperties,
+    titleRef,
+    getEditorMetaData,
+    placeholder,
+  } = props;
@@
-        Placeholder.configure({
-          placeholder: () => "Untitled",
+        Placeholder.configure({
+          placeholder: () => placeholder ?? "Untitled",
           includeChildren: true,
           showOnlyWhenEditable: false,
         }),

If id or updatePageProperties are ever non-stable, also consider adding them to the useEditor deps or memoizing them upstream to avoid a stale onUpdate closure.

Also applies to: 35-44, 49-67

packages/editor/src/core/components/editors/document/collaborative-editor.tsx (1)

92-101: Avoid returning null while editors initialize; consider showing a loader or skeleton instead.

if (!editor || !titleEditor) return null; results in an empty render while the editors are being created, even though you already have isLoading and state.isDocReady to drive loading UI via PageRenderer and CSS opacity. Consider rendering the same container and passing isLoading/state down (or a lightweight fallback) instead of returning null, so users always see a loader/skeleton rather than a blank area during initialization.

packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

160-195: Simplify titleEditorConfig typing to avoid the extra as cast.

The inline object type for titleEditorConfig followed by as Parameters<typeof useTitleEditor>[0] is a bit clunky. You can let TypeScript infer the shape directly from useTitleEditor’s parameter type:

-  const titleEditorConfig = useMemo<{
-    id: string;
-    editable: boolean;
-    provider: HocuspocusProvider;
-    titleRef?: React.MutableRefObject<EditorTitleRefApi | null>;
-    updatePageProperties?: ICollaborativeDocumentEditorProps["updatePageProperties"];
-    extensions: Extensions;
-    extendedEditorProps?: IEditorPropsExtended;
-    getEditorMetaData?: IEditorProps["getEditorMetaData"];
-  }>(
-    () => ({
+  const titleEditorConfig = useMemo<Parameters<typeof useTitleEditor>[0]>(
+    () => ({
       id,
       editable,
       provider,
       titleRef,
       updatePageProperties,
       extensions: titleExtensions,
       extendedEditorProps,
       getEditorMetaData,
     }),
-    [provider, id, editable, titleRef, updatePageProperties, titleExtensions, extendedEditorProps, getEditorMetaData]
+    [provider, id, editable, titleRef, updatePageProperties, titleExtensions, extendedEditorProps, getEditorMetaData]
   );
 
-  const titleEditor = useTitleEditor(titleEditorConfig as Parameters<typeof useTitleEditor>[0]);
+  const titleEditor = useTitleEditor(titleEditorConfig);

This keeps the config type-aligned with the hook’s signature and avoids redundant casting.

apps/live/src/extensions/title-sync.ts (2)

101-132: Title extraction and update scheduling look good; optionally simplify event handling

The extraction + scheduling path is clear and correctly uses the per‑document manager. If you only care about the latest event, you could avoid the loop and read events[events.length - 1] directly, but the current implementation is functionally fine.


137-145: Consider wrapping forceSave() in try/catch to avoid disrupting unload

beforeUnloadDocument awaits updateManager.forceSave() without a try/catch, so a rejected save could bubble out of the hook and interfere with the unload lifecycle. You already defensively clean up managers in afterUnloadDocument; mirroring your error‑handling pattern from onLoadDocument would make this more robust:

-    const updateManager = this.titleUpdateManagers.get(documentName);
-    if (updateManager) {
-      // Force immediate save and wait for it to complete
-      await updateManager.forceSave();
-      // Clean up the manager
-      this.titleUpdateManagers.delete(documentName);
-    }
+    const updateManager = this.titleUpdateManagers.get(documentName);
+    if (updateManager) {
+      try {
+        // Force immediate save and wait for it to complete
+        await updateManager.forceSave();
+      } catch (error) {
+        logger.error(
+          "Error force-saving document title before unload",
+          new AppError(error, { context: { operation: "beforeUnloadDocument", documentName } })
+        );
+      } finally {
+        // Clean up the manager regardless of outcome
+        this.titleUpdateManagers.delete(documentName);
+      }
+    }

Also applies to: 168-173

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8fa092 and 35f0e64.

📒 Files selected for processing (6)
  • apps/live/src/extensions/title-sync.ts (1 hunks)
  • apps/web/core/components/pages/editor/editor-body.tsx (10 hunks)
  • apps/web/core/hooks/use-page-fallback.ts (3 hunks)
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx (4 hunks)
  • packages/editor/src/core/hooks/use-collaborative-editor.ts (3 hunks)
  • packages/editor/src/core/hooks/use-title-editor.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • packages/editor/src/core/hooks/use-title-editor.ts
  • apps/live/src/extensions/title-sync.ts
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx
  • packages/editor/src/core/hooks/use-collaborative-editor.ts
  • apps/web/core/components/pages/editor/editor-body.tsx
  • apps/web/core/hooks/use-page-fallback.ts
🧠 Learnings (4)
📚 Learning: 2025-10-09T20:42:31.843Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7922
File: apps/admin/app/(all)/(dashboard)/ai/form.tsx:19-19
Timestamp: 2025-10-09T20:42:31.843Z
Learning: In the makeplane/plane repository, React types are globally available through TypeScript configuration. Type annotations like React.FC, React.ReactNode, etc. can be used without explicitly importing the React namespace. The codebase uses the modern JSX transform, so React imports are not required for JSX or type references.

Applied to files:

  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx
  • apps/web/core/components/pages/editor/editor-body.tsx
  • apps/web/core/hooks/use-page-fallback.ts
📚 Learning: 2025-10-21T17:22:05.204Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7989
File: apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/pages/(detail)/[pageId]/page.tsx:45-46
Timestamp: 2025-10-21T17:22:05.204Z
Learning: In the makeplane/plane repository, the refactor from useParams() to params prop is specifically scoped to page.tsx and layout.tsx files in apps/web/app (Next.js App Router pattern). Other components (hooks, regular client components, utilities) should continue using the useParams() hook as that is the correct pattern for non-route components.

Applied to files:

  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx
  • apps/web/core/components/pages/editor/editor-body.tsx
  • apps/web/core/hooks/use-page-fallback.ts
📚 Learning: 2025-10-01T15:30:17.605Z
Learnt from: lifeiscontent
Repo: makeplane/plane PR: 7888
File: packages/propel/src/avatar/avatar.stories.tsx:2-3
Timestamp: 2025-10-01T15:30:17.605Z
Learning: In the makeplane/plane repository, avoid suggesting inline type imports (e.g., `import { Avatar, type TAvatarSize }`) due to bundler compatibility issues. Keep type imports and value imports as separate statements.

Applied to files:

  • apps/web/core/components/pages/editor/editor-body.tsx
  • apps/web/core/hooks/use-page-fallback.ts
📚 Learning: 2025-05-14T13:16:23.323Z
Learnt from: vamsikrishnamathala
Repo: makeplane/plane PR: 7061
File: web/core/components/workspace-notifications/root.tsx:18-18
Timestamp: 2025-05-14T13:16:23.323Z
Learning: In the Plane project codebase, the path alias `@/plane-web` resolves to the `ce` directory, making imports like `@/plane-web/hooks/use-notification-preview` correctly resolve to files in `web/ce/hooks/`.

Applied to files:

  • apps/web/core/components/pages/editor/editor-body.tsx
🧬 Code graph analysis (3)
packages/editor/src/core/hooks/use-title-editor.ts (3)
packages/editor/src/core/types/editor.ts (3)
  • EditorTitleRefApi (146-146)
  • ICollaborativeDocumentEditorProps (187-205)
  • IEditorProps (149-179)
packages/editor/src/core/extensions/title-extension.ts (1)
  • TitleExtensions (6-14)
packages/editor/src/core/helpers/editor-ref.ts (1)
  • getEditorRefHelpers (24-282)
packages/editor/src/core/components/editors/document/collaborative-editor.tsx (5)
packages/editor/src/core/types/editor.ts (1)
  • ICollaborativeDocumentEditorProps (187-205)
packages/editor/src/core/contexts/collaboration-context.tsx (2)
  • useCollaboration (26-32)
  • CollaborationProvider (14-24)
packages/editor/src/core/hooks/use-collaborative-editor.ts (1)
  • useCollaborativeEditor (34-207)
packages/editor/src/core/components/editors/document/page-renderer.tsx (1)
  • PageRenderer (38-116)
packages/editor/src/index.ts (1)
  • CollaborativeDocumentEditorWithRef (3-3)
apps/web/core/hooks/use-page-fallback.ts (2)
packages/editor/src/core/types/collaboration.ts (1)
  • CollaborationState (29-33)
packages/types/src/page/core.ts (1)
  • TDocumentPayload (66-70)
🪛 Biome (2.1.2)
apps/live/src/extensions/title-sync.ts

[error] 45-45: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build packages
🔇 Additional comments (10)
apps/web/core/hooks/use-page-fallback.ts (4)

1-2: Refactoring to collaboration state looks good.

The shift from a boolean hasConnectionFailed prop to collaborationState: CollaborationState | null provides richer connection information and aligns with the broader real-time sync refactor.

Also applies to: 12-17


19-26: Well-structured hook setup with derived state.

Deriving hasConnectionFailed from collaborationState?.stage.kind centralizes the disconnection logic, and the new refs/state support the enhanced fallback UX.


77-84: Connection restoration logic looks good.

Resetting hasShownFallbackToast when the connection is restored allows the warning to be shown again if disconnection recurs, which is appropriate UX behavior.


88-88: Good API design exposing loading state.

Returning isFetchingFallbackBinary enables consuming components to display loading indicators during fallback operations.

apps/web/core/components/pages/editor/editor-body.tsx (4)

1-30: Imports and type wiring look consistent

React hooks usage matches imports, and import type for editor types keeps value bundles clean. No issues here.


88-89: Title ref creation and forwarding look correct

Creating titleEditorRef as useRef<EditorTitleRefApi>(null) and passing it through as titleRef to CollaborativeDocumentEditorWithRef is consistent with a controlled-title ref API. No changes needed here.

Also applies to: 267-267


165-179: Collaboration state → UI syncing mapping is clear and robust

Forwarding the full collaboration state to the parent and then mapping stage.kind into three UI buckets (error/synced/syncing) is a good separation of concerns. The default “else → syncing” branch also degrades gracefully if new stage kinds are introduced.

Also applies to: 181-181


261-267: Prop wiring into CollaborativeDocumentEditorWithRef looks consistent

Passing titleRef, updatePageProperties, and isFetchingFallbackBinary through to CollaborativeDocumentEditorWithRef aligns with the new collaboration and fallback behavior introduced above. As long as the editor package exposes these props as expected in this branch, there’s nothing more to change here.

Please double‑check against the current @plane/editor API that these prop names and types (titleRef, updatePageProperties, isFetchingFallbackBinary) are up to date and available in the version used by this app.

Also applies to: 280-291

packages/editor/src/core/components/editors/document/collaborative-editor.tsx (1)

53-84: Collaboration context wiring and ref forwarding look consistent.

Using CollaborationProvider + useCollaboration to supply a non-null provider into useCollaborativeEditor, and wrapping the legacy CollaborativeDocumentEditorWithRef around the new CollaborativeDocumentEditor keeps the external API stable while refactoring internals. The token memoization and prop threading into PageRenderer also look coherent with the new collaboration model.

Also applies to: 135-150, 152-156

packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

65-89: Collaborative + navigation wiring is coherent and memoized correctly.

The composition of SideMenuExtension, HeadingListExtension, Collaboration (with distinct fields for default vs title), and the navigation extensions, together with memoized editorConfig and the useEffect that registers main/title editors with useEditorNavigation, forms a clear, well-structured setup for collaborative editing. Dependency arrays on the memos/effect look correct for avoiding unnecessary re-instantiation while still reacting to key changes.

Also applies to: 105-159, 194-201

@Palanikannan1437 Palanikannan1437 changed the title Fix/realtime sync refactor fix: realtime sync post vite migration with title editor sync and indexeddb access Dec 10, 2025
@pushya22 pushya22 changed the title fix: realtime sync post vite migration with title editor sync and indexeddb access [WIKI-844] fix: realtime sync post vite migration with title editor sync and indexed db access Dec 10, 2025
@makeplane
Copy link

makeplane bot commented Dec 10, 2025

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
apps/live/src/extensions/title-update/title-utils.ts (1)

6-11: Duplicate function name exists in codebase with different implementations.

An extractTextFromHTML function already exists in packages/editor/src/core/helpers/yjs-utils.ts using regex-based stripping (/<[^>]*>/g), while this new implementation uses DOMPurify via sanitizeHTML. Although both functions are in separate modules and the DOMPurify approach is more secure and handles edge cases better, having two functions with identical names but different behaviors could create confusion during maintenance.

Consider either renaming this function (e.g., sanitizeHTMLToText) to clarify its approach, or consolidating by updating the existing function in yjs-utils.ts to use the safer DOMPurify approach for consistency across the codebase.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35f0e64 and 43bf244.

📒 Files selected for processing (1)
  • apps/live/src/extensions/title-update/title-utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • apps/live/src/extensions/title-update/title-utils.ts
🧬 Code graph analysis (1)
apps/live/src/extensions/title-update/title-utils.ts (2)
packages/editor/src/core/helpers/yjs-utils.ts (1)
  • extractTextFromHTML (208-212)
packages/utils/src/string.ts (1)
  • sanitizeHTML (122-125)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check:types
  • GitHub Check: check:lint
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/live/src/extensions/title-update/title-utils.ts (1)

1-11: Secure implementation using DOMPurify.

Using sanitizeHTML (which wraps DOMPurify) is the correct approach for extracting text from HTML. It properly handles malformed HTML, nested tags, and potential injection attempts—unlike regex-based approaches.

Regarding the past security scanner warning about "Incomplete multi-character sanitization": this appears to be a false positive. DOMPurify with ALLOWED_TAGS: [] completely strips all HTML elements through proper DOM parsing rather than string manipulation, so it won't leave partial tags like <script.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
packages/editor/src/core/helpers/yjs-utils.ts (1)

209-214: Consolidate duplicate extractTextFromHTML function.

This function is identically defined in apps/live/src/extensions/title-update/title-utils.ts (lines 5-10). Since title-utils.ts is listed as a dependent of this file, consider consolidating:

Option 1 (Recommended): Keep the function here in yjs-utils.ts and import it in title-utils.ts:

// In apps/live/src/extensions/title-update/title-utils.ts
import { extractTextFromHTML } from "@plane/editor/core/helpers/yjs-utils";
// Remove the local definition

Option 2: Remove this definition and import from title-utils.ts if this helper is only used for title operations:

-export const extractTextFromHTML = (html: string): string => {
-  // Use sanitizeHTML to safely extract text and remove all HTML tags
-  // This is more secure than regex as it handles edge cases and prevents injection
-  // Note: sanitizeHTML trims whitespace, which is acceptable for title extraction
-  return sanitizeHTML(html) || "";
-};

Option 1 is preferred as it centralizes the utility and improves reusability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43bf244 and 92aa3f7.

📒 Files selected for processing (1)
  • packages/editor/src/core/helpers/yjs-utils.ts (7 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • packages/editor/src/core/helpers/yjs-utils.ts
🧠 Learnings (3)
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Use `.ts`, `.mts`, `.cts` extensions in `import type` statements (TypeScript 5.2+)

Applied to files:

  • packages/editor/src/core/helpers/yjs-utils.ts
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{js,mjs,cjs} : Use `import` tags in JSDoc for cleaner type imports in JavaScript files when working in a mixed codebase (TypeScript 5.5+)

Applied to files:

  • packages/editor/src/core/helpers/yjs-utils.ts
📚 Learning: 2025-11-25T10:18:05.172Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: .github/instructions/typescript.instructions.md:0-0
Timestamp: 2025-11-25T10:18:05.172Z
Learning: Applies to **/*.{ts,tsx,mts,cts} : Use `with { type: "json" }` for import attributes; avoid deprecated `assert` syntax (TypeScript 5.3/5.8+)

Applied to files:

  • packages/editor/src/core/helpers/yjs-utils.ts
🧬 Code graph analysis (1)
packages/editor/src/core/helpers/yjs-utils.ts (5)
packages/editor/src/core/extensions/core-without-props.ts (2)
  • CoreEditorExtensionsWithoutProps (24-58)
  • DocumentEditorExtensionsWithoutProps (60-60)
packages/editor/src/core/extensions/title-extension.ts (1)
  • TitleExtensions (6-14)
apps/live/src/extensions/title-update/title-update-manager.ts (1)
  • updateTitle (45-68)
apps/live/src/extensions/title-update/title-utils.ts (1)
  • extractTextFromHTML (6-11)
packages/utils/src/string.ts (1)
  • sanitizeHTML (122-125)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (4)
packages/editor/src/core/helpers/yjs-utils.ts (4)

2-2: LGTM on imports.

The use of import type for Extensions follows TypeScript 5.0+ best practices for type-only imports. The new imports for TitleExtensions and sanitizeHTML are necessary for the added functionality.

Also applies to: 13-14


19-19: LGTM on the title extensions export.

The export provides a clear, type-safe API for consumers needing the title-specific editor extensions.


121-129: LGTM on the function signature extension.

The updateTitle parameter and optional titleHTML return property provide a clean API for conditional title extraction while maintaining backward compatibility.


193-196: LGTM on the function call update.

Passing false for updateTitle is appropriate for HTML-to-document conversion where the title hasn't been populated in the Y.Doc structure yet.

@sriramveeraghanta sriramveeraghanta merged commit e20f686 into preview Dec 10, 2025
8 checks passed
@sriramveeraghanta sriramveeraghanta deleted the fix/realtime-sync-refactor branch December 10, 2025 13:32
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.

3 participants