From 45b3e54d04cc107d835a528d2e6ecaf195f628a5 Mon Sep 17 00:00:00 2001
From: simos
Date: Fri, 11 Jul 2025 12:35:27 +0000
Subject: [PATCH] Added stared project and ux enahncements
---
package-lock.json | 4 +-
src/components/ChatInterface.jsx | 23 +++++++-
src/components/Sidebar.jsx | 96 ++++++++++++++++++++++++++++++--
3 files changed, 116 insertions(+), 7 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 8158c40..ed6cdfc 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "claude-code-ui",
- "version": "1.1.4",
+ "version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-code-ui",
- "version": "1.1.4",
+ "version": "1.2.0",
"license": "MIT",
"dependencies": {
"@anthropic-ai/claude-code": "^1.0.24",
diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx
index b3238fb..b0caae7 100755
--- a/src/components/ChatInterface.jsx
+++ b/src/components/ChatInterface.jsx
@@ -1597,6 +1597,14 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
}
}, []); // Only run once on mount
+ // Reset textarea height when input is cleared programmatically
+ useEffect(() => {
+ if (textareaRef.current && !input.trim()) {
+ textareaRef.current.style.height = 'auto';
+ setIsTextareaExpanded(false);
+ }
+ }, [input]);
+
const handleTranscript = useCallback((text) => {
if (text.trim()) {
setInput(prevInput => {
@@ -1689,6 +1697,12 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
setInput('');
setIsTextareaExpanded(false);
+
+ // Reset textarea height to minimal state
+ if (textareaRef.current) {
+ textareaRef.current.style.height = 'auto';
+ }
+
// Clear the saved draft since message was sent
if (selectedProject) {
localStorage.removeItem(`draft_input_${selectedProject.name}`);
@@ -1776,8 +1790,15 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
};
const handleInputChange = (e) => {
- setInput(e.target.value);
+ const newValue = e.target.value;
+ setInput(newValue);
setCursorPosition(e.target.selectionStart);
+
+ // Handle height reset when input becomes empty
+ if (!newValue.trim()) {
+ e.target.style.height = 'auto';
+ setIsTextareaExpanded(false);
+ }
};
const handleTextareaClick = (e) => {
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 8c19771..d076428 100755
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -3,7 +3,7 @@ import { ScrollArea } from './ui/scroll-area';
import { Button } from './ui/button';
import { Badge } from './ui/badge';
import { Input } from './ui/input';
-import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRight, Edit3, Check, X, Trash2, Settings, FolderPlus, RefreshCw, Sparkles, Edit2 } from 'lucide-react';
+import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRight, Edit3, Check, X, Trash2, Settings, FolderPlus, RefreshCw, Sparkles, Edit2, Star } from 'lucide-react';
import { cn } from '../lib/utils';
import ClaudeLogo from './ClaudeLogo';
import { api } from '../utils/api';
@@ -65,6 +65,17 @@ function Sidebar({
const [editingSession, setEditingSession] = useState(null);
const [editingSessionName, setEditingSessionName] = useState('');
const [generatingSummary, setGeneratingSummary] = useState({});
+
+ // Starred projects state - persisted in localStorage
+ const [starredProjects, setStarredProjects] = useState(() => {
+ try {
+ const saved = localStorage.getItem('starredProjects');
+ return saved ? new Set(JSON.parse(saved)) : new Set();
+ } catch (error) {
+ console.error('Error loading starred projects:', error);
+ return new Set();
+ }
+ });
// Touch handler to prevent double-tap issues on iPad (only for buttons, not scroll areas)
const handleTouchClick = (callback) => {
@@ -124,6 +135,37 @@ function Sidebar({
setExpandedProjects(newExpanded);
};
+ // Starred projects utility functions
+ const toggleStarProject = (projectName) => {
+ const newStarred = new Set(starredProjects);
+ if (newStarred.has(projectName)) {
+ newStarred.delete(projectName);
+ } else {
+ newStarred.add(projectName);
+ }
+ setStarredProjects(newStarred);
+
+ // Persist to localStorage
+ try {
+ localStorage.setItem('starredProjects', JSON.stringify([...newStarred]));
+ } catch (error) {
+ console.error('Error saving starred projects:', error);
+ }
+ };
+
+ const isProjectStarred = (projectName) => {
+ return starredProjects.has(projectName);
+ };
+
+ // Sort projects to show starred ones first
+ const sortedProjects = [...projects].sort((a, b) => {
+ const aStarred = isProjectStarred(a.name);
+ const bStarred = isProjectStarred(b.name);
+
+ if (aStarred && !bStarred) return -1;
+ if (!aStarred && bStarred) return 1;
+ return 0; // Keep original order for same star status
+ });
const startEditing = (project) => {
setEditingProject(project.name);
@@ -499,9 +541,10 @@ function Sidebar({
) : (
- projects.map((project) => {
+ sortedProjects.map((project) => {
const isExpanded = expandedProjects.has(project.name);
const isSelected = selectedProject?.name === project.name;
+ const isStarred = isProjectStarred(project.name);
return (
@@ -512,7 +555,8 @@ function Sidebar({
{
// On mobile, just toggle the folder - don't select the project
@@ -594,6 +638,28 @@ function Sidebar({
>
) : (
<>
+ {/* Star button */}
+
{getAllSessions(project).length === 0 && (