refactor(sidebar): restyle left sidebar layout and alignment#1027
refactor(sidebar): restyle left sidebar layout and alignment#1027leondri wants to merge 3 commits intogeneralaction:mainfrom
Conversation
|
@leondri is attempting to deploy a commit to the General Action Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryThis PR refactors the left sidebar to improve visual hierarchy and information density. Tasks are now indented under projects, agent names appear as secondary descriptions, and compact relative dates (e.g., "1d") are shown. The "Add Project" button moved to the Projects header, and archived tasks functionality was removed from the sidebar view. Key changes:
Issue found: Remote project indicator hardcoded to always show "disconnected" state instead of using actual connection data from Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| src/renderer/components/LeftSidebar.tsx | Removed archived tasks section and project deletion UI, moved "Add Project" button to header, changed project/task styling and indentation |
| src/renderer/components/TaskItem.tsx | Restructured task display to show agent names as secondary line, added compact date formatting, changed right-side content priority (archive > changes > PR > date) |
| src/renderer/hooks/useTaskAgentNames.ts | New hook to fetch and display agent names from task conversations, handles multi-agent tasks with "+N" notation |
Last reviewed commit: a2297a7
| > | ||
| <span className="block w-full truncate"> | ||
| <ProjectItem | ||
| project={typedProject} | ||
| isActive={isProjectActive} | ||
| onSelect={() => onSelectProject(typedProject)} | ||
| /> | ||
| </span> | ||
| <span className="hidden w-full truncate text-xs text-muted-foreground sm:block"> | ||
| {typedProject.githubInfo?.repository || typedProject.path} | ||
| </span> | ||
| </motion.button> | ||
| <div className="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-0.5"> | ||
| {showProjectDelete ? ( | ||
| <ProjectDeleteButton | ||
| projectName={typedProject.name} | ||
| tasks={typedProject.tasks || []} | ||
| onConfirm={() => handleDeleteProject(typedProject)} | ||
| isDeleting={isDeletingProject} | ||
| aria-label={`Delete project ${typedProject.name}`} | ||
| className={`bg-accent text-muted-foreground ${ | ||
| isDeletingProject | ||
| ? '' | ||
| : 'opacity-0 group-hover/project:opacity-100' | ||
| }`} | ||
| /> | ||
| ) : null} | ||
| <CollapsibleTrigger asChild> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon-sm" | ||
| aria-label={`Toggle tasks for ${typedProject.name}`} | ||
| onClick={(e) => e.stopPropagation()} | ||
| className="text-muted-foreground opacity-0 group-hover/project:opacity-100 group-data-[state=open]/collapsible:opacity-100" | ||
| {typedProject.name} | ||
| </button> | ||
| {projectIsRemote && connectionId && ( | ||
| <RemoteProjectIndicator | ||
| host={undefined} | ||
| connectionState={'disconnected' as ConnectionState} |
There was a problem hiding this comment.
hardcoded host={undefined} and connectionState={'disconnected' as ConnectionState} instead of using actual remote project data
The useRemoteProject hook was removed but the remote project indicator still displays. These hardcoded values mean remote projects will always show as disconnected.
| > | |
| <span className="block w-full truncate"> | |
| <ProjectItem | |
| project={typedProject} | |
| isActive={isProjectActive} | |
| onSelect={() => onSelectProject(typedProject)} | |
| /> | |
| </span> | |
| <span className="hidden w-full truncate text-xs text-muted-foreground sm:block"> | |
| {typedProject.githubInfo?.repository || typedProject.path} | |
| </span> | |
| </motion.button> | |
| <div className="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-0.5"> | |
| {showProjectDelete ? ( | |
| <ProjectDeleteButton | |
| projectName={typedProject.name} | |
| tasks={typedProject.tasks || []} | |
| onConfirm={() => handleDeleteProject(typedProject)} | |
| isDeleting={isDeletingProject} | |
| aria-label={`Delete project ${typedProject.name}`} | |
| className={`bg-accent text-muted-foreground ${ | |
| isDeletingProject | |
| ? '' | |
| : 'opacity-0 group-hover/project:opacity-100' | |
| }`} | |
| /> | |
| ) : null} | |
| <CollapsibleTrigger asChild> | |
| <Button | |
| variant="ghost" | |
| size="icon-sm" | |
| aria-label={`Toggle tasks for ${typedProject.name}`} | |
| onClick={(e) => e.stopPropagation()} | |
| className="text-muted-foreground opacity-0 group-hover/project:opacity-100 group-data-[state=open]/collapsible:opacity-100" | |
| {typedProject.name} | |
| </button> | |
| {projectIsRemote && connectionId && ( | |
| <RemoteProjectIndicator | |
| host={undefined} | |
| connectionState={'disconnected' as ConnectionState} | |
| {projectIsRemote && connectionId && (() => { | |
| const remote = useRemoteProject(typedProject); | |
| return ( | |
| <RemoteProjectIndicator | |
| host={remote.host || undefined} | |
| connectionState={remote.connectionState as ConnectionState} | |
| size="md" | |
| onReconnect={remote.reconnect} | |
| disabled={remote.isLoading} | |
| /> | |
| ); | |
| })()} |
|
Thanks! @leondri, will review the PR and give feedback. |
arnestrickmann
left a comment
There was a problem hiding this comment.
Review Summary
Nice UI improvements — the two-line task layout (name + agent), compact dates, and the new bulk-action task list in ProjectMainView are solid. The visual hierarchy is clearly better.
However, there are blocking regressions that remove or break existing functionality. These must be fixed before merge:
Blocking Issues
-
Remote project indicator hardcoded to "disconnected" — SSH remote projects will always show disconnected status regardless of actual connection state. The
useRemoteProjecthook was removed but the indicator still renders with hardcoded values. This is a functional regression. -
Task delete removed from sidebar —
onDeleteTaskis accepted as a prop but underscore-prefixed and never used.TaskItemalso underscore-prefixesonDelete/showDeleteinternally. Users can no longer delete tasks from the sidebar — only archive. The delete functionality should be preserved (e.g. in the context menu) even if archive-on-hover is the primary action. -
Project delete removed from sidebar —
onDeleteProjectis accepted but never wired up. Previously there was a delete button on project hover. This feature should remain accessible.
Non-blocking but worth addressing
-
useTaskAgentNames fires one IPC call per task — For 20+ tasks this means 20+ getConversations() IPC round-trips on render. Consider batching or caching.
-
useTaskAgentNames shows chat count, not unique provider count — "Claude +4" when there are 5 Claude chats but only 1 unique provider is misleading.
-
Filter state doesn't reset on project change — showFilter and searchFilter persist when switching projects.
-
Hardcoded bg-gray-50 for sidebar light mode — Uses a fixed Tailwind gray instead of semantic theme tokens.
-
Project name at 60% opacity + cursor-default — Makes projects look disabled/non-interactive.
| connectionState={'disconnected' as ConnectionState} | ||
| size="md" | ||
| disabled | ||
| /> |
There was a problem hiding this comment.
Blocking: RemoteProjectIndicator is hardcoded to always show disconnected with host={undefined}.
The useRemoteProject hook was removed but the indicator still renders — remote projects will always appear disconnected regardless of actual state. The onReconnect callback is also gone.
Since hooks can't be called inside a render callback, the fix is to extract this into a small child component that calls useRemoteProject internally:
const SidebarRemoteIndicator: React.FC<{ project: Project }> = ({ project }) => {
const remote = useRemoteProject(project);
return (
<RemoteProjectIndicator
host={remote.host || undefined}
connectionState={remote.connectionState as ConnectionState}
size="md"
onReconnect={remote.reconnect}
disabled={remote.isLoading}
/>
);
};Then use <SidebarRemoteIndicator project={typedProject} /> here.
| onSidebarContextChange, | ||
| onCreateTaskForProject, | ||
| onDeleteTask, | ||
| onDeleteTask: _onDeleteTask, |
There was a problem hiding this comment.
Blocking: onDeleteTask and onDeleteProject (lines 133, 137) are accepted as props but underscore-prefixed and never used. This means:
- Users can no longer delete tasks from the sidebar
- Users can no longer delete projects from the sidebar
These are feature regressions. Even if the primary sidebar action becomes archive-on-hover, delete should still be accessible — e.g. through the existing TaskItem context menu, or as a secondary hover action.
| export const TaskItem: React.FC<TaskItemProps> = ({ | ||
| task, | ||
| onDelete, | ||
| onDelete: _onDelete, |
There was a problem hiding this comment.
Blocking: onDelete is renamed to _onDelete and never used (same for showDelete → _showDelete on line 70). The delete button was completely removed from TaskItem.
The context menu still has archive + pin + rename — onDelete should be wired back in there as a context menu option, even if the hover action is now archive instead of delete.
|
|
||
| async function load() { | ||
| try { | ||
| const res = await window.electronAPI.getConversations(taskId); |
There was a problem hiding this comment.
Performance: This hook fires getConversations(taskId) per task on mount. With 20 tasks visible, that's 20 IPC round-trips.
Consider either:
- A batch API that fetches agent info for all tasks in one call
- A simple in-memory cache with short TTL
- Lifting the fetch to the parent list component and passing results down as props
| additionalCount > 0 ? `${primaryName} +${additionalCount}` : primaryName; | ||
|
|
||
| setInfo({ primaryName, additionalCount, displayLabel, providerIds }); | ||
| } catch { |
There was a problem hiding this comment.
Bug: additionalCount is totalChats - 1 (total conversations minus 1), not unique providers minus 1. If a task has 5 Claude conversations, this shows "Claude +4" — but there's really only 1 unique provider.
Should this be providerIds.length - 1 instead of totalChats - 1? Or if showing chat count is intentional, the label should clarify (e.g. "Claude (5 chats)").
Restyled sidebar using the same backend functionality: