Skip to content

Add keyboard shortcuts for jumping to sidebar threads#1456

Open
t3dotgg wants to merge 1 commit intomainfrom
t3code/thread-number-shortcuts
Open

Add keyboard shortcuts for jumping to sidebar threads#1456
t3dotgg wants to merge 1 commit intomainfrom
t3code/thread-number-shortcuts

Conversation

@t3dotgg
Copy link
Member

@t3dotgg t3dotgg commented Mar 27, 2026

Summary

  • Added platform-aware thread jump shortcuts for the first nine visible sidebar threads (Cmd+1-Cmd+9 on macOS, Ctrl+1-Ctrl+9 elsewhere).
  • Displayed contextual shortcut hints next to thread rows while the modifier key is held.
  • Extracted jump-key logic into shared sidebar helpers and covered it with unit tests.
  • Reused the existing thread navigation path so shortcut selection behaves the same as clicking a thread.

Testing

  • Not run

Note

Medium Risk
Adds global keydown/keyup listeners to drive navigation and UI hint state, which could conflict with existing shortcuts or focus/selection behavior. Scope is limited to sidebar UX with unit tests covering the new helper logic.

Overview
Adds platform-aware keyboard shortcuts to jump to the first nine visible sidebar threads (Cmd+1..9 on macOS, Ctrl+1..9 elsewhere) and navigates using the same path as clicking a thread (via a shared navigateToThread helper).

Shows contextual shortcut hint pills next to eligible thread rows only while the modifier key is held, and refactors per-project/thread rendering into a memoized renderedProjects structure to compute the visible-thread ordering used for jump-key assignment.

Extracts jump-key parsing/labeling into new Sidebar.logic helpers (getThreadJumpKey, isThreadJumpModifierPressed, resolveThreadJumpIndex, formatThreadJumpHintLabel) and adds unit tests for them.

Written by Cursor Bugbot for commit aa91042. This will update automatically on new commits. Configure here.

Note

Add keyboard shortcuts for jumping to sidebar threads

  • Holding Cmd (macOS) or Ctrl (other platforms) in the sidebar reveals numeric hint badges (1–9) on visible threads across all projects.
  • Pressing Cmd/Ctrl+1 through Cmd/Ctrl+9 navigates directly to the corresponding thread without mouse interaction.
  • New helpers in Sidebar.logic.ts handle key assignment (getThreadJumpKey), modifier detection (isThreadJumpModifierPressed), index resolution (resolveThreadJumpIndex), and label formatting (formatThreadJumpHintLabel).
  • Sidebar.tsx registers keydown/keyup/blur handlers on window to show/hide hints and trigger navigation; thread navigation is refactored into a shared navigateToThread callback.

Macroscope summarized aa91042.

Summary by CodeRabbit

  • New Features
    • Added keyboard shortcuts to quickly jump between threads using keys 1–9 with a platform-specific modifier (Cmd on macOS, Ctrl on other platforms).
    • Visual hints now display on thread rows indicating which key to press for each thread when the jump modifier is held.

- Map the first nine visible threads to number keys
- Show platform-specific shortcut hints while modifier is held
- Add tests for key mapping and modifier detection
@coderabbitai
Copy link

coderabbitai bot commented Mar 27, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c3b1aa82-9b10-4144-88be-4f69cf35c3c8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/thread-number-shortcuts

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

@github-actions github-actions bot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Mar 27, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
apps/web/src/components/Sidebar.logic.test.ts (1)

110-147: Good platform-aware modifier detection tests.

Tests correctly cover:

  • macOS with metaKey
  • Windows with ctrlKey
  • Rejection when shiftKey is also pressed

Consider adding a test case for altKey: true to verify it's also rejected, matching the implementation requirement.

📝 Optional: Add altKey rejection test
expect(
  isThreadJumpModifierPressed(
    {
      key: "Control",
      metaKey: false,
      ctrlKey: true,
      shiftKey: false,
      altKey: true,
    },
    "Win32",
  ),
).toBe(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/Sidebar.logic.test.ts` around lines 110 - 147, Add a
test asserting that isThreadJumpModifierPressed rejects when altKey is true: in
the test "detects the active jump modifier by platform" add a case calling
isThreadJumpModifierPressed with a Windows platform ("Win32") and an event
object where ctrlKey: true, altKey: true (and shiftKey: false, metaKey: false)
and expect the result to be false; this mirrors the existing shiftKey rejection
case and verifies altKey is also treated as an invalid modifier.
apps/web/src/components/Sidebar.logic.ts (1)

81-111: Default navigator.platform may throw in non-browser contexts.

While Sidebar.tsx always passes platform explicitly, the default parameter navigator.platform would throw a ReferenceError if these helpers are ever called without an argument in SSR or test environments where navigator is undefined.

Consider using a safer default or documenting that the argument is required outside browser contexts.

🛡️ Suggested defensive default
 export function isThreadJumpModifierPressed(
   event: ThreadJumpEvent,
-  platform = navigator.platform,
+  platform = typeof navigator !== "undefined" ? navigator.platform : "",
 ): boolean {

Apply the same pattern to resolveThreadJumpIndex and formatThreadJumpHintLabel.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/Sidebar.logic.ts` around lines 81 - 111, Defaulting
parameters to navigator.platform can throw in non-browser environments; update
isThreadJumpModifierPressed, resolveThreadJumpIndex, and
formatThreadJumpHintLabel to use a safe platform default (e.g. const
safePlatform = typeof navigator !== "undefined" ? navigator.platform : "" or
undefined) instead of directly using navigator.platform in the parameter list,
then use safePlatform in calls to isMacPlatform and other logic so these helpers
won’t ReferenceError in SSR/tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/components/Sidebar.logic.test.ts`:
- Around line 110-147: Add a test asserting that isThreadJumpModifierPressed
rejects when altKey is true: in the test "detects the active jump modifier by
platform" add a case calling isThreadJumpModifierPressed with a Windows platform
("Win32") and an event object where ctrlKey: true, altKey: true (and shiftKey:
false, metaKey: false) and expect the result to be false; this mirrors the
existing shiftKey rejection case and verifies altKey is also treated as an
invalid modifier.

In `@apps/web/src/components/Sidebar.logic.ts`:
- Around line 81-111: Defaulting parameters to navigator.platform can throw in
non-browser environments; update isThreadJumpModifierPressed,
resolveThreadJumpIndex, and formatThreadJumpHintLabel to use a safe platform
default (e.g. const safePlatform = typeof navigator !== "undefined" ?
navigator.platform : "" or undefined) instead of directly using
navigator.platform in the parameter list, then use safePlatform in calls to
isMacPlatform and other logic so these helpers won’t ReferenceError in
SSR/tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f3c49538-55ac-433c-86b5-46df99b9e76a

📥 Commits

Reviewing files that changed from the base of the PR and between add5f34 and aa91042.

📒 Files selected for processing (3)
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.logic.ts
  • apps/web/src/components/Sidebar.tsx

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

}

return mapping;
}, [renderedProjects]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Jump keys assigned to threads in collapsed projects

Medium Severity

The threadJumpKeyById memo iterates over renderedProject.renderedThreads for every project, including those where shouldShowThreadPanel is false (i.e., collapsed projects with no active thread). These threads are hidden inside a closed Collapsible but still consume jump key slots 1–9. This causes visible threads in later projects to receive higher-numbered (or no) shortcuts, and pressing a shortcut for a hidden thread navigates to it without the user seeing it in the sidebar. The loop needs to skip projects where shouldShowThreadPanel is false.

Fix in Cursor Fix in Web

});
},
[clearSelection, navigate, selectedThreadIds.size, setSelectionAnchor],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Duplicated navigation logic in handleThreadClick and navigateToThread

Low Severity

The new navigateToThread callback duplicates the plain-click branch of the existing handleThreadClick callback (clear selection, set anchor, navigate). The plain-click path in handleThreadClick (lines 964–972) performs the exact same sequence — check selectedThreadIds.size, call clearSelection, setSelectionAnchor, and navigate — but was not updated to call navigateToThread. This duplication means a future change to the navigation logic risks being applied in one place but missed in the other.

Additional Locations (1)
Fix in Cursor Fix in Web

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant