diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 8991d0c..3c1e745 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -759,6 +759,7 @@ pub fn run() { acp::commands::acp_send_prompt, acp::commands::acp_set_mode, acp::commands::acp_cancel, + sessions::count_workspace_sessions, sessions::session_list, sessions::session_create, sessions::session_get, diff --git a/apps/tauri/src-tauri/src/sessions.rs b/apps/tauri/src-tauri/src/sessions.rs index 751cc55..bae13a8 100644 --- a/apps/tauri/src-tauri/src/sessions.rs +++ b/apps/tauri/src-tauri/src/sessions.rs @@ -174,11 +174,47 @@ pub fn delete_session(conn: &Connection, id: &str) -> Result<(), String> { Ok(()) } +pub fn count_sessions_batch(conn: &Connection, workspace_paths: &[String]) -> Result, String> { + if workspace_paths.is_empty() { + return Ok(Vec::new()); + } + + const MAX_VARS: usize = 999; + let mut results: Vec<(String, i64)> = Vec::new(); + + for chunk in workspace_paths.chunks(MAX_VARS) { + let placeholders: Vec = (1..=chunk.len()).map(|i| format!("?{}", i)).collect(); + let sql = format!( + "SELECT workspace_path, COUNT(*) FROM sessions WHERE workspace_path IN ({}) GROUP BY workspace_path HAVING COUNT(*) > 0", + placeholders.join(", ") + ); + let mut stmt = conn.prepare(&sql).map_err(|e| format!("Prepare error: {}", e))?; + let params: Vec<&dyn rusqlite::ToSql> = chunk.iter().map(|p| p as &dyn rusqlite::ToSql).collect(); + let rows = stmt + .query_map(params.as_slice(), |row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))) + .map_err(|e| format!("Query error: {}", e))? + .collect::, _>>() + .map_err(|e| format!("Row error: {}", e))?; + results.extend(rows); + } + + Ok(results) +} + // --- Tauri commands --- use crate::comments::CommentsDb; use tauri::Manager; +#[tauri::command] +pub fn count_workspace_sessions( + workspace_paths: Vec, + db: tauri::State, +) -> Result, String> { + let conn = db.0.lock().map_err(|e| e.to_string())?; + count_sessions_batch(&conn, &workspace_paths) +} + #[tauri::command] pub fn session_list( workspace_path: String, diff --git a/apps/tauri/src-tauri/tauri.conf.json b/apps/tauri/src-tauri/tauri.conf.json index 25aa772..461b44b 100644 --- a/apps/tauri/src-tauri/tauri.conf.json +++ b/apps/tauri/src-tauri/tauri.conf.json @@ -39,7 +39,7 @@ }, { "label": "settings", - "title": "Settings", + "title": "", "url": "settings.html", "width": 600, "height": 520, diff --git a/apps/tauri/src/SettingsApp.tsx b/apps/tauri/src/SettingsApp.tsx index ab58044..4b19e30 100644 --- a/apps/tauri/src/SettingsApp.tsx +++ b/apps/tauri/src/SettingsApp.tsx @@ -20,8 +20,6 @@ export function SettingsApp() { const currentWindow = getCurrentWindow(); if (currentWindow.label !== "settings") return; - currentWindow.setTitle(t("settings.title")).catch(console.error); - const handleClose = currentWindow.onCloseRequested(async (event) => { event.preventDefault(); await currentWindow.hide(); @@ -48,7 +46,7 @@ export function SettingsApp() {

{t("settings.title")}

- + {t("settings.whisper")} diff --git a/apps/tauri/src/components/HomeScreen.tsx b/apps/tauri/src/components/HomeScreen.tsx index c0f28e9..077d616 100644 --- a/apps/tauri/src/components/HomeScreen.tsx +++ b/apps/tauri/src/components/HomeScreen.tsx @@ -2,7 +2,7 @@ import logoSvg from "@/assets/logo.svg"; import { Button } from "@/components/ui/button"; import { useApp } from "@/contexts/AppContext"; import { FileText, FolderOpen } from "lucide-react"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { WorkspaceCard } from "./WorkspaceCard"; @@ -12,6 +12,7 @@ export function HomeScreen () { const { t } = useTranslation(); const { workspaces, openFile, openDirectory, expandWorkspace, closeWorkspace } = useApp(); const [commentCounts, setCommentCounts] = useState>({}); + const [sessionCounts, setSessionCounts] = useState>({}); const byRecent = (a: { lastAccessed: Date }, b: { lastAccessed: Date }) => new Date(b.lastAccessed).getTime() - new Date(a.lastAccessed).getTime(); @@ -19,11 +20,13 @@ export function HomeScreen () { const fileWorkspaces = workspaces.filter((w) => w.type === "file").sort(byRecent); const dirWorkspaces = workspaces.filter((w) => w.type === "directory").sort(byRecent); + const filePaths = useMemo(() => fileWorkspaces.map((w) => w.path), [fileWorkspaces]); + const dirPaths = useMemo(() => dirWorkspaces.map((w) => w.path), [dirWorkspaces]); + const fetchCommentCounts = useCallback(async () => { - if (fileWorkspaces.length === 0) return; - const paths = fileWorkspaces.map((w) => w.path); + if (filePaths.length === 0) return; try { - const results = await invoke<[string, number][]>("count_unresolved_comments", { filePaths: paths }); + const results = await invoke<[string, number][]>("count_unresolved_comments", { filePaths }); const counts: Record = {}; for (const [path, count] of results) { counts[path] = count; @@ -32,12 +35,30 @@ export function HomeScreen () { } catch { // silently ignore } - }, [fileWorkspaces.map((w) => w.path).join(",")]); + }, [filePaths]); + + const fetchSessionCounts = useCallback(async () => { + if (dirPaths.length === 0) return; + try { + const results = await invoke<[string, number][]>("count_workspace_sessions", { workspacePaths: dirPaths }); + const counts: Record = {}; + for (const [path, count] of results) { + counts[path] = count; + } + setSessionCounts(counts); + } catch { + // silently ignore + } + }, [dirPaths]); useEffect(() => { fetchCommentCounts(); }, [fetchCommentCounts]); + useEffect(() => { + fetchSessionCounts(); + }, [fetchSessionCounts]); + const handleOpenFile = () => openFile(); const handleOpenDirectory = () => openDirectory(); @@ -142,6 +163,7 @@ export function HomeScreen () { diff --git a/apps/tauri/src/components/MarkdownViewer.tsx b/apps/tauri/src/components/MarkdownViewer.tsx index ffa545c..7fe7ffb 100644 --- a/apps/tauri/src/components/MarkdownViewer.tsx +++ b/apps/tauri/src/components/MarkdownViewer.tsx @@ -70,6 +70,7 @@ export function MarkdownViewer({ const [reviewCollapsed, setReviewCollapsed] = useState(true); const review = useComments(); + const prevPhaseRef = useRef(phase); const loadContent = useCallback(async (path: string) => { try { @@ -189,12 +190,14 @@ export function MarkdownViewer({ }, [resolvedPath, embedded]); useEffect(() => { - if (embedded && phase === "reviewing" && reviewRef.current?.isCollapsed()) { + const enteringReview = prevPhaseRef.current !== "reviewing" && phase === "reviewing"; + if (embedded && enteringReview && reviewRef.current?.isCollapsed()) { reviewRef.current.expand(); } if (!embedded && review.isPanelOpen && reviewRef.current?.isCollapsed()) { reviewRef.current.expand(); } + prevPhaseRef.current = phase; }, [phase, review.isPanelOpen, embedded]); const handleApprove = useCallback(() => { @@ -202,8 +205,10 @@ export function MarkdownViewer({ const reviewText = review.generateReview(); const hasComments = review.comments.some((c) => !c.resolved); review.resolveAll(); + reviewRef.current?.collapse(); + review.setIsPanelOpen(false); onApprovePlan(hasComments ? reviewText : undefined); - }, [review.generateReview, review.comments, review.resolveAll, onApprovePlan]); + }, [review.generateReview, review.comments, review.resolveAll, review.setIsPanelOpen, onApprovePlan]); // --- Embedded mode (session plan) --- if (embedded) { diff --git a/apps/tauri/src/components/TerminalInput.tsx b/apps/tauri/src/components/TerminalInput.tsx index ab46a54..a84a496 100644 --- a/apps/tauri/src/components/TerminalInput.tsx +++ b/apps/tauri/src/components/TerminalInput.tsx @@ -55,7 +55,7 @@ export function TerminalInput({ onClick={onCancel} size="sm" variant="destructive" - className="text-xs gap-1.5" + className="text-xs gap-1.5 min-w-[100px]" > {t("acp.cancel")} @@ -63,7 +63,7 @@ export function TerminalInput({ ) : ( -
+ + {/* Long Recording Alert */} @@ -401,18 +399,6 @@ export function WhisperSettings() { - {/* Check Permissions */} -
- -
); } diff --git a/apps/tauri/src/components/ui/tabs.tsx b/apps/tauri/src/components/ui/tabs.tsx index ba20fbb..7d4dbd3 100644 --- a/apps/tauri/src/components/ui/tabs.tsx +++ b/apps/tauri/src/components/ui/tabs.tsx @@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<