diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 63cc728..de35c30 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -10,6 +10,7 @@ import { TaskViewsPanel } from './components/TaskViewsPanel'; import { TaskDetail } from './components/TaskDetail'; import { ResourceTasksPanel } from './components/ResourceTasksPanel'; import { CommandPalette } from './components/CommandPalette'; +import { QuickAddModal } from './components/QuickAddModal'; import { MobileGate } from './components/MobileGate'; import { useProjects } from './hooks/useProjects'; import { useTasks } from './hooks/useTasks'; @@ -105,6 +106,7 @@ function AppShell() { const [viewPreset, setViewPreset] = usePersistedState('baton.board.viewPreset', 'all'); const [releaseTarget, setReleaseTarget] = usePersistedState('baton.board.releaseTarget', ''); const [paletteOpen, setPaletteOpen] = useState(false); + const [quickAddOpen, setQuickAddOpen] = useState(false); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [showArchived, setShowArchived] = useState(false); const [registryOpen, setRegistryOpen] = useState(false); @@ -207,6 +209,27 @@ function AppShell() { return () => window.removeEventListener('keydown', handler); }, []); + // Global T hotkey to open Quick Add modal. + React.useEffect(() => { + const handler = (e: KeyboardEvent) => { + const target = e.target as HTMLElement | null; + const isInput = target && ( + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.tagName === 'SELECT' || + target.isContentEditable + ); + if (isInput) return; + + if ((e.key === 't' || e.key === 'T') && !e.metaKey && !e.ctrlKey && !e.altKey) { + e.preventDefault(); + setQuickAddOpen(true); + } + }; + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, []); + React.useEffect(() => { const match = window.location.pathname.match(/^\/(tasks|users|agents)\/([^/]+)$/); if (!match) return; @@ -278,6 +301,7 @@ function AppShell() { onOpenRegistry={() => setRegistryOpen(true)} onOpenViews={() => setViewsOpen(true)} onOpenPalette={() => setPaletteOpen(true)} + onOpenQuickAdd={() => setQuickAddOpen(true)} onOpenSidebar={() => setMobileSidebarOpen(true)} density={density} onToggleDensity={() => setDensity(d => d === 'comfortable' ? 'compact' : 'comfortable')} @@ -483,6 +507,21 @@ function AppShell() { onClose={() => setPaletteOpen(false)} /> )} + setQuickAddOpen(false)} + onSubmit={async (data) => { + if (data.projectId !== selectedProjectId) { + return await api.createTask(data); + } + return await createTask(data); + }} + onOpenDetail={(task) => setLinkedTask(task)} + /> ); } diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 27e41fd..fceb23a 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -17,6 +17,7 @@ interface Props { onOpenRegistry: () => void; onOpenViews: () => void; onOpenPalette: () => void; + onOpenQuickAdd?: () => void; onOpenSidebar: () => void; density: 'comfortable' | 'compact'; onToggleDensity: () => void; @@ -41,6 +42,7 @@ export function Header({ onOpenRegistry, onOpenViews, onOpenPalette, + onOpenQuickAdd, onOpenSidebar, density, onToggleDensity, @@ -206,6 +208,19 @@ export function Header({ /> + {onOpenQuickAdd && ( + + )} + + + + {/* Main Body */} +
+ {/* Title Input with Smart Syntax Helper */} +
+ setRawTitle(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="What needs to be done? (Use @5pt or !high for quick formatting)..." + className="w-full bg-[#141414] border border-[#2a2a2a] focus:border-amber-500/50 rounded-lg px-3 py-2.5 text-sm text-zinc-100 placeholder-zinc-600 outline-none transition-colors" + /> +
+ Inline shortcuts: @3pt estimate, !high priority + Enter to save • Shift+Enter save & open +
+
+ +
+ +