From 01a214cc59f5e45019e9dddf807d543d64d94b40 Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Sat, 30 May 2026 14:13:38 -0400 Subject: [PATCH] fix: use platform shortcut labels in feature wall (#3811) --- .../feature-wall/BrowserAnimatedVisual.tsx | 6 ++- .../feature-wall/EditorAnimatedVisual.tsx | 8 +++- .../ReviewPRViewAnimatedVisual.tsx | 7 +++- .../feature-wall/WorkbenchAnimatedVisual.tsx | 19 +++++++-- .../feature-wall-shortcut-labels.test.tsx | 40 +++++++++++++++++++ 5 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 src/renderer/src/components/feature-wall/feature-wall-shortcut-labels.test.tsx diff --git a/src/renderer/src/components/feature-wall/BrowserAnimatedVisual.tsx b/src/renderer/src/components/feature-wall/BrowserAnimatedVisual.tsx index afe0a256e11..ed3f105e75b 100644 --- a/src/renderer/src/components/feature-wall/BrowserAnimatedVisual.tsx +++ b/src/renderer/src/components/feature-wall/BrowserAnimatedVisual.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState, type JSX, type ReactNode } from 'react' import { cn } from '@/lib/utils' import { ClaudeIcon } from '@/components/status-bar/icons' +import { useShortcutLabel } from '@/hooks/useShortcutLabel' import { FeatureWallClickRing } from './FeatureWallClickRing' // Why: this animation tells the full Orca story end-to-end — the user opens a @@ -152,6 +153,7 @@ const TERM_ENTRIES: readonly { entry: TermEntry; minPhase: Phase }[] = [ export function BrowserAnimatedVisual(props: { reducedMotion: boolean }): JSX.Element { const { reducedMotion } = props + const newBrowserShortcutLabel = useShortcutLabel('tab.newBrowser') const [phase, setPhase] = useState('idle') const [typedChars, setTypedChars] = useState(0) @@ -524,7 +526,9 @@ export function BrowserAnimatedVisual(props: { reducedMotion: boolean }): JSX.El New Browser Tab - ⌘⇧B + + {newBrowserShortcutLabel} + diff --git a/src/renderer/src/components/feature-wall/EditorAnimatedVisual.tsx b/src/renderer/src/components/feature-wall/EditorAnimatedVisual.tsx index f418bbf86bf..d5dfd5162c0 100644 --- a/src/renderer/src/components/feature-wall/EditorAnimatedVisual.tsx +++ b/src/renderer/src/components/feature-wall/EditorAnimatedVisual.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react' import type { JSX, ReactNode } from 'react' import { cn } from '@/lib/utils' +import { getShortcutPlatform } from '@/hooks/useShortcutLabel' // Why: the visual leans on direct DOM mutation (typing into a node, swapping // classes, anchoring a floating menu by measured rect) so the loop reads @@ -273,6 +274,9 @@ function SlashRow(props: { export function EditorAnimatedVisual(props: { reducedMotion: boolean }): JSX.Element { const { reducedMotion } = props + const editorShortcutPrefix = getShortcutPlatform() === 'darwin' ? '⌘' : 'Ctrl+' + const boldShortcutLabel = `${editorShortcutPrefix}B` + const italicShortcutLabel = `${editorShortcutPrefix}I` const docRef = useRef(null) const activeLineRef = useRef(null) @@ -714,8 +718,8 @@ export function EditorAnimatedVisual(props: { reducedMotion: boolean }): JSX.Ele shape. */}
Type / for blocks ·{' '} - ⌘B bold · ⌘I{' '} - italic + {boldShortcutLabel} bold ·{' '} + {italicShortcutLabel} italic
{/* Why: the imperative loop adds .slash-active and toggles diff --git a/src/renderer/src/components/feature-wall/ReviewPRViewAnimatedVisual.tsx b/src/renderer/src/components/feature-wall/ReviewPRViewAnimatedVisual.tsx index 9ce110c774d..79429a08554 100644 --- a/src/renderer/src/components/feature-wall/ReviewPRViewAnimatedVisual.tsx +++ b/src/renderer/src/components/feature-wall/ReviewPRViewAnimatedVisual.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef } from 'react' import type { ComponentType, JSX, ReactNode } from 'react' import { Files, GitBranch, ListChecks, MessageSquare, Search } from 'lucide-react' +import { useShortcutLabel } from '@/hooks/useShortcutLabel' import { ReviewPRViewVisualStyles } from './review-animated-visual-pr-view-styles' import { CheckTinyIcon, ChevDownIcon, CursorIcon } from './review-animated-visual-shared' @@ -18,6 +19,10 @@ const SIDEBAR_TABS: readonly { ] function SidebarTabs(props: { active: SidebarTabId; interactiveChecks?: boolean }): JSX.Element { + const checksShortcutLabel = useShortcutLabel('sidebar.checks.toggle') + const checksTooltip = + checksShortcutLabel === 'Unassigned' ? 'Checks' : `Checks (${checksShortcutLabel})` + return (
{SIDEBAR_TABS.map((tab) => { @@ -38,7 +43,7 @@ function SidebarTabs(props: { active: SidebarTabId; interactiveChecks?: boolean })} {props.interactiveChecks ? ( - Checks (⇧⌘K) + {checksTooltip} ) : null}
diff --git a/src/renderer/src/components/feature-wall/WorkbenchAnimatedVisual.tsx b/src/renderer/src/components/feature-wall/WorkbenchAnimatedVisual.tsx index 31ecec80df3..6d23144fc57 100644 --- a/src/renderer/src/components/feature-wall/WorkbenchAnimatedVisual.tsx +++ b/src/renderer/src/components/feature-wall/WorkbenchAnimatedVisual.tsx @@ -2,6 +2,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react' import type { JSX } from 'react' import { cn } from '@/lib/utils' +import { useShortcutLabel } from '@/hooks/useShortcutLabel' import { FeatureWallClickRing } from './FeatureWallClickRing' // Why: the right-click menu needs the same icons as the real Orca menu so the @@ -126,6 +127,8 @@ const RESPONSE_WIDTHS = [72, 88, 64, 78] as const export function WorkbenchAnimatedVisual(props: { reducedMotion: boolean }): JSX.Element { const { reducedMotion } = props + const splitRightShortcutLabel = useShortcutLabel('terminal.splitRight') + const splitDownShortcutLabel = useShortcutLabel('terminal.splitDown') const panelRef = useRef(null) const leftPaneRef = useRef(null) const splitRowRef = useRef(null) @@ -392,6 +395,8 @@ export function WorkbenchAnimatedVisual(props: { reducedMotion: boolean }): JSX. shown={menuShown} splitRowActive={splitRowActive} splitRowRef={splitRowRef} + splitRightShortcutLabel={splitRightShortcutLabel} + splitDownShortcutLabel={splitDownShortcutLabel} /> @@ -435,8 +440,8 @@ export function WorkbenchAnimatedVisual(props: { reducedMotion: boolean }): JSX. {/* Standalone keyboard hint stays inside the visual so the tour copy can remain a single subheader line. */}
- Same pane: ⌘D splits right ·{' '} - ⌘⇧D splits down + Same pane: {splitRightShortcutLabel} splits right ·{' '} + {splitDownShortcutLabel} splits down
) @@ -494,6 +499,8 @@ function ContextMenu(props: { shown: boolean splitRowActive: boolean splitRowRef: React.RefObject + splitRightShortcutLabel: string + splitDownShortcutLabel: string }): JSX.Element { return (
Split Terminal Right - ⌘D + + {props.splitRightShortcutLabel} +
Split Terminal Down - ⌘⇧D + + {props.splitDownShortcutLabel} +
diff --git a/src/renderer/src/components/feature-wall/feature-wall-shortcut-labels.test.tsx b/src/renderer/src/components/feature-wall/feature-wall-shortcut-labels.test.tsx new file mode 100644 index 00000000000..34f7ffb0183 --- /dev/null +++ b/src/renderer/src/components/feature-wall/feature-wall-shortcut-labels.test.tsx @@ -0,0 +1,40 @@ +import { renderToStaticMarkup } from 'react-dom/server' +import { afterEach, describe, expect, it } from 'vitest' +import { BrowserAnimatedVisual } from './BrowserAnimatedVisual' +import { EditorAnimatedVisual } from './EditorAnimatedVisual' +import { ReviewPRViewAnimatedVisual } from './ReviewPRViewAnimatedVisual' +import { WorkbenchAnimatedVisual } from './WorkbenchAnimatedVisual' + +const originalUserAgent = navigator.userAgent + +function setUserAgent(userAgent: string): void { + Object.defineProperty(navigator, 'userAgent', { + configurable: true, + value: userAgent + }) +} + +describe('feature wall shortcut labels', () => { + afterEach(() => { + setUserAgent(originalUserAgent) + }) + + it('renders Windows shortcut copy in workbench and browser visuals', () => { + setUserAgent('Windows NT 10.0') + + const html = [ + renderToStaticMarkup(), + renderToStaticMarkup(), + renderToStaticMarkup(), + renderToStaticMarkup() + ].join('\n') + + expect(html).toContain('Ctrl+Shift+B') + expect(html).toContain('Ctrl+Shift+D') + expect(html).toContain('Alt+Shift+D') + expect(html).toContain('Ctrl+B') + expect(html).toContain('Ctrl+I') + expect(html).toContain('Checks') + expect(html).not.toContain('⌘') + }) +})