-
-
{t("chat.emptyTitle")}
+
+
+
+
{t("chat.emptyTitle")}
{t("chat.emptyHint")}
diff --git a/components/chat/SidebarChatSection.tsx b/components/chat/SidebarChatSection.tsx
new file mode 100644
index 0000000..2c9aa50
--- /dev/null
+++ b/components/chat/SidebarChatSection.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { useState, useSyncExternalStore } from "react";
+
+import { ChatHistoryList } from "./HistoryPanel";
+import { chatActive } from "@/lib/chat-active";
+import { chatHistoryStore, deleteConversation, renameConversation, saveConversation } from "@/lib/chat-store";
+import { useI18n } from "@/lib/i18n";
+import { showToast } from "@/lib/toast";
+import type { ChatConversation } from "@/lib/schemas";
+
+/**
+ * Chat history inside the main sidebar: when the Chat section is active the
+ * deck navigation is replaced by the chat list (search, new chat, items with
+ * rename/delete). Collapsed, it keeps one New-chat icon row.
+ */
+export function SidebarChatSection({ collapsed }: { collapsed: boolean }) {
+ const { t } = useI18n();
+ const router = useRouter();
+ const conversations = useSyncExternalStore(
+ chatHistoryStore.subscribe,
+ chatHistoryStore.getSnapshot,
+ chatHistoryStore.getServerSnapshot,
+ );
+ const activeId = useSyncExternalStore(chatActive.subscribe, chatActive.getSnapshot, chatActive.getServerSnapshot);
+ const [query, setQuery] = useState("");
+
+ const onDelete = (conversation: ChatConversation) => {
+ deleteConversation(conversation.id);
+ showToast({
+ message: t("chat.deletedChat"),
+ actionLabel: t("toast.undo"),
+ onAction: () => saveConversation(conversation),
+ });
+ };
+
+ // Collapsed, the list needs width — the highlighted Chat row above (and the
+ // expand chevron) already cover navigation, so the section renders nothing.
+ if (collapsed) return null;
+
+ return (
+
+
+ {t("chat.history")}
+
+ router.push(`/chat?c=${id}`)}
+ onRename={renameConversation}
+ onDelete={onDelete}
+ onNew={() => router.push("/chat")}
+ />
+
+ );
+}
diff --git a/components/icons.tsx b/components/icons.tsx
index ca12837..c0e512b 100644
--- a/components/icons.tsx
+++ b/components/icons.tsx
@@ -321,8 +321,7 @@ export function AddToHomeIcon({ className }: IconProps) {
export function ChatIcon({ className }: IconProps) {
return (
);
}
diff --git a/lib/chat-active.ts b/lib/chat-active.ts
new file mode 100644
index 0000000..7245e6f
--- /dev/null
+++ b/lib/chat-active.ts
@@ -0,0 +1,29 @@
+"use client";
+
+/**
+ * Which conversation the chat screen is currently showing, for surfaces
+ * outside the chat route (the sidebar list highlights it). A tiny external
+ * store instead of reading `?c=` from the URL, because the sidebar renders on
+ * every page and `useSearchParams` there would force pages dynamic.
+ */
+
+let active: string | null = null;
+const listeners = new Set<() => void>();
+
+export const chatActive = {
+ set(id: string | null): void {
+ if (active === id) return;
+ active = id;
+ listeners.forEach((listener) => listener());
+ },
+ subscribe(listener: () => void): () => void {
+ listeners.add(listener);
+ return () => listeners.delete(listener);
+ },
+ getSnapshot(): string | null {
+ return active;
+ },
+ getServerSnapshot(): string | null {
+ return null;
+ },
+};
diff --git a/public/sw.js b/public/sw.js
index 18e4b37..0fda80a 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -9,7 +9,7 @@
*
* Bumping CACHE_VERSION is how a new deploy invalidates old assets.
*/
-const CACHE_VERSION = "freezmo-v34";
+const CACHE_VERSION = "freezmo-v35";
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
const ASSET_CACHE = `${CACHE_VERSION}-assets`;