Major UI refresh: sidebar redesign, theme expansion, and chat performance optimizations (#706)

## Summary
Complete sidebar redesign and comprehensive UI polish pass with performance optimizations, theme system refinements, and desktop integration improvements.

## Key Changes

**Sidebar & Navigation Redesign**
- Redesigned sessions sidebar layout with unified button primitives
- Added activity sections with project grouping and improved session organization
- Refined sidebar corners, spacing, and visual hierarchy
- Removed NavRail component in favor of streamlined sidebar
- Stabilized sessions bar toggle position in fullscreen mode

**Performance Optimizations**
- Reduced chat streaming CPU usage and storage churn
- Optimized task tool polling and live timers with debouncing
- Prevented chat state races and reduced background request load
- Debounced draft writes and coalesced session reloads
- Optimized message store updates and turn tracking

**Theme & Visual System**
- Added theme-aware window corners (desktop) and border radius tokens
- Introduced glassmorphism effects on desktop sidebar
- Added backdrop blur to UI elements

**Chat Experience**
- Added session-based permission auto-accept toggle in chat input
- Polished permission shield UX with improved icon sizing and spacing
- Fixed chat scroll-to-bottom behavior and timeline tracking
- Enhanced tool output display with better path label detection
- Removed duplicate draft context details in chat header
- Added text selection menu to chat messages

**Git Improvements**
- Refreshed git history visual design with cleaner dividers
- Added remote removal action in sync selector
- Stabilized git polling to prevent excessive requests
- Improved tool output rendering for git operations

**Settings & Panels**
- Fixed mobile scrolling on settings pages
- Made outside-click settings close instantly
- Reduced settings load churn and CPU spikes
- Improved services dropdown layout and spacing
- Softened panel resize handles

**Desktop Integration**
- Synced macOS window theme with app theme
- Restored window dragging in sidebar header zones
- Fixed system window corners on macOS
- Improved header session metadata and action controls

**Button & Component Standardization**
- Unified button primitives across all components
- Standardized destructive action patterns
- Removed unused button variants (button-large, button-small)
- Aligned context tab close hit areas

---------

Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
This commit is contained in:
Bohdan Triapitsyn
2026-03-20 01:01:03 +02:00
committed by GitHub
co-authored by Iuliia Ivashko
parent 359879153a
commit 321cc7252a
222 changed files with 8575 additions and 5456 deletions
@@ -81,69 +81,8 @@ interface ProjectActivityResult {
export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivityResult => {
const activityParts: TurnActivityRecord[] = [];
const hasTools = input.assistantMessages.some((message) =>
message.parts.some((part) => part.type === 'tool')
);
const hasReasoning = input.assistantMessages.some((message) =>
message.parts.some((part) => part.type === 'reasoning' && Boolean(getPartText(part)))
);
input.assistantMessages.forEach((message) => {
message.parts.forEach((part, partIndex) => {
if (part.type === 'tool') {
activityParts.push({
...buildTurnPartRecord(input.turnId, message.info.id, part, partIndex),
kind: 'tool',
});
return;
}
if (part.type === 'reasoning') {
const text = getPartText(part);
if (!text) {
return;
}
activityParts.push({
...buildTurnPartRecord(input.turnId, message.info.id, part, partIndex),
kind: 'reasoning',
});
return;
}
if (!input.showTextJustificationActivity || part.type !== 'text') {
return;
}
if (!isAssistantMessageCompleted(message)) {
return;
}
const finish = getMessageFinish(message);
if (!finish) {
return;
}
if (finish === 'stop') {
return;
}
const text = getPartText(part);
if (!text) {
return;
}
activityParts.push({
...buildTurnPartRecord(input.turnId, message.info.id, part, partIndex),
kind: 'justification',
});
});
});
const activitySegments: TurnActivityGroup[] = [];
const activityByPart = new WeakMap<object, TurnActivityRecord>();
activityParts.forEach((activity) => {
activityByPart.set(activity.part as object, activity);
});
let hasTools = false;
let hasReasoning = false;
const taskMessageById = new Map<string, string>();
const taskOrder: string[] = [];
@@ -151,32 +90,66 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit
let currentAfterToolPartId: string | null = null;
input.assistantMessages.forEach((message) => {
const messageId = message.info.id;
const messageCompleted = isAssistantMessageCompleted(message);
const finish = getMessageFinish(message);
message.parts.forEach((part, partIndex) => {
if (part.type === 'tool') {
const toolName = (part as { tool?: unknown }).tool;
if (isStandaloneTool(toolName)) {
const toolPartId = part.id ?? `${messageId}-part-${partIndex}-${part.type}`;
if (!taskMessageById.has(toolPartId)) {
taskMessageById.set(toolPartId, messageId);
taskOrder.push(toolPartId);
}
currentAfterToolPartId = toolPartId;
return;
}
const isTool = part.type === 'tool';
if (isTool) {
hasTools = true;
}
const activity = activityByPart.get(part as object);
if (!activity) {
const text = part.type === 'reasoning' || part.type === 'text'
? getPartText(part)
: undefined;
if (part.type === 'reasoning' && text) {
hasReasoning = true;
}
const toolName = isTool
? (part as { tool?: unknown }).tool
: undefined;
const standaloneTool = isTool && isStandaloneTool(toolName);
if (standaloneTool) {
const toolPartId = part.id ?? `${message.info.id}-part-${partIndex}-${part.type}`;
if (!taskMessageById.has(toolPartId)) {
taskMessageById.set(toolPartId, message.info.id);
taskOrder.push(toolPartId);
}
currentAfterToolPartId = toolPartId;
}
let kind: TurnActivityRecord['kind'] | null = null;
if (isTool) {
kind = 'tool';
} else if (part.type === 'reasoning') {
if (text) {
kind = 'reasoning';
}
} else if (
input.showTextJustificationActivity
&& part.type === 'text'
&& messageCompleted
&& typeof finish === 'string'
&& finish !== 'stop'
&& text
) {
kind = 'justification';
}
if (!kind) {
return;
}
if (activity.kind === 'tool') {
const toolName = (activity.part as { tool?: unknown }).tool;
if (isStandaloneTool(toolName)) {
return;
}
const activity: TurnActivityRecord = {
...buildTurnPartRecord(input.turnId, message.info.id, part, partIndex),
kind,
};
activityParts.push(activity);
if (kind === 'tool' && standaloneTool) {
return;
}
const list = partsByAfterTool.get(currentAfterToolPartId) ?? [];
@@ -185,6 +158,8 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit
});
});
const activitySegments: TurnActivityGroup[] = [];
const pickStartAnchor = (segmentParts: TurnActivityRecord[]): string | undefined => {
if (segmentParts.length === 0) {
return undefined;