diff --git a/server/utils/plugin-loader.js b/server/utils/plugin-loader.js index 9d91068f..b73e0391 100644 --- a/server/utils/plugin-loader.js +++ b/server/utils/plugin-loader.js @@ -1,7 +1,8 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; -import { spawn } from 'child_process'; + +import { spawn } from 'cross-spawn'; const PLUGINS_DIR = path.join(os.homedir(), '.claude-code-ui', 'plugins'); const PLUGINS_CONFIG_PATH = path.join(os.homedir(), '.claude-code-ui', 'plugins.json'); diff --git a/src/components/chat/hooks/useChatSessionState.ts b/src/components/chat/hooks/useChatSessionState.ts index 20f42551..d11ff3cb 100644 --- a/src/components/chat/hooks/useChatSessionState.ts +++ b/src/components/chat/hooks/useChatSessionState.ts @@ -383,12 +383,47 @@ export function useChatSessionState({ setIsUserScrolledUp(false); }, [selectedProject?.projectId, selectedSession?.id]); - // Initial scroll to bottom + // Initial scroll to bottom — robust to lazy content reflow. + // The previous implementation fired one scrollToBottom() at +200ms and + // cleared the pending flag. When markdown blocks, code highlighting, or + // images finished rendering after that window, scrollHeight grew but + // nothing re-anchored the viewport, leaving the chat tab visually + // "scrolled way up" with the latest assistant message off-screen. + // + // This version re-scrolls every animation frame while scrollHeight is + // still growing, capped at ~1s (60 frames) or 3 consecutive stable + // frames. Cancels cleanly on session change via the pending flag. useEffect(() => { if (!pendingInitialScrollRef.current || !scrollContainerRef.current || isLoadingSessionMessages) return; if (chatMessages.length === 0) { pendingInitialScrollRef.current = false; return; } - pendingInitialScrollRef.current = false; - if (!searchScrollActiveRef.current) setTimeout(() => scrollToBottom(), 200); + if (searchScrollActiveRef.current) { pendingInitialScrollRef.current = false; return; } + + const container = scrollContainerRef.current; + let frame = 0; + let lastHeight = 0; + let stableCount = 0; + let rafId = 0; + + const tick = () => { + if (!pendingInitialScrollRef.current || !scrollContainerRef.current) return; + container.scrollTop = container.scrollHeight; + if (container.scrollHeight === lastHeight) { + stableCount++; + } else { + stableCount = 0; + lastHeight = container.scrollHeight; + } + frame++; + if (stableCount < 3 && frame < 60) { + rafId = requestAnimationFrame(tick); + } else { + pendingInitialScrollRef.current = false; + } + }; + rafId = requestAnimationFrame(tick); + return () => { + if (rafId) cancelAnimationFrame(rafId); + }; }, [chatMessages.length, isLoadingSessionMessages, scrollToBottom]); // Main session loading effect — store-based diff --git a/src/components/plugins/view/PluginSettingsTab.tsx b/src/components/plugins/view/PluginSettingsTab.tsx index 72de671b..f668e30b 100644 --- a/src/components/plugins/view/PluginSettingsTab.tsx +++ b/src/components/plugins/view/PluginSettingsTab.tsx @@ -26,6 +26,7 @@ const STARTER_PLUGIN_URL = 'https://github.com/cloudcli-ai/cloudcli-plugin-start const TERMINAL_PLUGIN_URL = 'https://github.com/cloudcli-ai/cloudcli-plugin-terminal'; const SCHEDULED_PROMPT_PLUGIN_URL = 'https://github.com/grostim/cloudcli-cron'; const CLAUDE_WATCH_PLUGIN_URL = 'https://github.com/satsuki19980613/cloudcli-claude-watch'; +const PRISM_CLOUDCLI_PLUGIN_URL = 'https://github.com/jakeefr/cloudcli-plugin-prism'; type PluginRecommendation = { id: string; @@ -72,6 +73,14 @@ const UNOFFICIAL_PLUGIN_RECOMMENDATIONS: PluginRecommendation[] = [ icon: Clock, source: 'unofficial', }, + { + id: 'prism', + translationKey: 'prismCloudCLI', + repoUrl: PRISM_CLOUDCLI_PLUGIN_URL, + installedNames: ['prism'], + icon: Activity, + source: 'unofficial' + } ]; function repoSlug(repoUrl: string) { diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index b80d17d2..6b550530 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -502,6 +502,12 @@ "description": "Watch long-running Claude Code sessions for hangs and expose process controls.", "install": "Install" }, + "prismCloudCLI": { + "name": "PRISM CloudCLI", + "badge": "unofficial", + "description": "Session intelligence for Claude Code, inside CloudCLI. See why your sessions are burning tokens without leaving the browser.", + "install": "Install" + }, "morePlugins": "More", "enable": "Enable", "disable": "Disable",