feat(#67): implement search UI with filters and shortcuts

Implements comprehensive search interface for knowledge base:

Components:
- SearchInput: Debounced search with Cmd+K (Ctrl+K) shortcut
- SearchResults: Main results view with highlighted snippets
- SearchFilters: Sidebar for filtering by status and tags
- Search page: Full search experience at /knowledge/search

Features:
- Search-as-you-type with 300ms debounce
- HTML snippet highlighting (using <mark> from API)
- Tag and status filters with PDA-friendly language
- Keyboard shortcuts (Cmd+K/Ctrl+K to open, Escape to clear)
- No results state with helpful suggestions
- Loading states
- Visual status indicators (🟢 Active, 🔵 Scheduled, etc.)

Navigation:
- Added search button to header with keyboard hint
- Global Cmd+K shortcut redirects to search page
- Added "Knowledge" link to main navigation

Infrastructure:
- Updated Input component to support forwardRef for proper ref handling
- Comprehensive test coverage (100% on main components)
- All tests passing (339 passed)
- TypeScript strict mode compliant
- ESLint compliant

Fixes #67

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-02 14:50:25 -06:00
parent c3500783d1
commit 3cb6eb7f8b
12 changed files with 1221 additions and 11 deletions

View File

@@ -1,20 +1,38 @@
"use client";
import { usePathname } from "next/navigation";
import { usePathname, useRouter } from "next/navigation";
import Link from "next/link";
import { useAuth } from "@/lib/auth/auth-context";
import { LogoutButton } from "@/components/auth/LogoutButton";
import { useEffect } from "react";
export function Navigation(): React.JSX.Element {
const pathname = usePathname();
const router = useRouter();
const { user } = useAuth();
const navItems = [
{ href: "/", label: "Dashboard" },
{ href: "/tasks", label: "Tasks" },
{ href: "/calendar", label: "Calendar" },
{ href: "/knowledge", label: "Knowledge" },
];
// Global keyboard shortcut for search (Cmd+K or Ctrl+K)
useEffect((): (() => void) => {
const handleKeyDown = (e: KeyboardEvent): void => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
router.push("/knowledge/search");
}
};
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [router]);
return (
<nav className="fixed top-0 left-0 right-0 bg-white border-b border-gray-200 z-50">
<div className="container mx-auto px-4">
@@ -41,6 +59,17 @@ export function Navigation(): React.JSX.Element {
</div>
</div>
<div className="flex items-center gap-4">
<Link
href="/knowledge/search"
className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
title="Search knowledge base (Cmd+K)"
>
<span>🔍</span>
<span>Search</span>
<kbd className="hidden sm:inline-block px-2 py-0.5 text-xs font-semibold text-gray-600 bg-gray-100 border border-gray-200 rounded">
K
</kbd>
</Link>
{user && <div className="text-sm text-gray-600">{user.name || user.email}</div>}
<LogoutButton variant="secondary" />
</div>