Skip to content

Conversation

@ai-engineer-devansh-singh
Copy link
Contributor

🐛 Bug Fix

Fixes #1298

📝 Description

This PR resolves the issue where keyboard shortcuts (hotkeys) stop functioning after switching from editing mode to preview mode and back to editing mode in the Markdoc editor.

🔧 Changes Made

  • Restructured useMarkdownHotkeys hook in markdoc/editor/hotkeys/hotkeys.markdoc.ts
  • Added refs to track textarea element changes (currentTextareaRef and handlerRef)
  • Implemented proper event listener management to reattach listeners when textarea element changes
  • Fixed memory leaks by properly cleaning up old event listeners

🧪 Testing

Manual Testing Steps:

  1. ✅ Navigate to editor page (/create or /create/{id})
  2. ✅ Test hotkeys work initially (e.g., Ctrl+B for bold)
  3. ✅ Click "View preview"
  4. ✅ Click "Back to editing"
  5. ✅ Verify hotkeys still work after returning to edit mode
  6. ✅ Repeat multiple times to ensure consistency

Affected Hotkeys (All working after fix):

  • Ctrl+1 through Ctrl+6 - Headings
  • Ctrl+B - Bold
  • Ctrl+I - Italic
  • Ctrl+Shift+B - Bold Italic
  • Ctrl+S - Code snippet
  • Ctrl+Shift+C - Code block
  • Ctrl+Shift+. - Block quote
  • Ctrl+L - Link
  • Ctrl+Shift+I - Image
  • Ctrl+U - URL
  • Backspace - Select previous

🔍 Root Cause

The issue occurred because:

  1. Event listeners were attached directly to textarea elements
  2. When switching to preview mode, the textarea was unmounted
  3. When returning to edit mode, a new textarea element was created
  4. Event listeners remained attached to the old (unmounted) element
  5. New textarea had no event listeners

💡 Solution

The fix ensures that:

  • Event listeners are properly transferred to new textarea elements
  • Old event listeners are cleaned up to prevent memory leaks
  • The hook detects when the textarea element reference changes
  • Hotkeys work consistently across preview/edit mode switches

📂 Files Changed

  • markdoc/editor/hotkeys/hotkeys.markdoc.ts - Main fix implementation

🎯 Impact

  • ✅ Improves editor UX by maintaining hotkey functionality
  • ✅ Prevents memory leaks from orphaned event listeners
  • ✅ No breaking changes to existing functionality
  • ✅ All existing hotkeys continue to work as expected

- Restructure useMarkdownHotkeys hook to properly reattach event listeners
- Track current textarea element to detect when it changes
- Ensure hotkeys work after switching between preview and edit modes
@ai-engineer-devansh-singh ai-engineer-devansh-singh requested a review from a team as a code owner October 18, 2025 14:32
@vercel
Copy link

vercel bot commented Oct 18, 2025

@ai-engineer-devansh-singh is attempting to deploy a commit to the Codú Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 18, 2025

Walkthrough

Refactors event listener management in the hotkeys module by introducing refs to track the active textarea and keyboard handler. Replaces inline keydown binding with two coordinated useEffect hooks to ensure listeners attach and detach properly when switching between preview and edit modes.

Changes

Cohort / File(s) Summary
Event Listener Lifecycle Refactor
markdoc/editor/hotkeys/hotkeys.markdoc.ts
Adds useRef hook to track currentTextareaRef and handlerRef. Replaces direct keydown listener binding with two useEffect hooks: one that centralizes keyboard handler logic with meta/ctrl key detection, and another that manages attaching/detaching the listener to the active textarea with proper cleanup. Maintains hotkey processing behavior while eliminating stale reference issues on mode switches.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Editor as Editor Component
    participant Handler as Keyboard Handler
    participant DOM as DOM Event Listener

    Note over Editor,DOM: Old Flow (Buggy)
    User->>Editor: Mount component
    Editor->>DOM: Attach listener (stale reference)
    User->>Editor: Switch to preview
    Editor->>Editor: Component unmounts listener
    User->>Editor: Back to editing
    Editor->>Editor: Remount (but handler stale)
    User->>Handler: Press hotkey
    Handler-->>User: ❌ No response

    Note over Editor,DOM: New Flow (Fixed)
    User->>Editor: Mount component
    Editor->>Editor: useEffect: Store handler in handlerRef
    Editor->>DOM: useEffect: Attach fresh listener
    User->>Editor: Switch to preview
    Editor->>DOM: Cleanup: Detach listener
    User->>Editor: Back to editing
    Editor->>Editor: useEffect: Create new handler ref
    Editor->>DOM: useEffect: Reattach listener with fresh ref
    User->>Handler: Press hotkey
    Handler-->>User: ✅ Works correctly
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The change involves understanding ref-driven event listener management and verifying that the two-useEffect pattern properly handles textarea swaps and cleanup. Requires careful inspection of dependency arrays and event handler lifecycle to confirm the bug fix is complete.

Poem

🐰 A fix for hotkeys lost in the switch,
Two useEffect hooks in perfect pitch,
Refs keep the handler sharp and true,
No more stale bindings spoiling the view!
Preview to edit, back again—now the shortcuts flew. ⌨️✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Fix: Hotkeys stop working after switching between preview and edit modes #1298" clearly and concisely describes the primary change in the pull request. It is specific about what is being fixed (hotkey functionality after mode switching), directly relates to the actual code changes in the hotkeys.markdoc.ts file, and references the associated issue number. The title is neither vague nor misleading and would help teammates quickly understand the purpose of this PR when scanning history.
Description Check ✅ Passed The pull request description follows the repository template and provides comprehensive information addressing all required sections. It includes the issue reference ("Fixes #1298"), a detailed description of the bug and changes made, an explanation of the root cause, the solution approach, manual testing steps with verification checkpoints, a list of affected hotkeys, and an impact assessment. All critical sections are complete and well-documented, making it easy for reviewers to understand both the problem and how it was solved.

[pre_merge_check_pass]

The pull request implementation directly addresses all coding requirements specified in linked issue #1298. The changes to the useMarkdownHotkeys hook include adding refs to track textarea element changes, implementing proper event listener management that reattaches listeners when the textarea is recreated, and cleaning up old listeners to prevent memory leaks. The PR description confirms that all affected hotkeys listed in the issue (Ctrl+1-6, Ctrl+B, Ctrl+I, Ctrl+Shift+B, Ctrl+S, Ctrl+Shift+C, Ctrl+Shift+., Ctrl+L, Ctrl+Shift+I, Ctrl+U, and Backspace) are working after the fix. Manual testing steps align with the issue's reproduction steps and validation requirements. |
| Docstring Coverage | ✅ Passed | No functions found in the changes. Docstring coverage check skipped. |

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

🧹 Nitpick comments (2)
markdoc/editor/hotkeys/hotkeys.markdoc.ts (2)

174-198: Consider adding dependency array to prevent running on every render.

The useEffect has no dependency array, meaning it executes after every component render. While the early return on line 177 prevents unnecessary listener re-attachment, the comparison logic still runs on every render, which could impact performance if this component re-renders frequently.

Since textareaRef.current can change without triggering a re-render (refs are mutable), this pattern may be intentional. However, consider these alternatives:

  1. Callback ref pattern: Use a callback ref instead of useRef to get notified when the textarea element changes.
  2. Custom hook: Create a specialized hook that tracks ref changes more efficiently.

Example callback ref approach:

const attachListener = useCallback((textarea: HTMLTextAreaElement | null) => {
  // Clean up previous
  if (currentTextareaRef.current && handlerRef.current) {
    currentTextareaRef.current.removeEventListener('keydown', handlerRef.current);
  }
  
  // Attach new
  if (textarea && handlerRef.current) {
    textarea.addEventListener('keydown', handlerRef.current);
    currentTextareaRef.current = textarea;
  } else {
    currentTextareaRef.current = null;
  }
}, []);

Then pass attachListener as the ref callback.


174-198: Consider leveraging react-hotkeys-hook for consistency.

The codebase already uses react-hotkeys-hook (imported on line 2, used in shortcuts.markdoc.ts). The library is designed to handle hotkey lifecycle management, including the exact issue this PR addresses (listeners remaining on unmounted elements).

Consider refactoring to use useHotkeys for these keyboard shortcuts as well, which would:

  • Eliminate manual event listener management
  • Reduce the risk of lifecycle bugs
  • Provide a more consistent API across the codebase

Check if the library supports all the required key combinations:

#!/bin/bash
# Check how react-hotkeys-hook is currently used in the codebase
rg -n "useHotkeys" --type=ts --type=tsx -C 3
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01581a6 and 9698f37.

📒 Files selected for processing (1)
  • markdoc/editor/hotkeys/hotkeys.markdoc.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
markdoc/editor/hotkeys/hotkeys.markdoc.ts (2)
components/CustomTextareAutosize/CustomTextareaAutosize.tsx (2)
  • props (13-38)
  • node (16-35)
markdoc/editor/shortcuts/shortcuts.markdoc.ts (1)
  • textareaRef (36-101)
🔇 Additional comments (3)
markdoc/editor/hotkeys/hotkeys.markdoc.ts (3)

1-1: LGTM: Import changes support the fix.

Adding useRef is necessary for the ref-based event listener management pattern introduced in this fix.


53-54: LGTM: Refs enable proper listener lifecycle tracking.

The refs correctly track the textarea element and handler function, allowing the event listeners to update when the textarea changes without losing the reference to the current handler.


156-172: LGTM: Handler ref pattern avoids unnecessary listener churn.

Storing the handler in a ref allows the event listener to automatically use the latest handler without needing to detach/reattach listeners when handleHotkey changes. This is an effective optimization.

Copy link
Contributor

@NiallJoeMaher NiallJoeMaher left a comment

Choose a reason for hiding this comment

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

🌮

@NiallJoeMaher NiallJoeMaher merged commit 4a1228d into codu-code:develop Oct 18, 2025
0 of 3 checks passed
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.

Hotkeys Stop Working After Switching Between Preview and Edit Modes

2 participants