From 6924de3e088142260d8ef89547dbe7d7f5d42eba Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Sat, 11 Apr 2026 16:07:27 +0000 Subject: [PATCH] refactor(nexus): drop dangling chat surfaces and settings whitelist entries (phase 16b) - Delete ChatSearchDialog and ChatBookmarkList (orphaned after ChatPanel deletion in Phase 16a; grep confirmed zero consumers). - Remove /instance/settings/heartbeats and /instance/settings/ experimental from the normalizeRememberedInstanceSettingsPath whitelist; Phase 13 collapsed those pages into /general but left the whitelist entries behind. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/components/ChatBookmarkList.tsx | 64 ------------ ui/src/components/ChatSearchDialog.tsx | 138 ------------------------- ui/src/lib/instance-settings.test.ts | 11 +- ui/src/lib/instance-settings.ts | 4 +- 4 files changed, 9 insertions(+), 208 deletions(-) delete mode 100644 ui/src/components/ChatBookmarkList.tsx delete mode 100644 ui/src/components/ChatSearchDialog.tsx diff --git a/ui/src/components/ChatBookmarkList.tsx b/ui/src/components/ChatBookmarkList.tsx deleted file mode 100644 index 9c18b459..00000000 --- a/ui/src/components/ChatBookmarkList.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Bookmark } from "lucide-react"; -import { useChatBookmarks } from "../hooks/useChatBookmarks"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Skeleton } from "@/components/ui/skeleton"; - -interface ChatBookmarkListProps { - companyId: string; - onNavigate: (conversationId: string, messageId: string) => void; -} - -function formatRelativeTime(dateStr: string): string { - const date = new Date(dateStr); - const now = new Date(); - const diffMs = now.getTime() - date.getTime(); - const diffMin = Math.floor(diffMs / 60000); - if (diffMin < 1) return "just now"; - if (diffMin < 60) return `${diffMin}m ago`; - const diffHr = Math.floor(diffMin / 60); - if (diffHr < 24) return `${diffHr}h ago`; - const diffDays = Math.floor(diffHr / 24); - if (diffDays < 30) return `${diffDays}d ago`; - return date.toLocaleDateString(); -} - -export function ChatBookmarkList({ companyId, onNavigate }: ChatBookmarkListProps) { - const { data, isLoading } = useChatBookmarks(companyId); - const bookmarks = data?.items ?? []; - - return ( - -
- {isLoading ? ( - Array.from({ length: 4 }).map((_, i) => ( - - )) - ) : bookmarks.length === 0 ? ( -
- -

No bookmarks yet

-
- ) : ( - bookmarks.map((bookmark) => ( - - )) - )} -
-
- ); -} diff --git a/ui/src/components/ChatSearchDialog.tsx b/ui/src/components/ChatSearchDialog.tsx deleted file mode 100644 index 8eec4514..00000000 --- a/ui/src/components/ChatSearchDialog.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import { useState } from "react"; -import { Search } from "lucide-react"; -import { useChatSearch } from "../hooks/useChatSearch"; -import { - CommandDialog, - CommandEmpty, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { Command } from "cmdk"; - -interface ChatSearchDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - companyId: string | null; - onNavigate: (conversationId: string, messageId: string) => void; -} - -function stripMarkdown(text: string): string { - return text - .replace(/```[\s\S]*?```/g, "") - .replace(/`[^`]+`/g, "") - .replace(/\*\*([^*]+)\*\*/g, "$1") - .replace(/\*([^*]+)\*/g, "$1") - .replace(/#{1,6}\s/g, "") - .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") - .replace(/>\s/g, "") - .trim(); -} - -/** Split text into segments, marking portions that match the query terms */ -function splitWithHighlight(text: string, query: string): Array<{ text: string; highlight: boolean }> { - if (!query.trim()) return [{ text, highlight: false }]; - const terms = query.trim().split(/\s+/).filter(Boolean); - const pattern = terms.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"); - const re = new RegExp(`(${pattern})`, "gi"); - const parts = text.split(re); - return parts.map((part) => ({ - text: part, - highlight: re.test(part), - })); -} - -function HighlightedText({ text, query }: { text: string; query: string }) { - const segments = splitWithHighlight(text, query); - return ( - <> - {segments.map((seg, i) => - seg.highlight ? ( - - {seg.text} - - ) : ( - {seg.text} - ), - )} - - ); -} - -function formatRelativeTime(dateStr: string): string { - const date = new Date(dateStr); - const now = new Date(); - const diffMs = now.getTime() - date.getTime(); - const diffMin = Math.floor(diffMs / 60000); - if (diffMin < 1) return "just now"; - if (diffMin < 60) return `${diffMin}m ago`; - const diffHr = Math.floor(diffMin / 60); - if (diffHr < 24) return `${diffHr}h ago`; - const diffDays = Math.floor(diffHr / 24); - if (diffDays < 30) return `${diffDays}d ago`; - return date.toLocaleDateString(); -} - -export function ChatSearchDialog({ open, onOpenChange, companyId, onNavigate }: ChatSearchDialogProps) { - const [query, setQuery] = useState(""); - const { data } = useChatSearch(companyId, query); - const results = data?.items ?? []; - - function handleSelect(conversationId: string, messageId: string) { - onNavigate(conversationId, messageId); - onOpenChange(false); - setQuery(""); - } - - return ( - { - onOpenChange(v); - if (!v) setQuery(""); - }} - title="Search messages" - description="Search all messages across conversations" - > - - - - {query.trim().length >= 2 && results.length === 0 && ( - No results found. - )} - {results.map((result) => { - const snippet = stripMarkdown(result.content).slice(0, 120); - return ( - handleSelect(result.conversationId, result.messageId)} - className="flex flex-col items-start gap-0.5 py-2" - > -
- - - {result.conversationTitle ?? "Untitled conversation"} - - - {result.role} - - - {formatRelativeTime(result.createdAt)} - -
-

- -

-
- ); - })} -
-
-
- ); -} diff --git a/ui/src/lib/instance-settings.test.ts b/ui/src/lib/instance-settings.test.ts index f81e4a1e..69e9eee5 100644 --- a/ui/src/lib/instance-settings.test.ts +++ b/ui/src/lib/instance-settings.test.ts @@ -9,9 +9,6 @@ describe("normalizeRememberedInstanceSettingsPath", () => { expect(normalizeRememberedInstanceSettingsPath("/instance/settings/general")).toBe( "/instance/settings/general", ); - expect(normalizeRememberedInstanceSettingsPath("/instance/settings/experimental")).toBe( - "/instance/settings/experimental", - ); expect(normalizeRememberedInstanceSettingsPath("/instance/settings/plugins/example?tab=config#logs")).toBe( "/instance/settings/plugins/example?tab=config#logs", ); @@ -21,6 +18,14 @@ describe("normalizeRememberedInstanceSettingsPath", () => { expect(normalizeRememberedInstanceSettingsPath("/instance/settings/nope")).toBe( DEFAULT_INSTANCE_SETTINGS_PATH, ); + // Phase 16b: /heartbeats and /experimental no longer exist as + // separate settings pages — they collapse into /general. + expect(normalizeRememberedInstanceSettingsPath("/instance/settings/heartbeats")).toBe( + DEFAULT_INSTANCE_SETTINGS_PATH, + ); + expect(normalizeRememberedInstanceSettingsPath("/instance/settings/experimental")).toBe( + DEFAULT_INSTANCE_SETTINGS_PATH, + ); expect(normalizeRememberedInstanceSettingsPath(null)).toBe(DEFAULT_INSTANCE_SETTINGS_PATH); }); }); diff --git a/ui/src/lib/instance-settings.ts b/ui/src/lib/instance-settings.ts index c231791c..7cb1ec54 100644 --- a/ui/src/lib/instance-settings.ts +++ b/ui/src/lib/instance-settings.ts @@ -10,9 +10,7 @@ export function normalizeRememberedInstanceSettingsPath(rawPath: string | null): if ( pathname === "/instance/settings/general" || - pathname === "/instance/settings/heartbeats" || - pathname === "/instance/settings/plugins" || - pathname === "/instance/settings/experimental" + pathname === "/instance/settings/plugins" ) { return `${pathname}${search}${hash}`; }