@@ -325,18 +333,18 @@ function WelcomeView() {
{[
- ['⌘P', 'Quick Open'],
- ['⌘B', 'Toggle Explorer'],
- ['⌘J', 'Toggle Agent'],
- ['⌘K', 'Inline Edit'],
- ['⌘S', 'Save'],
- ['⌘⇧F', 'Search Files'],
- ['⌘\`', 'Terminal'],
+ ['meta+P', 'Quick Open'],
+ ['meta+B', 'Toggle Explorer'],
+ ['meta+J', 'Toggle Agent'],
+ ['meta+K', 'Inline Edit'],
+ ['meta+S', 'Save'],
+ ['meta+shift+F', 'Search Files'],
+ ['meta+`', 'Terminal'],
['?', 'All Shortcuts'],
- ].map(([key, label]) => (
-
+ ].map(([combo, label]) => (
+
- {key}
+ {formatShortcut(combo)}
{label}
@@ -1235,7 +1243,7 @@ export function CodeEditor() {
Reject
- ⌘⏎ accept · Esc reject
+ {formatShortcut('meta+Enter')} accept · Esc reject
)}
@@ -1250,7 +1258,7 @@ export function CodeEditor() {
{[
{
icon: 'lucide:message-square',
- tip: 'Add to Chat (⌘L)',
+ tip: `Add to Chat (${formatShortcut('meta+L')})`,
ev: 'add-to-chat',
detail: {
path: activeFile || 'untitled',
@@ -1261,7 +1269,7 @@ export function CodeEditor() {
},
{
icon: 'lucide:pencil',
- tip: 'Edit (⌘K)',
+ tip: `Edit (${formatShortcut('meta+K')})`,
ev: 'inline-edit-request',
detail: { text: selToolbar.text },
},
diff --git a/components/command-palette.tsx b/components/command-palette.tsx
index defcaa0..c999fc8 100644
--- a/components/command-palette.tsx
+++ b/components/command-palette.tsx
@@ -5,6 +5,7 @@ import { Icon } from '@iconify/react'
import { cn } from '@/lib/utils'
import { useView, type ViewId } from '@/context/view-context'
import { isTauri } from '@/lib/tauri'
+import { formatShortcut } from '@/lib/platform'
type CommandId =
| 'find-files'
@@ -53,7 +54,7 @@ interface CommandItem {
hint: string
keywords: string[]
icon: string
- shortcut?: string
+ combo?: string
group: 'search' | 'layout' | 'preset' | 'navigate' | 'git' | 'pr' | 'preview'
}
@@ -65,7 +66,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Open quick file search',
keywords: ['file', 'quick', 'open'],
icon: 'lucide:file-search',
- shortcut: '\u2318P',
+ combo: 'meta+P',
group: 'search',
},
{
@@ -74,7 +75,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Save the active file',
keywords: ['save', 'write', 'file'],
icon: 'lucide:save',
- shortcut: '\u2318S',
+ combo: 'meta+S',
group: 'search',
},
{
@@ -91,7 +92,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Open editor search',
keywords: ['find', 'search', 'match'],
icon: 'lucide:search',
- shortcut: '\u2318F',
+ combo: 'meta+F',
group: 'search',
},
{
@@ -100,7 +101,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Open replace widget',
keywords: ['replace', 'search', 'find'],
icon: 'lucide:replace',
- shortcut: '\u2318H',
+ combo: 'meta+H',
group: 'search',
},
{
@@ -135,7 +136,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Show or hide the file tree',
keywords: ['files', 'tree', 'explorer', 'sidebar'],
icon: 'lucide:folder',
- shortcut: '\u2318B',
+ combo: 'meta+B',
group: 'layout',
},
{
@@ -144,7 +145,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Show or hide the terminal panel',
keywords: ['terminal', 'shell', 'console'],
icon: 'lucide:terminal',
- shortcut: '\u2318J',
+ combo: 'meta+J',
group: 'layout',
},
{
@@ -153,7 +154,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Show or hide the AI agent panel',
keywords: ['chat', 'agent', 'ai', 'assistant'],
icon: 'lucide:message-square',
- shortcut: '\u2318I',
+ combo: 'meta+I',
group: 'layout',
},
{
@@ -170,7 +171,7 @@ const COMMANDS: CommandItem[] = [
hint: 'Minimize editor to icon rail',
keywords: ['collapse', 'minimize', 'hide', 'editor'],
icon: 'lucide:minimize-2',
- shortcut: '\u2318E',
+ combo: 'meta+E',
group: 'layout',
},
@@ -577,9 +578,9 @@ export function CommandPalette({ open, onClose, onRun }: CommandPaletteProps) {
{command.hint}
- {command.shortcut && (
+ {command.combo && (
- {command.shortcut}
+ {formatShortcut(command.combo)}
)}
diff --git a/components/editor-tabs.tsx b/components/editor-tabs.tsx
index 98183ba..8670b9a 100644
--- a/components/editor-tabs.tsx
+++ b/components/editor-tabs.tsx
@@ -3,6 +3,7 @@
import { useState, useCallback, useRef } from 'react'
import { Icon } from '@iconify/react'
import { useEditor } from '@/context/editor-context'
+import { formatShortcut } from '@/lib/platform'
const EXT_ICONS: Record
= {
ts: { icon: 'lucide:file-code', color: '#3178c6' },
@@ -151,7 +152,7 @@ export function EditorTabs() {
closeFile(file.path)
}}
className="p-1.5 rounded-lg opacity-0 group-hover:opacity-100 hover:bg-[color-mix(in_srgb,var(--text-primary)_10%,transparent)] transition-all cursor-pointer ml-1 hover:scale-110"
- title="Close (⌘W)"
+ title={`Close (${formatShortcut('meta+W')})`}
>
diff --git a/components/keyboard-handler.tsx b/components/keyboard-handler.tsx
index 8a374a2..b4cdd89 100644
--- a/components/keyboard-handler.tsx
+++ b/components/keyboard-handler.tsx
@@ -37,32 +37,32 @@ export function useKeyboardShortcuts({
const handler = (e: KeyboardEvent) => {
const meta = e.metaKey || e.ctrlKey
- // ⌘P — Quick open
+ // meta+P — Quick open
if (meta && e.key === 'p' && !e.shiftKey) {
e.preventDefault()
onQuickOpen()
}
- // ⌘⇧P — Command palette
+ // meta+shift+P — Command palette
if (meta && e.shiftKey && e.key === 'p') {
e.preventDefault()
onCommandPalette()
}
- // ⌘⇧F — Global search
+ // meta+shift+F — Global search
if (meta && e.shiftKey && e.key === 'f') {
e.preventDefault()
onGlobalSearch()
}
- // ⌘\\ — Toggle sidebar
+ // meta+\ — Toggle sidebar
if (meta && e.key === '\\') {
e.preventDefault()
layout.toggle('sidebar')
}
- // ⌘J / ⌘` — Toggle terminal
+ // meta+J / meta+` — Toggle terminal
if (meta && (e.key === 'j' || e.key === '`') && !e.shiftKey) {
e.preventDefault()
layout.toggle('terminal')
}
- // ⌘L — Open side chat panel and focus input
+ // meta+L — Open side chat panel and focus input
if (meta && e.key === 'l' && !e.shiftKey) {
e.preventDefault()
if (mode === 'chat') {
@@ -73,7 +73,7 @@ export function useKeyboardShortcuts({
}
requestAnimationFrame(() => emit('focus-agent-input'))
}
- // ⌘⌥1-4 — Focus key regions (explorer/editor/chat/terminal)
+ // meta+alt+1-4 — Focus key regions (explorer/editor/chat/terminal)
if (meta && e.altKey && ['1', '2', '3', '4'].includes(e.key)) {
e.preventDefault()
if (activeViewRef.current !== 'editor') setView('editor')
@@ -94,7 +94,7 @@ export function useKeyboardShortcuts({
}
}
// Esc — Close overlays (handled by each overlay individually via props)
- // ⌘⇧1/2/3 — Mode switching
+ // meta+shift+1/2/3 — Mode switching
if (meta && e.shiftKey && ['1', '2', '3'].includes(e.key)) {
e.preventDefault()
const modes: AppMode[] = ['classic', 'chat', 'tui']
@@ -102,7 +102,7 @@ export function useKeyboardShortcuts({
if (target) setMode(target)
return
}
- // ⌘1..N — View switching (mode-aware)
+ // meta+1..N — View switching (mode-aware)
if (meta && e.key >= '1' && e.key <= String(visibleViews.length)) {
e.preventDefault()
const target = visibleViews[parseInt(e.key) - 1]
@@ -126,7 +126,7 @@ export function useKeyboardShortcuts({
onFlashTab,
])
- // ─── ⌘S — Save file ───────────────────────────────────
+ // ─── meta+S — Save file ───────────────────────────────
useEffect(() => {
const keyHandler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
diff --git a/components/onboarding-tour.tsx b/components/onboarding-tour.tsx
index 55f7d17..486145e 100644
--- a/components/onboarding-tour.tsx
+++ b/components/onboarding-tour.tsx
@@ -2,6 +2,7 @@
import { useEffect, useMemo, useState } from 'react'
import { Icon } from '@iconify/react'
+import { formatShortcut } from '@/lib/platform'
export const ONBOARDING_KEY = 'ce:onboarding:v1'
@@ -29,12 +30,12 @@ export function OnboardingTour({ open, onClose }: { open: boolean; onClose: () =
},
{
title: 'Keyboard-first navigation',
- body: 'Use ⌘P to open files, ⌘⇧P for the command palette, and ⌘⌥1–4 to jump focus (Files / Editor / Chat / Terminal).',
+ body: `Use ${formatShortcut('meta+P')} to open files, ${formatShortcut('meta+shift+P')} for the command palette, and ${formatShortcut('meta+alt+1')}–4 to jump focus (Files / Editor / Chat / Terminal).`,
icon: 'lucide:keyboard',
},
{
title: 'Panels & layout',
- body: 'Toggle Explorer with ⌘B, Chat with ⌘I, Terminal with ⌘J (or ⌘`). On smaller screens, panels open as drawers to avoid clipping.',
+ body: `Toggle Explorer with ${formatShortcut('meta+B')}, Chat with ${formatShortcut('meta+I')}, Terminal with ${formatShortcut('meta+J')} (or ${formatShortcut('meta+`')}). On smaller screens, panels open as drawers to avoid clipping.`,
icon: 'lucide:layout-panel-left',
},
{
diff --git a/components/preview/preview-panel.tsx b/components/preview/preview-panel.tsx
index a588b94..75af0cb 100644
--- a/components/preview/preview-panel.tsx
+++ b/components/preview/preview-panel.tsx
@@ -14,6 +14,7 @@ import { useEditor } from '@/context/editor-context'
import { useView } from '@/context/view-context'
import { useLocal } from '@/context/local-context'
import { isTauri, tauriInvoke } from '@/lib/tauri'
+import { formatShortcut } from '@/lib/platform'
import { emit } from '@/lib/events'
/* ── Script metadata ─────────────────────────────────────────── */
@@ -705,7 +706,7 @@ function SingleDeviceZoomBar({
@@ -717,7 +718,7 @@ function SingleDeviceZoomBar({
}}
className="p-0.5 rounded hover:bg-[var(--bg-subtle)] text-[var(--text-disabled)] hover:text-[var(--text-secondary)] cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
disabled={zoom <= ZOOM_MIN}
- title="Zoom out (⌘−)"
+ title={`Zoom out (${formatShortcut('meta+-')})`}
>
@@ -753,7 +754,7 @@ function SingleDeviceZoomBar({
}}
className="p-0.5 rounded hover:bg-[var(--bg-subtle)] text-[var(--text-disabled)] hover:text-[var(--text-secondary)] cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
disabled={zoom >= ZOOM_MAX}
- title="Zoom in (⌘+)"
+ title={`Zoom in (${formatShortcut('meta+=')})`}
>
@@ -814,7 +815,7 @@ function SingleDeviceZoomBar({
className="w-full flex items-center gap-2 px-3 py-1.5 text-[11px] text-[var(--text-secondary)] hover:bg-[var(--bg-subtle)] transition-colors cursor-pointer"
>
- Fit to screen (⌘1)
+ Fit to screen ({formatShortcut('meta+1')})
>
diff --git a/components/settings-panel.tsx b/components/settings-panel.tsx
index 4f2509b..0fa3f4b 100644
--- a/components/settings-panel.tsx
+++ b/components/settings-panel.tsx
@@ -10,6 +10,7 @@ import { AgentBuilder, AgentSummary } from '@/components/agent-builder'
import { SkillsInterface } from '@/components/skills/skills-interface'
import { type AgentConfig, getAgentConfig, clearAgentConfig } from '@/lib/agent-session'
import { isTauri, tauriReadFileBase64 } from '@/lib/tauri'
+import { formatShortcut } from '@/lib/platform'
interface Props {
open: boolean
@@ -151,20 +152,20 @@ export function SettingsPanel({ open, onClose, initialTab }: Props) {
]
const shortcuts = [
- { keys: '⌘B', desc: 'Toggle explorer' },
- { keys: '⌘J', desc: 'Toggle terminal' },
- { keys: '⌘\\', desc: 'Toggle sidebar' },
- { keys: '⌘P', desc: 'Quick open file' },
- { keys: '⌘K', desc: 'Inline edit' },
- { keys: '⌘L', desc: 'Send selection to chat' },
- { keys: '⌘⌥1', desc: 'Focus explorer' },
- { keys: '⌘⌥2', desc: 'Focus editor' },
- { keys: '⌘⌥3', desc: 'Focus chat' },
- { keys: '⌘⌥4', desc: 'Focus terminal' },
- { keys: '⌘S', desc: 'Save file' },
- { keys: '⌘⇧F', desc: 'Global search' },
- { keys: '⌘⇧P', desc: 'Command palette' },
- { keys: 'Esc', desc: 'Close overlays' },
+ { combo: 'meta+B', desc: 'Toggle explorer' },
+ { combo: 'meta+J', desc: 'Toggle terminal' },
+ { combo: 'meta+\\', desc: 'Toggle sidebar' },
+ { combo: 'meta+P', desc: 'Quick open file' },
+ { combo: 'meta+K', desc: 'Inline edit' },
+ { combo: 'meta+L', desc: 'Send selection to chat' },
+ { combo: 'meta+alt+1', desc: 'Focus explorer' },
+ { combo: 'meta+alt+2', desc: 'Focus editor' },
+ { combo: 'meta+alt+3', desc: 'Focus chat' },
+ { combo: 'meta+alt+4', desc: 'Focus terminal' },
+ { combo: 'meta+S', desc: 'Save file' },
+ { combo: 'meta+shift+F', desc: 'Global search' },
+ { combo: 'meta+shift+P', desc: 'Command palette' },
+ { combo: 'Escape', desc: 'Close overlays' },
]
return (
@@ -625,12 +626,12 @@ export function SettingsPanel({ open, onClose, initialTab }: Props) {
{shortcuts.map((s) => (
{s.desc}
- {s.keys}
+ {formatShortcut(s.combo)}
))}
diff --git a/components/shortcuts-overlay.tsx b/components/shortcuts-overlay.tsx
index 2e8da6b..9d17d2a 100644
--- a/components/shortcuts-overlay.tsx
+++ b/components/shortcuts-overlay.tsx
@@ -3,6 +3,7 @@
import { useEffect, useState, useMemo } from 'react'
import { Icon } from '@iconify/react'
import { isTauri } from '@/lib/tauri'
+import { formatShortcutKeys } from '@/lib/platform'
interface ShortcutsOverlayProps {
open: boolean
@@ -10,31 +11,31 @@ interface ShortcutsOverlayProps {
}
const NAV_SHORTCUTS = [
- { keys: ['⌘', 'K'], desc: 'Command palette' },
- { keys: ['⌘', 'P'], desc: 'Quick file open' },
- { keys: ['⌘', 'B'], desc: 'Toggle file explorer' },
- { keys: ['⌘', 'J'], desc: 'Toggle agent panel' },
- { keys: ['⌘', '⇧', 'E'], desc: 'Toggle Gateway Engine' },
- { keys: ['⌘', '⌥', '1'], desc: 'Focus Explorer' },
- { keys: ['⌘', '⌥', '2'], desc: 'Focus Editor' },
- { keys: ['⌘', '⌥', '3'], desc: 'Focus Chat' },
- { keys: ['?'], desc: 'This shortcuts overlay' },
+ { combo: 'meta+shift+P', desc: 'Command palette' },
+ { combo: 'meta+P', desc: 'Quick file open' },
+ { combo: 'meta+B', desc: 'Toggle file explorer' },
+ { combo: 'meta+J', desc: 'Toggle agent panel' },
+ { combo: 'meta+shift+E', desc: 'Toggle Gateway Engine' },
+ { combo: 'meta+alt+1', desc: 'Focus Explorer' },
+ { combo: 'meta+alt+2', desc: 'Focus Editor' },
+ { combo: 'meta+alt+3', desc: 'Focus Chat' },
+ { combo: '?', desc: 'This shortcuts overlay' },
]
const NAV_TERMINAL_SHORTCUTS = [
- { keys: ['⌘', '`'], desc: 'Toggle terminal' },
- { keys: ['⌘', '⌥', '4'], desc: 'Focus Terminal' },
+ { combo: 'meta+`', desc: 'Toggle terminal' },
+ { combo: 'meta+alt+4', desc: 'Focus Terminal' },
]
const STATIC_SECTIONS = [
{
title: 'Editing',
shortcuts: [
- { keys: ['⌘', '⇧', 'K'], desc: 'Inline edit at selection' },
- { keys: ['⌘', '⇧', 'V'], desc: 'Cycle markdown edit/preview/split' },
- { keys: ['⌘', 'S'], desc: 'Save (commit) file' },
- { keys: ['⌘', 'Z'], desc: 'Undo' },
- { keys: ['⌘', '⇧', 'Z'], desc: 'Redo' },
+ { combo: 'meta+shift+K', desc: 'Inline edit at selection' },
+ { combo: 'meta+shift+V', desc: 'Cycle markdown edit/preview/split' },
+ { combo: 'meta+S', desc: 'Save (commit) file' },
+ { combo: 'meta+Z', desc: 'Undo' },
+ { combo: 'meta+shift+Z', desc: 'Redo' },
],
},
{
@@ -51,7 +52,7 @@ const STATIC_SECTIONS = [
{ keys: ['/undo'], desc: 'Undo last commit' },
],
},
-]
+] as const
export function ShortcutsOverlay({ open, onClose }: ShortcutsOverlayProps) {
const [isDesktop, setIsDesktop] = useState(false)
@@ -119,30 +120,33 @@ export function ShortcutsOverlay({ open, onClose }: ShortcutsOverlayProps) {
- {section.shortcuts.map((s) => (
-
-
- {s.desc}
-
-
- {s.keys.map((key, i) => (
-
- {key}
-
- ))}
+ {section.shortcuts.map((s) => {
+ const keys = 'combo' in s ? formatShortcutKeys(s.combo) : s.keys
+ return (
+
+
+ {s.desc}
+
+
+ {keys.map((key, i) => (
+
+ {key}
+
+ ))}
+
-
- ))}
+ )
+ })}
))}
diff --git a/components/status-bar.tsx b/components/status-bar.tsx
index 375cb56..5aaa09b 100644
--- a/components/status-bar.tsx
+++ b/components/status-bar.tsx
@@ -10,6 +10,7 @@ import { useAppMode } from '@/context/app-mode-context'
import { PluginSlotRenderer } from '@/context/plugin-context'
import { BranchPicker } from '@/components/branch-picker'
import { FolderIndicator } from '@/components/source-switcher'
+import { formatShortcut } from '@/lib/platform'
// ─── Activity Pulse Ring ─────────────────────────────
function ActivityPulseRing({ status, agentActive }: { status: string; agentActive: boolean }) {
@@ -124,7 +125,7 @@ export function StatusBar({ agentActive }: StatusBarProps) {
? 'text-[var(--brand)]'
: 'text-[var(--text-disabled)] hover:text-[var(--text-secondary)]'
}`}
- title={`${terminalVisible ? 'Hide' : 'Show'} Terminal (⌘J)`}
+ title={`${terminalVisible ? 'Hide' : 'Show'} Terminal (${formatShortcut('meta+J')})`}
>
diff --git a/components/views/editor-view.tsx b/components/views/editor-view.tsx
index c96bdb0..7c43248 100644
--- a/components/views/editor-view.tsx
+++ b/components/views/editor-view.tsx
@@ -14,6 +14,7 @@ import { FloatingPanel } from '@/components/floating-panel'
import { KnotLogo } from '@/components/knot-logo'
import { KnotBackground } from '@/components/knot-background'
import { isTauri } from '@/lib/tauri'
+import { formatShortcut } from '@/lib/platform'
import { emit } from '@/lib/events'
const FileExplorer = dynamic(
@@ -32,15 +33,15 @@ const AgentPanel = dynamic(
const PANEL_SPRING = { type: 'spring' as const, stiffness: 500, damping: 35 }
const QUICK_ACTIONS = [
- { icon: 'lucide:file-search', label: 'File', shortcut: '\u2318P', event: 'quick-open' },
- { icon: 'lucide:folder', label: 'Browse', shortcut: '\u2318B', event: 'toggle-tree' },
+ { icon: 'lucide:file-search', label: 'File', combo: 'meta+P', event: 'quick-open' },
+ { icon: 'lucide:folder', label: 'Browse', combo: 'meta+B', event: 'toggle-tree' },
{
icon: 'lucide:message-square',
label: 'Chat',
- shortcut: '\u2318L',
+ combo: 'meta+L',
event: 'open-side-chat',
},
- { icon: 'lucide:terminal', label: 'Terminal', shortcut: '\u2318J', event: 'toggle-terminal' },
+ { icon: 'lucide:terminal', label: 'Terminal', combo: 'meta+J', event: 'toggle-terminal' },
]
export function EditorView() {
@@ -150,7 +151,7 @@ export function EditorView() {
@@ -160,7 +161,7 @@ export function EditorView() {
layout.show('tree')
}}
className="mt-1.5 p-2.5 rounded-xl hover:bg-[var(--bg-subtle)] text-[var(--text-disabled)] hover:text-[var(--text-tertiary)] transition-colors cursor-pointer"
- title="Open explorer (⌘B)"
+ title={`Open explorer (${formatShortcut('meta+B')})`}
>
@@ -196,7 +197,7 @@ export function EditorView() {
@@ -227,7 +228,7 @@ export function EditorView() {
@@ -328,7 +329,7 @@ export function EditorView() {
{item.label}
- {item.shortcut}
+ {formatShortcut(item.combo)}
),
@@ -350,7 +351,7 @@ export function EditorView() {
/>
Press{' '}
- ⌘P
+ {formatShortcut('meta+P')}
{' '}
to quickly find any file
@@ -372,7 +373,7 @@ export function EditorView() {