From 4caea4c8fb7abaa18ebf2652839940aa8e3c7652 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Sat, 12 Sep 2026 23:41:06 +0300 Subject: [PATCH] =?UTF-8?q?Wave-5=20reskin=20batch=202:=20the=20session=20?= =?UTF-8?q?sidebar=20=E2=80=94=20LH=20wordmark,=20New=20session=20+=20Sear?= =?UTF-8?q?ch=20rows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 'Learning Harness' wordmark → 'LH' (the owner's header in the mockup; flagged as a copy change — it's visually the same brand mark). - New session: + NEW SESSION row wired with one POST /api/chats then navigate (the mock drew it as visual-only; POST already existed). - Search: a SESSIONS label + inline input filtering the polled snapshot client-side (title substring, no server round trip). - Skin: sidebar stays bg-background with the edge #434343 border-r; the selected row now carries the rounding + borders from the raised surface #1C1C1C via accent tokens. --- components/sidebar.tsx | 65 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 8eeb6c0..072e119 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -1,10 +1,11 @@ "use client"; -import { createContext, useCallback, useContext, useEffect, useState } from "react"; +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; -import { PanelLeftCloseIcon, PanelLeftOpenIcon } from "lucide-react"; +import { PanelLeftCloseIcon, PanelLeftOpenIcon, PlusIcon, SearchIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import type { ChatListItem, Phase3 } from "@/src/core"; @@ -181,9 +182,26 @@ export function SessionRow({ function SessionsSidebar() { const { open, toggle } = useSidebar(); const [chats, setChats] = useState(null); + const [query, setQuery] = useState(""); const pathname = usePathname(); const router = useRouter(); + /* New session: one POST, then hello. Search: a client-side filter over + the poll snapshot — no server round trip. Both were visual-only in + the wave-5 mock; wiring each is a single tiny handler. */ + const newSession = useCallback(async () => { + const res = await fetch("/api/chats", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }); + const d = (await res.json().catch(() => null)) as { id?: string } | null; + if (d?.id) router.push(`/chat/${d.id}`); + }, [router]); + + const shown = useMemo(() => { + const q = query.trim().toLowerCase(); + if (!chats) return chats; + if (!q) return chats; + return chats.filter((c) => c.title.toLowerCase().includes(q)); + }, [chats, query]); + const refresh = useCallback(async () => { try { const res = await fetch("/api/chats", { cache: "no-store" }); @@ -213,22 +231,51 @@ function SessionsSidebar() { aria-hidden={!open} >
+ {/* LH wordmark — the owner's header; close toggle sits at the edge. */}
- - Learning Harness + + LH
+ + +
+ Sessions + + setQuery(e.target.value)} + placeholder="Search..." + aria-label="Search sessions" + className="h-6 rounded-sm border-border bg-background pl-6 text-xs placeholder:text-muted-foreground/60" + /> + + +
+
- {chats === null ? ( -
Loading…
- ) : chats.length === 0 ? ( -
No sessions yet.
+ {shown === null ? ( +
Loading…
+ ) : shown.length === 0 ? ( +
+ {query.trim() ? "No sessions match." : "No sessions yet."} +
) : ( - chats.map((c) => ( + shown.map((c) => (