Skip to content

[WIKI-638] fix: peek overview closing while dropdowns are open#7841

Merged
pushya22 merged 5 commits intopreviewfrom
fix/peek-overview-close
Sep 24, 2025
Merged

[WIKI-638] fix: peek overview closing while dropdowns are open#7841
pushya22 merged 5 commits intopreviewfrom
fix/peek-overview-close

Conversation

@aaryan610
Copy link
Member

@aaryan610 aaryan610 commented Sep 23, 2025

Description

This PR fixes the bug where peek overview is getting closed on outside click/pressing the Esc key while editor dropdowns are still open.

Solution:

  1. Table dropdowns are now tracked using the utility extension storage.
  2. Editor ref now exposes another callback to find if any dropdowns are open or not.
  3. Instead of checking for DOM elements before closing the peek overview, we now use the editor ref helper.
  4. Created new commands to manage active dropbars instead of manually checking at every instance.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Summary by CodeRabbit

  • New Features

    • App now tracks editor dropdown/menu state and shares that state with issue peek views.
  • Bug Fixes

    • Prevents accidental closing or route changes when editor menus/dropbars are open.
    • Improved outside-click and keydown handling to respect active editor menus.
  • Documentation

    • Editor API expanded with a method to check whether any editor dropdown/menu is currently open.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds active-dropbar tracking to the editor (commands + storage + types), exposes EditorRefApi.isAnyDropbarOpen(), updates slash-commands and table drag-handle flows to register/unregister active dropbar extensions, and threads an editorRef into peek-overview components to consult dropbar state for outside-click/keydown logic.

Changes

Cohort / File(s) Summary
Peek overview components
apps/web/core/components/issues/peek-overview/view.tsx, apps/web/core/components/issues/peek-overview/issue-detail.tsx
Create/propagate an editorRef (type EditorRefApi) from view.tsx into PeekOverviewIssueDetails; use editorRef.current?.isAnyDropbarOpen() in outside-click and keydown handling; change PeekOverviewIssueDetails props to accept editorRef.
Editor ref API & helpers / types
packages/editor/src/core/helpers/editor-ref.ts, packages/editor/src/core/types/editor.ts
Add isAnyDropbarOpen(): boolean to EditorRefApi and implement it by reading CORE_EXTENSIONS.UTILITY storage activeDropbarExtensions.
Utility extension & TipTap augmentation
packages/editor/src/core/extensions/utility.ts
Extend TActiveDropbarExtensions to include SLASH_COMMANDS and TABLE; switch TipTap Commands key to computed [CORE_EXTENSIONS.UTILITY]; add addActiveDropbarExtension / removeActiveDropbarExtension commands and implementations that mutate storage.activeDropbarExtensions.
Slash-commands extension
packages/editor/src/core/extensions/slash-commands/root.tsx
Type renderItems as SuggestionOptions["render"]; onStart now calls editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.SLASH_COMMANDS); onExit removes it via editor.commands.removeActiveDropbarExtension(...).
Table drag-handles (row & column)
packages/editor/src/core/extensions/table/plugins/drag-handles/.../drag-handle.tsx
When floating/dropdown opens, call editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE); on close schedule removal via editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE); route dropdown close through context.onOpenChange(false).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant PeekView as PeekOverview View
  participant Detail as PeekOverviewIssueDetails
  participant EditorRef as EditorRefApi
  participant Utility as Utility Storage

  User->>PeekView: click / keydown while peek open
  PeekView->>EditorRef: isAnyDropbarOpen()
  EditorRef->>Utility: read activeDropbarExtensions
  Utility-->>EditorRef: hasEntries? (true/false)
  EditorRef-->>PeekView: true / false
  alt dropbar open (true)
    PeekView->>User: ignore outside-click / keydown close
  else no dropbar (false)
    PeekView->>User: proceed with route removal / close
  end
  PeekView->>Detail: render with `editorRef`
  Note right of Detail: Detail may also call `editorRef.isAnyDropbarOpen()`
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Palanikannan1437
  • sriramveeraghanta

Poem

I nibble at keys and twitch my ear,
Dropbars open? I keep you near.
Slash and table flags I tend—
Peek stays steady, not to end.
A little hop, a tidy merge 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "[WIKI-638] fix: peek overview closing while dropdowns are open" is a concise, single-sentence summary that clearly identifies the main change (a bug fix preventing the peek overview from closing while editor dropdowns are open) and includes the issue ID for easy tracking, so it accurately reflects the changeset.
Description Check ✅ Passed The PR description gives a clear problem statement, a concise solution summary, and correctly marks the change as a bug fix, so core information is present for reviewers; however, it omits the "Test Scenarios", "Screenshots and Media", and "References" sections from the repository template which are useful for reproducing and verifying the fix and for linking related work.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/peek-overview-close

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.

@makeplane
Copy link

makeplane bot commented Sep 23, 2025

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a bug where the peek overview closes unexpectedly when clicking outside or pressing the Esc key while editor dropdowns (table, slash commands) are still open.

  • Introduces a new isAnyDropbarOpen method to the editor API that checks for active dropdowns
  • Tracks dropdown state using the utility extension storage for table and slash command dropdowns
  • Updates peek overview close logic to prevent closing when dropdowns are active

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/editor/src/core/types/editor.ts Adds isAnyDropbarOpen method to the EditorRefApi type definition
packages/editor/src/core/helpers/editor-ref.ts Implements the isAnyDropbarOpen helper method using utility storage
packages/editor/src/core/extensions/utility.ts Extends dropdown tracking types to include table and slash commands
packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx Tracks row dropdown state and adds outside click prevention attributes
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx Tracks column dropdown state and adds outside click prevention attributes
packages/editor/src/core/extensions/slash-commands/root.tsx Tracks slash command dropdown state in utility storage
apps/web/core/components/issues/peek-overview/view.tsx Uses editor ref to check dropdown state before closing peek overview
apps/web/core/components/issues/peek-overview/issue-detail.tsx Receives editor ref as prop instead of creating its own

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

cursor[bot]

This comment was marked as outdated.

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: 2

🧹 Nitpick comments (6)
packages/editor/src/core/extensions/utility.ts (1)

14-19: Consider adding type safety for activeDropbarExtensions array operations.

The expanded TActiveDropbarExtensions type now includes more extension types, but the array operations in the drag handle components use indexOf and splice without type checking. Consider adding helper functions to safely manage this array.

Consider adding type-safe helper functions for managing the activeDropbarExtensions array:

// In packages/editor/src/core/helpers/extension-storage-helpers.ts
export const addActiveDropbarExtension = (
  storage: UtilityExtensionStorage,
  extension: TActiveDropbarExtensions
): void => {
  if (!storage.activeDropbarExtensions.includes(extension)) {
    storage.activeDropbarExtensions.push(extension);
  }
};

export const removeActiveDropbarExtension = (
  storage: UtilityExtensionStorage,
  extension: TActiveDropbarExtensions
): void => {
  const index = storage.activeDropbarExtensions.indexOf(extension);
  if (index !== -1) {
    storage.activeDropbarExtensions.splice(index, 1);
  }
};
packages/editor/src/core/extensions/slash-commands/root.tsx (3)

63-68: Avoid duplicate entries when marking SLASH_COMMANDS active.

Prevents multiple pushes which otherwise require multiple exits to clean up.

Apply this diff:

-      // Track active dropdown
-      getExtensionStorage(props.editor, CORE_EXTENSIONS.UTILITY).activeDropbarExtensions.push(
-        CORE_EXTENSIONS.SLASH_COMMANDS
-      );
+      // Track active dropdown
+      const utilityStorage = getExtensionStorage(props.editor, CORE_EXTENSIONS.UTILITY);
+      if (!utilityStorage.activeDropbarExtensions.includes(CORE_EXTENSIONS.SLASH_COMMANDS)) {
+        utilityStorage.activeDropbarExtensions.push(CORE_EXTENSIONS.SLASH_COMMANDS);
+      }

94-103: Also clear active state on Escape to avoid leaks.

If Suggestion doesn’t call onExit after hiding, the active flag can stick.

Apply this diff:

     onKeyDown: (props) => {
       if (props.event.key === "Escape") {
+        try {
+          const utilityStorage = getExtensionStorage(props.editor, CORE_EXTENSIONS.UTILITY);
+          let idx = utilityStorage.activeDropbarExtensions.lastIndexOf(CORE_EXTENSIONS.SLASH_COMMANDS);
+          while (idx > -1) {
+            utilityStorage.activeDropbarExtensions.splice(idx, 1);
+            idx = utilityStorage.activeDropbarExtensions.lastIndexOf(CORE_EXTENSIONS.SLASH_COMMANDS);
+          }
+        } catch {}
         popup?.[0].hide();
         return true;
       }

60-62: Type popup correctly and drop the ts-expect-error (optional).

tippy("body", ...) returns an array of instances. Use document.body for correct typing and declare popup as Instance[].

Apply this diff:

-  let popup: Instance | null = null;
+  let popup: Instance[] | null = null;
-      // @ts-expect-error - Tippy types are incorrect
-      popup = tippy("body", {
+      popup = tippy(document.body, {
         getReferenceClientRect: props.clientRect,
         appendTo: tippyContainer,
         content: component.element,
         showOnCreate: true,
         interactive: true,
         trigger: "manual",
         placement: "bottom-start",
       });

Also applies to: 76-85

apps/web/core/components/issues/peek-overview/view.tsx (2)

83-91: Coerce dropbar check to boolean; optionally gate on editor fullscreen modal too.

Prevents undefined from bypassing the guard; consider also ignoring outside-click when the editor image fullscreen modal is open.

Apply this diff:

   usePeekOverviewOutsideClickDetector(
     issuePeekOverviewRef,
     () => {
-      const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen();
+      const isAnyDropbarOpen = !!editorRef.current?.isAnyDropbarOpen();
+      const editorImageFullScreenModalElement = document.querySelector(".editor-image-full-screen-modal");
       if (!embedIssue) {
-        if (!isAnyModalOpen && !isAnyEpicModalOpen && !isAnyLocalModalOpen && !isAnyDropbarOpen) {
+        if (
+          !isAnyModalOpen &&
+          !isAnyEpicModalOpen &&
+          !isAnyLocalModalOpen &&
+          !isAnyDropbarOpen &&
+          !editorImageFullScreenModalElement
+        ) {
           removeRoutePeekId();
         }
       }
     },
     issueId
   );

98-104: Coerce dropbar check to boolean in Esc handler.

Ensures consistent gating.

Apply this diff:

-    const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen();
-    if (!isAnyModalOpen && !dropdownElement && !isAnyDropbarOpen && !editorImageFullScreenModalElement) {
+    const isAnyDropbarOpen = !!editorRef.current?.isAnyDropbarOpen();
+    if (!isAnyModalOpen && !dropdownElement && !isAnyDropbarOpen && !editorImageFullScreenModalElement) {
       removeRoutePeekId();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47f68e3 and 334ea49.

📒 Files selected for processing (8)
  • apps/web/core/components/issues/peek-overview/issue-detail.tsx (2 hunks)
  • apps/web/core/components/issues/peek-overview/view.tsx (6 hunks)
  • packages/editor/src/core/extensions/slash-commands/root.tsx (4 hunks)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (5 hunks)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (5 hunks)
  • packages/editor/src/core/extensions/utility.ts (1 hunks)
  • packages/editor/src/core/helpers/editor-ref.ts (1 hunks)
  • packages/editor/src/core/types/editor.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (2)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
packages/editor/src/index.ts (1)
  • CORE_EXTENSIONS (16-16)
packages/editor/src/core/extensions/slash-commands/root.tsx (2)
packages/editor/src/core/extensions/slash-commands/command-menu.tsx (1)
  • SlashCommandsMenuProps (10-14)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (2)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
packages/editor/src/index.ts (1)
  • CORE_EXTENSIONS (16-16)
packages/editor/src/core/helpers/editor-ref.ts (1)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (1)
packages/editor/src/core/types/editor.ts (1)
  • EditorRefApi (99-138)
apps/web/core/components/issues/peek-overview/view.tsx (2)
packages/editor/src/core/types/editor.ts (1)
  • EditorRefApi (99-138)
apps/web/core/store/issue/issue-details/root.store.ts (1)
  • isAnyModalOpen (227-238)
packages/editor/src/core/extensions/utility.ts (1)
packages/editor/src/ce/types/utils.ts (1)
  • TAdditionalActiveDropbarExtensions (1-1)
⏰ 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: Analyze (javascript)
  • GitHub Check: Build and lint web apps
🔇 Additional comments (10)
packages/editor/src/core/types/editor.ts (1)

122-122: LGTM!

The addition of the isAnyDropbarOpen method to the EditorRefApi interface is appropriate and aligns well with the PR's objective to fix the peek overview closing issue. The method signature follows the existing pattern and provides a clean way to query dropdown state.

packages/editor/src/core/helpers/editor-ref.ts (1)

84-88: LGTM! Clean implementation of dropbar state check.

The implementation correctly:

  • Guards against a missing editor instance
  • Retrieves the utility storage using the helper function
  • Returns a boolean based on the presence of active dropbar extensions
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (1)

206-206: Good use of data-prevent-outside-click attribute.

The addition of data-prevent-outside-click to both the FloatingOverlay and the dropdown container properly prevents unintended closures when interacting with dropdown content.

Also applies to: 216-216

packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (2)

205-205: Good use of data-prevent-outside-click attribute.

The addition of data-prevent-outside-click attributes maintains consistency with the column drag handle implementation.

Also applies to: 215-215


164-177: Extract duplicate dropbar tracking logic into a shared hook.

This is identical to the logic in column/drag-handle.tsx. As mentioned in the column drag handle review, this should be extracted into a shared custom hook.

packages/editor/src/core/extensions/utility.ts (2)

23-23: Good use of dynamic key for Commands augmentation.

Using [CORE_EXTENSIONS.UTILITY] as a computed property key instead of a hardcoded string "utility" improves maintainability and type safety.


14-19: Import path is correct — no action needed.
tsconfig (packages/editor/tsconfig.json) maps "@/plane-editor/" → ./src/ce/, so "@/plane-editor/types/utils" resolves to packages/editor/src/ce/types/utils.ts.

apps/web/core/components/issues/peek-overview/issue-detail.tsx (2)

173-176: Restore via editorRef LGTM.

Using setEditorValue(descriptionHTML, true) matches EditorRefApi.


32-42: Prop addition LGTM — verify all call sites are updated.

Automated search in the sandbox failed (rg errored on --type=tsx and returned no matches); ensure every <PeekOverviewIssueDetails ...> invocation passes the new required editorRef prop. To re-check locally: rg -n -S '<PeekOverviewIssueDetails' .

apps/web/core/components/issues/peek-overview/view.tsx (1)

171-181: Passing editorRef down LGTM.

Both layout branches correctly propagate editorRef to PeekOverviewIssueDetails.

Also applies to: 213-222

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 (4)
packages/editor/src/core/extensions/slash-commands/root.tsx (1)

63-67: Deduplicate storage push to avoid stale open-state.

Guard against duplicate entries when the menu re-initializes.

Apply this diff:

-      // Track active dropdown
-      getExtensionStorage(props.editor, CORE_EXTENSIONS.UTILITY).activeDropbarExtensions.push(
-        CORE_EXTENSIONS.SLASH_COMMANDS
-      );
+      // Track active dropdown (dedup)
+      const utilityStorage = getExtensionStorage(props.editor, CORE_EXTENSIONS.UTILITY);
+      if (!utilityStorage.activeDropbarExtensions.includes(CORE_EXTENSIONS.SLASH_COMMANDS)) {
+        utilityStorage.activeDropbarExtensions.push(CORE_EXTENSIONS.SLASH_COMMANDS);
+      }
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (1)

164-177: Add unmount cleanup to avoid stuck TABLE entry.

If the component unmounts while open, TABLE can remain in activeDropbarExtensions.

Apply this diff:

   useEffect(() => {
     const utilityStorage = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY);
     const index = utilityStorage.activeDropbarExtensions.indexOf(CORE_EXTENSIONS.TABLE);
     if (isDropdownOpen) {
       if (index === -1) {
         utilityStorage.activeDropbarExtensions.push(CORE_EXTENSIONS.TABLE);
       }
     } else {
       if (index !== -1) {
         utilityStorage.activeDropbarExtensions.splice(index, 1);
       }
     }
-  }, [editor, isDropdownOpen]);
+    return () => {
+      const i = utilityStorage.activeDropbarExtensions.indexOf(CORE_EXTENSIONS.TABLE);
+      if (i !== -1) utilityStorage.activeDropbarExtensions.splice(i, 1);
+    };
+  }, [editor, isDropdownOpen]);
packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (1)

163-177: Add unmount cleanup to avoid stuck TABLE entry.

Mirror the column handle cleanup to prevent stale open-state.

Apply this diff:

   useEffect(() => {
     const utilityStorage = getExtensionStorage(editor, CORE_EXTENSIONS.UTILITY);
     const index = utilityStorage.activeDropbarExtensions.indexOf(CORE_EXTENSIONS.TABLE);
     if (isDropdownOpen) {
       if (index === -1) {
         utilityStorage.activeDropbarExtensions.push(CORE_EXTENSIONS.TABLE);
       }
     } else {
       if (index !== -1) {
         utilityStorage.activeDropbarExtensions.splice(index, 1);
       }
     }
-  }, [editor, isDropdownOpen]);
+    return () => {
+      const i = utilityStorage.activeDropbarExtensions.indexOf(CORE_EXTENSIONS.TABLE);
+      if (i !== -1) utilityStorage.activeDropbarExtensions.splice(i, 1);
+    };
+  }, [editor, isDropdownOpen]);
apps/web/core/components/issues/peek-overview/view.tsx (1)

85-88: Use a default false to avoid undefined checks.

Minor clarity/readability improvement.

Apply this diff:

-      const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen();
+      const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen() ?? false;
-    const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen();
+    const isAnyDropbarOpen = editorRef.current?.isAnyDropbarOpen() ?? false;

Also applies to: 98-100

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47f68e3 and a86183b.

📒 Files selected for processing (8)
  • apps/web/core/components/issues/peek-overview/issue-detail.tsx (2 hunks)
  • apps/web/core/components/issues/peek-overview/view.tsx (6 hunks)
  • packages/editor/src/core/extensions/slash-commands/root.tsx (4 hunks)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (3 hunks)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (3 hunks)
  • packages/editor/src/core/extensions/utility.ts (1 hunks)
  • packages/editor/src/core/helpers/editor-ref.ts (1 hunks)
  • packages/editor/src/core/types/editor.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
packages/editor/src/core/extensions/slash-commands/root.tsx (2)
packages/editor/src/core/extensions/slash-commands/command-menu.tsx (1)
  • SlashCommandsMenuProps (10-14)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
apps/web/core/components/issues/peek-overview/issue-detail.tsx (1)
packages/editor/src/core/types/editor.ts (1)
  • EditorRefApi (99-138)
packages/editor/src/core/extensions/utility.ts (1)
packages/editor/src/ce/types/utils.ts (1)
  • TAdditionalActiveDropbarExtensions (1-1)
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (2)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
packages/editor/src/index.ts (1)
  • CORE_EXTENSIONS (16-16)
apps/web/core/components/issues/peek-overview/view.tsx (2)
packages/editor/src/core/types/editor.ts (1)
  • EditorRefApi (99-138)
apps/web/core/store/issue/issue-details/root.store.ts (1)
  • isAnyModalOpen (227-238)
packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (1)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
packages/editor/src/core/helpers/editor-ref.ts (1)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
⏰ 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 (10)
packages/editor/src/core/extensions/slash-commands/root.tsx (2)

59-59: Good type annotation for render.

Typing renderItems as SuggestionOptions["render"] improves clarity and tooling.


104-112: Bug: removes EMOJI instead of SLASH_COMMANDS on exit.

This leaves the slash-commands entry stuck in storage, making the app think a dropdown is still open.

Apply this diff:

-        const index = utilityStorage.activeDropbarExtensions.indexOf(CORE_EXTENSIONS.EMOJI);
+        const index = utilityStorage.activeDropbarExtensions.indexOf(CORE_EXTENSIONS.SLASH_COMMANDS);
packages/editor/src/core/extensions/utility.ts (2)

14-19: Active dropbar set looks right.

Including MENTION, EMOJI, SLASH_COMMANDS, and TABLE aligns with the new UI checks.


23-23: No action required — CORE_EXTENSIONS.UTILITY matches the extension name.
CORE_EXTENSIONS.UTILITY = "utility" in packages/editor/src/core/constants/extension.ts and the extension uses name: "utility" in packages/editor/src/core/extensions/utility.ts.

apps/web/core/components/issues/peek-overview/issue-detail.tsx (2)

32-46: Prop-based editorRef looks good.

Passing editorRef from parent removes local ref divergence and enables shared state checks.


127-137: Correctly threading editorRef to the description input.

This enables restore and dropdown state detection downstream.

apps/web/core/components/issues/peek-overview/view.tsx (2)

82-90: Outside-click gating by editor dropbars: LGTM.

Prevents closing when any editor dropdown is open.


171-181: Passing editorRef to details: LGTM.

Ensures consistent state checks across peek modes.

Also applies to: 213-222

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

122-122: isAnyDropbarOpen implemented and exposed on editor-ref.
Implementation found in packages/editor/src/core/helpers/editor-ref.ts; type declared in packages/editor/src/core/types/editor.ts; used in apps/web/core/components/issues/peek-overview/view.tsx.

packages/editor/src/core/helpers/editor-ref.ts (1)

84-88: LGTM! Well-implemented dropbar state check.

The length property is blazing fast because it's stored as a property on the array object - no computation needed, so checking activeDropbarExtensions.length > 0 is performant. The implementation follows a defensive pattern by returning false when the editor is unavailable, ensuring the peek overview behavior remains predictable.

The function correctly leverages the utility extension's storage to determine if any dropbar extensions are currently open, which aligns with the PR objective to prevent peek overview closure when editor dropdowns are active.

iam-vipin
iam-vipin previously approved these changes Sep 23, 2025
cursor[bot]

This comment was marked as outdated.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ceec33b and ecebd9a.

📒 Files selected for processing (4)
  • packages/editor/src/core/extensions/slash-commands/root.tsx (3 hunks)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (2 hunks)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (2 hunks)
  • packages/editor/src/core/extensions/utility.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
packages/editor/src/core/extensions/slash-commands/root.tsx (2)
packages/editor/src/core/extensions/slash-commands/command-menu.tsx (1)
  • SlashCommandsMenuProps (10-14)
packages/editor/src/index.ts (1)
  • CORE_EXTENSIONS (16-16)
packages/editor/src/core/extensions/utility.ts (2)
packages/editor/src/index.ts (1)
  • CORE_EXTENSIONS (16-16)
packages/editor/src/ce/types/utils.ts (1)
  • TAdditionalActiveDropbarExtensions (1-1)
⏰ 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: Cursor Bugbot
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (7)
packages/editor/src/core/extensions/utility.ts (4)

14-19: Active dropbar union looks right.

Covers the needed core extensions and leaves room for CE via TAdditionalActiveDropbarExtensions.


34-35: Good: public add/remove commands for dropbar tracking.

Clear API surface and consistent with other extensions’ usage.


112-123: Idempotent add/remove logic LGTM.

indexOf guard prevents dupes; single-splice removal is fine given the add-side guard.


23-23: CORE_EXTENSIONS.UTILITY is the string literal "utility" — no change required.
Defined as UTILITY = "utility" in packages/editor/src/core/constants/extension.ts, so the computed Commands key is safe for TypeScript typings.

packages/editor/src/core/extensions/slash-commands/root.tsx (3)

62-66: Correct: tracks SLASH_COMMANDS on start.

Matches the intent and fixes prior EMOJI/SLASH mismatch in older revisions.


91-95: Verify Escape path triggers onExit.

onKeyDown hides the popup and returns true. Ensure Suggestion still calls onExit so the removeActiveDropbarExtension runs; otherwise, also remove in this branch.

If needed, adjust:

     if (props.event.key === "Escape") {
-      popup?.[0].hide();
+      popup?.[0].hide();
+      props.editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.SLASH_COMMANDS);
       return true;
     }

101-104: Cleanup on exit is correct.

Removes SLASH_COMMANDS and tears down tippy/renderer.

onExit: () => {
onExit: ({ editor }) => {
// Remove from active dropdowns
editor?.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.SLASH_COMMANDS);
Copy link

Choose a reason for hiding this comment

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

Bug: TipTap Suggestion Plugin Callback Parameter Issue

The onExit callback for the TipTap Suggestion plugin now destructures { editor }, but the callback typically receives no parameters. This makes editor undefined, preventing removeActiveDropbarExtension from running and leaving the slash command in the active dropdowns list, which breaks dropdown tracking.

Fix in Cursor Fix in Web

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecebd9a and 712030a.

📒 Files selected for processing (2)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (3 hunks)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (2)
packages/editor/src/index.ts (1)
  • CORE_EXTENSIONS (16-16)
packages/editor/src/core/extensions/table/plugins/drag-handles/column/dropdown.tsx (1)
  • ColumnOptionsDropdown (62-100)
⏰ 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: Cursor Bugbot
  • GitHub Check: Build and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx (3)

18-19: LGTM: Importing CORE_EXTENSIONS here makes the intent explicit.


64-73: Deduplicate dropbar tracking logic via a shared hook (same as prior review).

Extract this onOpenChange add/remove into a reusable hook and reuse in row/drag-handle to keep behavior consistent.


208-208: Good: Route close through FloatingUI context.

Centralizing via context.onOpenChange(false) keeps state transitions consistent and avoids local/desynced toggles.

Comment on lines +64 to +73
onOpenChange: (open) => {
setIsDropdownOpen(open);
if (open) {
editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE);
} else {
setTimeout(() => {
editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE);
}, 0);
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Fix race when switching between table dropbars (shared key + deferred removal).

Problem:

  • Using a single key (CORE_EXTENSIONS.TABLE) and removing it via setTimeout can clear the active state when rapidly switching from one table dropdown to another (e.g., column -> row). The “close” of A schedules a removal that runs after B’s “open” added the same key, resulting in a false “no dropbar open”.

Recommendations:

  • Track per-instance IDs (ref-count) in the utility extension. Extend commands to accept an instanceId and remove only that instance.
  • Prefer queueMicrotask over setTimeout to reduce ordering surprises.
  • Ensure cleanup on unmount to avoid stale state.

Proposed change in this component (requires extending commands to accept instanceId):

-    onOpenChange: (open) => {
-      setIsDropdownOpen(open);
-      if (open) {
-        editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE);
-      } else {
-        setTimeout(() => {
-          editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE);
-        }, 0);
-      }
-    },
+    onOpenChange: (open) => {
+      setIsDropdownOpen(open);
+      if (open) {
+        editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE, instanceIdRef.current);
+      } else {
+        queueMicrotask(() => {
+          editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE, instanceIdRef.current);
+        });
+      }
+    },

Additionally (outside this hunk), create a stable instance id and clean up on unmount:

// imports: add useRef, useEffect
const instanceIdRef = useRef(Symbol("table-col-dropbar"));

useEffect(() => {
  return () => {
    // defensive cleanup
    try {
      editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE, instanceIdRef.current);
    } catch {}
  };
}, [editor]);

Run to verify whether ref-counting already exists (if so, just pass instance ids everywhere):

Expected:

  • Storage holds a map/set of instance IDs per extension (not a single array of extension keys).
  • Commands support an instance identifier for add/remove.
    If not, introduce it in the utility extension and update all call sites accordingly.

🏁 Script executed:

#!/bin/bash
rg -n -C2 "activeDropbarExtensions" packages/editor
rg -n -C3 "addActiveDropbarExtension|removeActiveDropbarExtension" packages/editor

Length of output: 11357


Fix race when switching dropbars: implement per-instance tracking for activeDropbarExtensions

Utility currently stores activeDropbarExtensions as a simple array and add/remove only accept the extension key; the deferred removal via setTimeout can remove a key added by another instance (race). Implement per-instance ref-counting and update call sites.

  • Change storage to Map<TActiveDropbarExtensions, Set> (or equivalent ref-counting).
  • Change commands to accept instanceId (e.g. addActiveDropbarExtension(extension, instanceId) / removeActiveDropbarExtension(extension, instanceId)) and only remove the instanceId; delete the map entry only when its set is empty.
  • Replace setTimeout(...) removals with queueMicrotask(...) to reduce ordering surprises and ensure microtask ordering with React updates.
  • Add stable per-instance ids in components (useRef(Symbol(...))) and remove on unmount (useEffect cleanup).
  • Update consumers to query “any active dropbars” via the new storage shape (helper like hasActiveDropbars) instead of checking activeDropbarExtensions.length.

Call sites to update:

  • packages/editor/src/core/extensions/utility.ts (storage + command implementations)
  • packages/editor/src/core/helpers/editor-ref.ts
  • packages/editor/src/core/extensions/enter-key.ts
  • packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx
  • packages/editor/src/core/extensions/table/plugins/drag-handles/row/drag-handle.tsx
  • packages/editor/src/core/extensions/mentions/utils.ts
  • packages/editor/src/core/extensions/emoji/suggestion.ts
  • packages/editor/src/core/extensions/slash-commands/root.tsx
  • (Any other locations that push/splice activeDropbarExtensions)

Component-level change (example; requires command API change):

-    onOpenChange: (open) => {
-      setIsDropdownOpen(open);
-      if (open) {
-        editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE);
-      } else {
-        setTimeout(() => {
-          editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE);
-        }, 0);
-      }
-    },
+    onOpenChange: (open) => {
+      setIsDropdownOpen(open);
+      if (open) {
+        editor.commands.addActiveDropbarExtension(CORE_EXTENSIONS.TABLE, instanceIdRef.current);
+      } else {
+        queueMicrotask(() => {
+          editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE, instanceIdRef.current);
+        });
+      }
+    },

Also add in the component:

const instanceIdRef = useRef(Symbol("table-col-dropbar"));
useEffect(() => () => {
  try { editor.commands.removeActiveDropbarExtension(CORE_EXTENSIONS.TABLE, instanceIdRef.current); } catch {}
}, [editor]);
🤖 Prompt for AI Agents
packages/editor/src/core/extensions/table/plugins/drag-handles/column/drag-handle.tsx
lines 64-73: the current add/remove of CORE_EXTENSIONS.TABLE uses a global array
and deferred setTimeout removal which can remove a key added by another instance
(race); replace this by giving each component a stable per-instance id
(useRef(Symbol("table-col-dropbar"))), pass that id into
editor.commands.addActiveDropbarExtension(extension, instanceId) and
removeActiveDropbarExtension(extension, instanceId), change the utility storage
to Map<ExtensionKey, Set<InstanceId>> (or ref-counting set) and only delete the
map entry when the set is empty, switch deferred removal to queueMicrotask(...)
instead of setTimeout, add a helper hasActiveDropbars() that checks map
non-emptiness and update all consumers listed in the review to use the new APIs,
and ensure the component removes its id on unmount via useEffect cleanup calling
removeActiveDropbarExtension inside a try/catch.

@pushya22 pushya22 merged commit dce8b75 into preview Sep 24, 2025
7 of 8 checks passed
@pushya22 pushya22 deleted the fix/peek-overview-close branch September 24, 2025 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants