From 2e51aeff835492c1a9eb6e048fa63fe22213a52e Mon Sep 17 00:00:00 2001 From: Dan Rocha Date: Tue, 10 Mar 2026 13:32:07 -0600 Subject: [PATCH 1/2] feat: add task navigation with Ctrl+# shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add direct task navigation using Ctrl+1-9 shortcuts. Tasks display number badges (⌃1, ⌃2, etc.) and pressing Ctrl+N selects the corresponding task. Cmd+1-9 remains for agent tab switching. - Add getTaskSelectionIndex function for Ctrl+1-9 task selection - Add shortcut badges to TaskItem component (first 9 tasks globally) - Add handleSelectTaskByIndex to useTaskManagement hook - Add numberShortcutBehavior setting to swap Ctrl/Cmd behavior - Add settings UI dropdown to configure number shortcut behavior - Update tests for new keyboard shortcut functions The behavior is configurable: users can swap modifiers in Settings so that Cmd+# selects tasks and Ctrl+# switches agent tabs. Closes #1389 --- src/main/settings.ts | 8 + .../components/AppKeyboardShortcuts.tsx | 4 +- .../components/KeyboardSettingsCard.tsx | 36 ++- src/renderer/components/TaskItem.tsx | 12 + .../components/sidebar/LeftSidebar.tsx | 23 ++ src/renderer/hooks/useKeyboardShortcuts.ts | 72 ++++- src/renderer/hooks/useTaskManagement.ts | 19 ++ src/renderer/types/shortcuts.ts | 6 + .../renderer/useKeyboardShortcuts.test.ts | 259 ++++++++++++++---- 9 files changed, 387 insertions(+), 52 deletions(-) diff --git a/src/main/settings.ts b/src/main/settings.ts index 37546504f..d22c28153 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -34,6 +34,8 @@ export interface ShortcutBinding { modifier: ShortcutModifier; } +export type NumberShortcutBehavior = 'ctrl-tasks' | 'cmd-tasks'; + export interface KeyboardSettings { commandPalette?: ShortcutBinding; settings?: ShortcutBinding; @@ -48,6 +50,7 @@ export interface KeyboardSettings { newTask?: ShortcutBinding; nextAgent?: ShortcutBinding; prevAgent?: ShortcutBinding; + numberShortcutBehavior?: NumberShortcutBehavior; } export interface InterfaceSettings { @@ -166,6 +169,7 @@ const DEFAULT_SETTINGS: AppSettings = { newTask: { key: 'n', modifier: 'cmd' }, nextAgent: { key: ']', modifier: 'cmd+shift' }, prevAgent: { key: '[', modifier: 'cmd+shift' }, + numberShortcutBehavior: 'ctrl-tasks', }, interface: { autoRightSidebarBehavior: false, @@ -448,6 +452,10 @@ export function normalizeSettings(input: AppSettings): AppSettings { newTask: normalizeBinding(keyboard.newTask, DEFAULT_SETTINGS.keyboard!.newTask!), nextAgent: normalizeBinding(keyboard.nextAgent, DEFAULT_SETTINGS.keyboard!.nextAgent!), prevAgent: normalizeBinding(keyboard.prevAgent, DEFAULT_SETTINGS.keyboard!.prevAgent!), + numberShortcutBehavior: + keyboard.numberShortcutBehavior === 'cmd-tasks' + ? 'cmd-tasks' + : DEFAULT_SETTINGS.keyboard!.numberShortcutBehavior, }; const platformTaskDefaults = getPlatformTaskSwitchDefaults(); const isLegacyArrowPair = diff --git a/src/renderer/components/AppKeyboardShortcuts.tsx b/src/renderer/components/AppKeyboardShortcuts.tsx index 995f784ea..b284e2023 100644 --- a/src/renderer/components/AppKeyboardShortcuts.tsx +++ b/src/renderer/components/AppKeyboardShortcuts.tsx @@ -49,7 +49,8 @@ const AppKeyboardShortcuts: React.FC = ({ const { toggle: toggleRightSidebar } = useRightSidebar(); const { toggleTheme } = useTheme(); const { settings: keyboardSettings } = useKeyboardSettings(); - const { handleNextTask, handlePrevTask, handleNewTask } = useTaskManagementContext(); + const { handleNextTask, handlePrevTask, handleNewTask, handleSelectTaskByIndex } = + useTaskManagementContext(); useKeyboardShortcuts({ onToggleCommandPalette: handleToggleCommandPalette, @@ -72,6 +73,7 @@ const AppKeyboardShortcuts: React.FC = ({ ), onSelectAgentTab: (tabIndex) => window.dispatchEvent(new CustomEvent('emdash:select-agent-tab', { detail: { tabIndex } })), + onSelectTask: handleSelectTaskByIndex, onOpenInEditor: handleOpenInEditor, onCloseModal: ( [ diff --git a/src/renderer/components/KeyboardSettingsCard.tsx b/src/renderer/components/KeyboardSettingsCard.tsx index 00b63d00f..1fe81d794 100644 --- a/src/renderer/components/KeyboardSettingsCard.tsx +++ b/src/renderer/components/KeyboardSettingsCard.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react' import { ArrowBigUp, Command, RotateCcw } from 'lucide-react'; import { Button } from './ui/button'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; import { toast } from '../hooks/use-toast'; import { APP_SHORTCUTS, @@ -144,7 +145,12 @@ const KeyboardSettingsCard: React.FC = () => { const result: Record = {}; for (const shortcut of CONFIGURABLE_SHORTCUTS) { const saved = keyboard?.[shortcut.settingsKey as keyof typeof keyboard]; - result[shortcut.settingsKey] = saved ?? { key: shortcut.key, modifier: shortcut.modifier! }; + // Only use saved if it's a ShortcutBinding (has key and modifier) + if (saved && typeof saved === 'object' && 'key' in saved && 'modifier' in saved) { + result[shortcut.settingsKey] = saved; + } else { + result[shortcut.settingsKey] = { key: shortcut.key, modifier: shortcut.modifier! }; + } } return result as Record; }, [settings?.keyboard]); @@ -345,6 +351,34 @@ const KeyboardSettingsCard: React.FC = () => { ))} + + {/* Number shortcuts behavior setting */} +
+
+

Number shortcuts (1-9)

+

+ Choose which modifier switches tasks vs agent tabs +

+
+ +
+ {error ?

{error}

: null} diff --git a/src/renderer/components/TaskItem.tsx b/src/renderer/components/TaskItem.tsx index e6db43b65..bdd418a46 100644 --- a/src/renderer/components/TaskItem.tsx +++ b/src/renderer/components/TaskItem.tsx @@ -61,6 +61,10 @@ interface TaskItemProps { showDelete?: boolean; showDirectBadge?: boolean; primaryAction?: 'delete' | 'archive'; + /** 1-9 index for keyboard shortcut badge display */ + shortcutIndex?: number; + /** Which modifier to show in the badge: 'ctrl' (⌃) or 'cmd' (⌘) */ + shortcutModifier?: 'ctrl' | 'cmd'; } export const TaskItem: React.FC = ({ @@ -73,6 +77,8 @@ export const TaskItem: React.FC = ({ showDelete, showDirectBadge = true, primaryAction = 'delete', + shortcutIndex, + shortcutModifier = 'ctrl', }) => { const { totalAdditions, totalDeletions, isLoading } = useTaskChanges(task.path, task.id); const { pr } = usePrStatus(task.path); @@ -276,6 +282,12 @@ export const TaskItem: React.FC = ({ /> ) : ( <> + {shortcutIndex !== undefined && ( + + {shortcutModifier === 'cmd' ? '⌘' : '⌃'} + {shortcutIndex} + + )} {isPinned && ( = ({ const { settings } = useAppSettings(); const taskHoverAction = settings?.interface?.taskHoverAction ?? 'delete'; + const taskModifier = settings?.keyboard?.numberShortcutBehavior === 'cmd-tasks' ? 'cmd' : 'ctrl'; + + // Compute global shortcut indices for tasks across all projects (1-9) + const taskShortcutIndices = useMemo(() => { + const indices = new Map(); + let globalIndex = 0; + for (const project of sortedProjects) { + const tasks = (tasksByProjectId[project.id] ?? []) + .slice() + .sort((a, b) => (b.metadata?.isPinned ? 1 : 0) - (a.metadata?.isPinned ? 1 : 0)); + for (const task of tasks) { + if (globalIndex < 9) { + indices.set(task.id, globalIndex + 1); // 1-indexed for display + } + globalIndex++; + } + } + return indices; + }, [sortedProjects, tasksByProjectId]); const [forceOpenIds, setForceOpenIds] = useState>(new Set()); const prevTaskCountsRef = useRef>(new Map()); @@ -313,6 +332,8 @@ export const LeftSidebar: React.FC = ({ ) .map((task) => { const isActive = activeTask?.id === task.id; + // Get global shortcut index (1-9) for first 9 tasks across all projects + const shortcutIndex = taskShortcutIndices.get(task.id); return ( = ({ onDelete={() => handleDeleteTask(typedProject, task)} onArchive={() => onArchiveTask?.(typedProject, task)} primaryAction={taskHoverAction} + shortcutIndex={shortcutIndex} + shortcutModifier={taskModifier} /> ); diff --git a/src/renderer/hooks/useKeyboardShortcuts.ts b/src/renderer/hooks/useKeyboardShortcuts.ts index 8a3f22f17..899fc1daf 100644 --- a/src/renderer/hooks/useKeyboardShortcuts.ts +++ b/src/renderer/hooks/useKeyboardShortcuts.ts @@ -280,13 +280,57 @@ export function hasShortcutConflict(shortcut1: ShortcutConfig, shortcut2: Shortc ); } +/** + * Get task selection index for Ctrl/Cmd+1-9 shortcuts. + * Returns 0-8 for keys 1-9 if the task modifier is pressed, null otherwise. + */ +export function getTaskSelectionIndex( + event: Pick, + useCmdForTasks: boolean, + isMac = isMacPlatform +): number | null { + // Determine which modifier to check based on setting + let hasTaskModifier: boolean; + if (useCmdForTasks) { + // User wants Cmd/Meta for tasks + hasTaskModifier = isMac ? event.metaKey && !event.ctrlKey : event.metaKey; + } else { + // Default: Ctrl for tasks (not Meta) + hasTaskModifier = event.ctrlKey && !event.metaKey; + } + + if (!hasTaskModifier || event.altKey || event.shiftKey) { + return null; + } + + const key = normalizeShortcutKey(event.key); + if (!/^[1-9]$/.test(key)) { + return null; + } + + return Number(key) - 1; +} + +/** + * Get agent tab selection index for Cmd/Ctrl+1-9 shortcuts. + * Returns 0-8 for keys 1-9 if the agent modifier is pressed, null otherwise. + */ export function getAgentTabSelectionIndex( event: Pick, + useCtrlForAgents: boolean, isMac = isMacPlatform ): number | null { - const hasCommandModifier = - (isMac ? event.metaKey : event.metaKey || event.ctrlKey) && !event.shiftKey; - if (!hasCommandModifier || event.altKey) { + // Determine which modifier to check based on setting (inverse of tasks) + let hasAgentModifier: boolean; + if (useCtrlForAgents) { + // When tasks use Cmd, agents use Ctrl + hasAgentModifier = event.ctrlKey && !event.metaKey; + } else { + // Default: agents use Cmd/Meta (or Ctrl on non-Mac when Meta not available) + hasAgentModifier = isMac ? event.metaKey && !event.ctrlKey : event.metaKey || event.ctrlKey; + } + + if (!hasAgentModifier || event.altKey || event.shiftKey) { return null; } @@ -557,7 +601,27 @@ export function useKeyboardShortcuts(handlers: GlobalShortcutHandlers) { } } - const agentTabIndex = getAgentTabSelectionIndex(event); + // Handle number key shortcuts (1-9) for task/agent selection + const useCmdForTasks = + handlers.customKeyboardSettings?.numberShortcutBehavior === 'cmd-tasks'; + + // Check for task selection first (Ctrl+1-9 by default, or Cmd+1-9 if configured) + const taskIndex = getTaskSelectionIndex(event, useCmdForTasks); + if (taskIndex !== null) { + const isCommandPaletteOpen = Boolean(handlers.isCommandPaletteOpen); + if (isCommandPaletteOpen) { + event.preventDefault(); + handlers.onCloseModal?.(); + setTimeout(() => handlers.onSelectTask?.(taskIndex), 100); + return; + } + event.preventDefault(); + handlers.onSelectTask?.(taskIndex); + return; + } + + // Check for agent tab selection (Cmd+1-9 by default, or Ctrl+1-9 if tasks use Cmd) + const agentTabIndex = getAgentTabSelectionIndex(event, useCmdForTasks); if (agentTabIndex === null) { return; } diff --git a/src/renderer/hooks/useTaskManagement.ts b/src/renderer/hooks/useTaskManagement.ts index ba0287125..ebfc7a67f 100644 --- a/src/renderer/hooks/useTaskManagement.ts +++ b/src/renderer/hooks/useTaskManagement.ts @@ -389,6 +389,24 @@ export function useTaskManagement() { [activateProjectView, projects, openTaskModal] ); + const handleSelectTaskByIndex = useCallback( + (index: number) => { + // Build a flat list of all tasks in sidebar order (by project, then pinned first within each) + const allTasksInOrder: Task[] = []; + for (const project of projects) { + const projectTasks = (tasksByProjectId[project.id] ?? []) + .slice() + .sort((a, b) => (b.metadata?.isPinned ? 1 : 0) - (a.metadata?.isPinned ? 1 : 0)); + allTasksInOrder.push(...projectTasks); + } + + if (index >= 0 && index < allTasksInOrder.length) { + handleSelectTask(allTasksInOrder[index]); + } + }, + [projects, tasksByProjectId, handleSelectTask] + ); + // --------------------------------------------------------------------------- // Delete task mutation // --------------------------------------------------------------------------- @@ -1005,6 +1023,7 @@ export function useTaskManagement() { handleTaskInterfaceReady, openTaskModal, handleSelectTask, + handleSelectTaskByIndex, handleNextTask, handlePrevTask, handleNewTask, diff --git a/src/renderer/types/shortcuts.ts b/src/renderer/types/shortcuts.ts index 2fb51bcaa..b1fa949a4 100644 --- a/src/renderer/types/shortcuts.ts +++ b/src/renderer/types/shortcuts.ts @@ -7,6 +7,8 @@ export type ShortcutModifier = | 'cmd+shift' | 'ctrl+shift'; +export type NumberShortcutBehavior = 'ctrl-tasks' | 'cmd-tasks'; + export interface ShortcutBinding { key: string; modifier: ShortcutModifier; @@ -27,6 +29,7 @@ export interface KeyboardSettings { nextAgent?: ShortcutBinding; prevAgent?: ShortcutBinding; openInEditor?: ShortcutBinding; + numberShortcutBehavior?: NumberShortcutBehavior; } export interface ShortcutConfig { @@ -92,6 +95,9 @@ export interface GlobalShortcutHandlers { onPrevAgent?: () => void; onSelectAgentTab?: (tabIndex: number) => void; + // Task selection by number (Ctrl+1-9) + onSelectTask?: (index: number) => void; + // Open in editor onOpenInEditor?: () => void; diff --git a/src/test/renderer/useKeyboardShortcuts.test.ts b/src/test/renderer/useKeyboardShortcuts.test.ts index a518ff08f..220ac96c2 100644 --- a/src/test/renderer/useKeyboardShortcuts.test.ts +++ b/src/test/renderer/useKeyboardShortcuts.test.ts @@ -1,26 +1,35 @@ import { describe, expect, it } from 'vitest'; -import { getAgentTabSelectionIndex } from '../../renderer/hooks/useKeyboardShortcuts'; +import { + getAgentTabSelectionIndex, + getTaskSelectionIndex, +} from '../../renderer/hooks/useKeyboardShortcuts'; describe('getAgentTabSelectionIndex', () => { - it('maps Cmd/Ctrl+1 through Cmd/Ctrl+9 to zero-based tab indexes', () => { - expect( - getAgentTabSelectionIndex({ - key: '1', - metaKey: true, - ctrlKey: false, - altKey: false, - shiftKey: false, - } as KeyboardEvent) + it('maps Cmd/Ctrl+1 through Cmd/Ctrl+9 to zero-based tab indexes (default: agents use Cmd)', () => { + expect( + getAgentTabSelectionIndex( + { + key: '1', + metaKey: true, + ctrlKey: false, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false // useCtrlForAgents = false (default: Cmd for agents) + ) ).toBe(0); expect( - getAgentTabSelectionIndex({ - key: '9', - metaKey: true, - ctrlKey: false, - altKey: false, - shiftKey: false, - } as KeyboardEvent) + getAgentTabSelectionIndex( + { + key: '9', + metaKey: true, + ctrlKey: false, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false + ) ).toBe(8); }); @@ -34,50 +43,208 @@ describe('getAgentTabSelectionIndex', () => { altKey: false, shiftKey: false, } as KeyboardEvent, - false + false, // useCtrlForAgents + false // isMac ) ).toBe(3); }); it('ignores keys outside 1-9 and modified variants', () => { expect( - getAgentTabSelectionIndex({ - key: '0', - metaKey: true, - ctrlKey: false, - altKey: false, - shiftKey: false, - } as KeyboardEvent) + getAgentTabSelectionIndex( + { + key: '0', + metaKey: true, + ctrlKey: false, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false + ) + ).toBeNull(); + + expect( + getAgentTabSelectionIndex( + { + key: '1', + metaKey: true, + ctrlKey: false, + altKey: false, + shiftKey: true, + } as KeyboardEvent, + false + ) + ).toBeNull(); + + expect( + getAgentTabSelectionIndex( + { + key: '1', + metaKey: true, + ctrlKey: false, + altKey: true, + shiftKey: false, + } as KeyboardEvent, + false + ) + ).toBeNull(); + + expect( + getAgentTabSelectionIndex( + { + key: '1', + metaKey: false, + ctrlKey: false, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false + ) + ).toBeNull(); + }); + + it('uses Ctrl for agents when useCtrlForAgents is true', () => { + // When tasks use Cmd, agents use Ctrl + expect( + getAgentTabSelectionIndex( + { + key: '1', + metaKey: false, + ctrlKey: true, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + true // useCtrlForAgents = true + ) + ).toBe(0); + + // Cmd should NOT work when agents are set to use Ctrl + expect( + getAgentTabSelectionIndex( + { + key: '1', + metaKey: true, + ctrlKey: false, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + true // useCtrlForAgents = true + ) + ).toBeNull(); + }); +}); + +describe('getTaskSelectionIndex', () => { + it('maps Ctrl+1 through Ctrl+9 to zero-based task indexes (default: tasks use Ctrl)', () => { + expect( + getTaskSelectionIndex( + { + key: '1', + metaKey: false, + ctrlKey: true, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false // useCmdForTasks = false (default: Ctrl for tasks) + ) + ).toBe(0); + + expect( + getTaskSelectionIndex( + { + key: '9', + metaKey: false, + ctrlKey: true, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false + ) + ).toBe(8); + }); + + it('uses Cmd for tasks when useCmdForTasks is true', () => { + // When tasks use Cmd + expect( + getTaskSelectionIndex( + { + key: '1', + metaKey: true, + ctrlKey: false, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + true, // useCmdForTasks = true + true // isMac + ) + ).toBe(0); + + // Ctrl should NOT work when tasks are set to use Cmd + expect( + getTaskSelectionIndex( + { + key: '1', + metaKey: false, + ctrlKey: true, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + true // useCmdForTasks = true + ) + ).toBeNull(); + }); + + it('ignores keys outside 1-9 and modified variants', () => { + expect( + getTaskSelectionIndex( + { + key: '0', + metaKey: false, + ctrlKey: true, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false + ) ).toBeNull(); expect( - getAgentTabSelectionIndex({ - key: '1', - metaKey: true, - ctrlKey: false, - altKey: false, - shiftKey: true, - } as KeyboardEvent) + getTaskSelectionIndex( + { + key: '1', + metaKey: false, + ctrlKey: true, + altKey: false, + shiftKey: true, + } as KeyboardEvent, + false + ) ).toBeNull(); expect( - getAgentTabSelectionIndex({ - key: '1', - metaKey: true, - ctrlKey: false, - altKey: true, - shiftKey: false, - } as KeyboardEvent) + getTaskSelectionIndex( + { + key: '1', + metaKey: false, + ctrlKey: true, + altKey: true, + shiftKey: false, + } as KeyboardEvent, + false + ) ).toBeNull(); expect( - getAgentTabSelectionIndex({ - key: '1', - metaKey: false, - ctrlKey: false, - altKey: false, - shiftKey: false, - } as KeyboardEvent) + getTaskSelectionIndex( + { + key: '1', + metaKey: false, + ctrlKey: false, + altKey: false, + shiftKey: false, + } as KeyboardEvent, + false + ) ).toBeNull(); }); }); From e20b80c23d6b648c8e7cb0bc80c5c1cd3b5b02c2 Mon Sep 17 00:00:00 2001 From: Dan Rocha Date: Tue, 10 Mar 2026 21:10:27 -0600 Subject: [PATCH 2/2] fix: address PR review feedback - Add isEditableTarget guard before number-key shortcuts to prevent task switching while typing in input fields - Use same project ordering (from localStorage) in handleSelectTaskByIndex as LeftSidebar uses for badge display, fixing mismatch when projects are reordered --- src/renderer/hooks/useKeyboardShortcuts.ts | 3 +++ src/renderer/hooks/useTaskManagement.ts | 23 +++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/renderer/hooks/useKeyboardShortcuts.ts b/src/renderer/hooks/useKeyboardShortcuts.ts index 899fc1daf..9f0b4f3f9 100644 --- a/src/renderer/hooks/useKeyboardShortcuts.ts +++ b/src/renderer/hooks/useKeyboardShortcuts.ts @@ -601,6 +601,9 @@ export function useKeyboardShortcuts(handlers: GlobalShortcutHandlers) { } } + // Skip number shortcuts when typing in editable fields + if (isEditableTarget) return; + // Handle number key shortcuts (1-9) for task/agent selection const useCmdForTasks = handlers.customKeyboardSettings?.numberShortcutBehavior === 'cmd-tasks'; diff --git a/src/renderer/hooks/useTaskManagement.ts b/src/renderer/hooks/useTaskManagement.ts index ebfc7a67f..6339ce905 100644 --- a/src/renderer/hooks/useTaskManagement.ts +++ b/src/renderer/hooks/useTaskManagement.ts @@ -391,9 +391,30 @@ export function useTaskManagement() { const handleSelectTaskByIndex = useCallback( (index: number) => { + // Sort projects using the same order as LeftSidebar (from localStorage) + let sortedProjects = projects; + try { + const stored = localStorage.getItem('sidebarProjectOrder'); + if (stored) { + const projectOrder: string[] = JSON.parse(stored); + if (projectOrder.length > 0) { + sortedProjects = [...projects].sort((a, b) => { + const ai = projectOrder.indexOf(a.id); + const bi = projectOrder.indexOf(b.id); + if (ai === -1 && bi === -1) return 0; + if (ai === -1) return -1; + if (bi === -1) return 1; + return ai - bi; + }); + } + } + } catch { + // Use default order if localStorage read fails + } + // Build a flat list of all tasks in sidebar order (by project, then pinned first within each) const allTasksInOrder: Task[] = []; - for (const project of projects) { + for (const project of sortedProjects) { const projectTasks = (tasksByProjectId[project.id] ?? []) .slice() .sort((a, b) => (b.metadata?.isPinned ? 1 : 0) - (a.metadata?.isPinned ? 1 : 0));