From 01dbe2a8bfcb3b265995f01f905b218d5f576f7b Mon Sep 17 00:00:00 2001 From: Haileyesus <118998054+blackmammoth@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:55:40 +0300 Subject: [PATCH 1/4] chore: add prism plugin --- server/utils/plugin-loader.js | 3 ++- src/components/plugins/view/PluginSettingsTab.tsx | 9 +++++++++ src/i18n/locales/en/settings.json | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) 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/plugins/view/PluginSettingsTab.tsx b/src/components/plugins/view/PluginSettingsTab.tsx index 72de671b..b3b5ac8d 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 = '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-cloudcli', + translationKey: 'prismCloudCLI', + repoUrl: PRISM_CLOUDCLI, + installedNames: ['prism-cloudcli'], + 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", From 3cd89956ba06f0fc3e17d349b0c50baab4012658 Mon Sep 17 00:00:00 2001 From: Haileyesus <118998054+blackmammoth@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:10:24 +0300 Subject: [PATCH 2/4] fix: update naming convention --- src/components/plugins/view/PluginSettingsTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/plugins/view/PluginSettingsTab.tsx b/src/components/plugins/view/PluginSettingsTab.tsx index b3b5ac8d..8ebf3dbf 100644 --- a/src/components/plugins/view/PluginSettingsTab.tsx +++ b/src/components/plugins/view/PluginSettingsTab.tsx @@ -26,7 +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 = 'https://github.com/jakeefr/cloudcli-plugin-prism'; +const PRISM_CLOUDCLI_PLUGIN_URL = 'https://github.com/jakeefr/cloudcli-plugin-prism'; type PluginRecommendation = { id: string; @@ -76,7 +76,7 @@ const UNOFFICIAL_PLUGIN_RECOMMENDATIONS: PluginRecommendation[] = [ { id: 'prism-cloudcli', translationKey: 'prismCloudCLI', - repoUrl: PRISM_CLOUDCLI, + repoUrl: PRISM_CLOUDCLI_PLUGIN_URL, installedNames: ['prism-cloudcli'], icon: Activity, source: 'unofficial' From ca8fd0ee235b6a3210157bd0d9af83024d4a2248 Mon Sep 17 00:00:00 2001 From: Haileyesus <118998054+blackmammoth@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:44:42 +0300 Subject: [PATCH 3/4] fix: align prism plugin name and id with manifest.json --- src/components/plugins/view/PluginSettingsTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/plugins/view/PluginSettingsTab.tsx b/src/components/plugins/view/PluginSettingsTab.tsx index 8ebf3dbf..f668e30b 100644 --- a/src/components/plugins/view/PluginSettingsTab.tsx +++ b/src/components/plugins/view/PluginSettingsTab.tsx @@ -74,10 +74,10 @@ const UNOFFICIAL_PLUGIN_RECOMMENDATIONS: PluginRecommendation[] = [ source: 'unofficial', }, { - id: 'prism-cloudcli', + id: 'prism', translationKey: 'prismCloudCLI', repoUrl: PRISM_CLOUDCLI_PLUGIN_URL, - installedNames: ['prism-cloudcli'], + installedNames: ['prism'], icon: Activity, source: 'unofficial' } From 33a4e72ca4f84df60aadfc4ff3f3467d6f5ae948 Mon Sep 17 00:00:00 2001 From: ShockStruck Date: Sun, 24 May 2026 18:28:05 -0400 Subject: [PATCH 4/4] fix(chat): re-anchor initial scroll across lazy content reflow The previous initial-scroll behavior fired one scrollToBottom() at +200ms after the session load and cleared the pending flag. When markdown, syntax highlighting, or images finished rendering after that window, scrollHeight grew but nothing re-anchored the viewport. The chat tab appeared "scrolled way up" with the latest assistant message off-screen until the user manually scrolled or sent a new message. This replaces the setTimeout with a requestAnimationFrame loop that re-scrolls every frame while scrollHeight is still growing, capped at ~1s (60 frames) or 3 consecutive stable frames. The loop cancels cleanly on session change via the existing pendingInitialScrollRef flag, and the cleanup function cancels any in-flight rAF on unmount. No behavior change for sessions whose content layout is already stable at the first frame. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../chat/hooks/useChatSessionState.ts | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) 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