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:
committed by
GitHub
co-authored by
Iuliia Ivashko
parent
359879153a
commit
321cc7252a
@@ -19,6 +19,7 @@ export function createFlexokiCodeMirrorTheme(theme: Theme): Extension {
|
||||
color: theme.colors.syntax.base.foreground,
|
||||
fontSize: 'var(--text-code)',
|
||||
lineHeight: '1.5rem',
|
||||
position: 'relative' as const,
|
||||
},
|
||||
'.cm-scroller': {
|
||||
fontFamily: monoFont,
|
||||
@@ -122,70 +123,350 @@ export function createFlexokiCodeMirrorTheme(theme: Theme): Extension {
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: theme.colors.surface.overlay,
|
||||
},
|
||||
/* ── Floating search: panels container ── */
|
||||
'.cm-panels': {
|
||||
backgroundColor: theme.colors.surface.elevated,
|
||||
backgroundColor: 'transparent',
|
||||
color: theme.colors.surface.foreground,
|
||||
position: 'absolute' as const,
|
||||
top: '0',
|
||||
right: '0',
|
||||
left: 'auto',
|
||||
width: 'auto',
|
||||
zIndex: 10,
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
'.cm-panels-top': {
|
||||
borderBottom: `1px solid ${theme.colors.interactive.border}`,
|
||||
boxShadow: `0 1px 0 ${theme.colors.surface.subtle}`,
|
||||
borderBottom: 'none',
|
||||
position: 'absolute' as const,
|
||||
top: '6px',
|
||||
right: '14px',
|
||||
left: 'auto',
|
||||
width: 'auto',
|
||||
},
|
||||
|
||||
/* ── Floating search panel ── */
|
||||
'.cm-panel.cm-search': {
|
||||
padding: '0.625rem 0.75rem',
|
||||
backgroundColor: theme.colors.surface.elevated,
|
||||
pointerEvents: 'auto',
|
||||
padding: '4px',
|
||||
backgroundColor: `color-mix(in srgb, ${theme.colors.surface.elevated} 85%, transparent)`,
|
||||
backdropFilter: 'blur(12px)',
|
||||
WebkitBackdropFilter: 'blur(12px)',
|
||||
border: `1px solid ${theme.colors.interactive.border}`,
|
||||
borderRadius: '10px',
|
||||
boxShadow: `0 2px 8px color-mix(in srgb, ${theme.colors.surface.background} 60%, transparent)`,
|
||||
width: 'auto',
|
||||
minWidth: '360px',
|
||||
maxWidth: '480px',
|
||||
},
|
||||
|
||||
/* ── Search inner layout: flexbox rows ── */
|
||||
'.cm-search': {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
gap: '0',
|
||||
width: '100%',
|
||||
color: theme.colors.surface.foreground,
|
||||
fontSize: '0.8125rem',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: '13px',
|
||||
lineHeight: '1',
|
||||
},
|
||||
|
||||
/* ── Hide <br>; we force row break via replace input's flex-basis ── */
|
||||
'.cm-search br': {
|
||||
display: 'none',
|
||||
},
|
||||
|
||||
/* ──────────────────────────────────────────────────
|
||||
ROW 1: [Find input] [Aa] [ab] [.*] [match count] [↑] [↓] [≡] [×]
|
||||
────────────────────────────────────────────────── */
|
||||
|
||||
/* ── Text input base: border-radius matches action buttons (4px) ── */
|
||||
'.cm-search .cm-textfield': {
|
||||
minHeight: '2rem',
|
||||
padding: '0.375rem 0.625rem',
|
||||
borderRadius: '0.5rem',
|
||||
appearance: 'none',
|
||||
height: '26px',
|
||||
margin: 0,
|
||||
padding: '0 8px',
|
||||
borderRadius: '6px !important',
|
||||
border: `1px solid ${theme.colors.interactive.border}`,
|
||||
backgroundColor: theme.colors.surface.background,
|
||||
color: theme.colors.surface.foreground,
|
||||
fontFamily: 'inherit',
|
||||
fontSize: '12px',
|
||||
lineHeight: '1',
|
||||
outline: 'none',
|
||||
transition: 'border-color 120ms ease, box-shadow 120ms ease',
|
||||
transition: 'border-color 150ms ease',
|
||||
},
|
||||
'.cm-search .cm-textfield:hover': {
|
||||
borderColor: theme.colors.interactive.borderHover,
|
||||
'.cm-search .cm-textfield::placeholder': {
|
||||
color: theme.colors.surface.mutedForeground,
|
||||
opacity: 1,
|
||||
},
|
||||
'.cm-search .cm-textfield:focus, .cm-search .cm-textfield:focus-visible': {
|
||||
borderColor: theme.colors.interactive.borderFocus,
|
||||
boxShadow: `0 0 0 2px ${theme.colors.interactive.focusRing}`,
|
||||
'.cm-search .cm-textfield:focus': {
|
||||
borderColor: theme.colors.primary.base,
|
||||
},
|
||||
'.cm-search .cm-button': {
|
||||
minHeight: '2rem',
|
||||
padding: '0.375rem 0.625rem',
|
||||
borderRadius: '0.5rem',
|
||||
border: `1px solid ${theme.colors.interactive.border}`,
|
||||
backgroundColor: theme.colors.surface.background,
|
||||
color: theme.colors.surface.foreground,
|
||||
|
||||
/* Find input: row 1, max-width matches replace input for alignment */
|
||||
'.cm-search .cm-textfield[name="search"]': {
|
||||
order: 1,
|
||||
flex: '1 1 120px',
|
||||
maxWidth: 'calc(100% - 142px)',
|
||||
minWidth: '100px',
|
||||
},
|
||||
|
||||
/* Replace input is styled below in the row-2 section */
|
||||
|
||||
/* ────────────────────────────────────────────────────
|
||||
ICON-ONLY TOGGLE BUTTONS (checkbox labels → icon toggles)
|
||||
Labels contain: [checkbox] "match case" / "regexp" / "by word"
|
||||
We hide text, hide the checkbox, and show icon via label::after
|
||||
──────────────────────────────────────────────────── */
|
||||
'.cm-search label': {
|
||||
order: 2,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
minWidth: '26px',
|
||||
flexShrink: '0',
|
||||
margin: 0,
|
||||
marginLeft: '1px',
|
||||
padding: 0,
|
||||
borderRadius: '4px',
|
||||
border: `1px solid transparent`,
|
||||
backgroundColor: 'transparent',
|
||||
color: 'transparent', /* hide label text */
|
||||
cursor: 'pointer',
|
||||
transition: 'background-color 120ms ease, border-color 120ms ease, color 120ms ease',
|
||||
userSelect: 'none' as const,
|
||||
fontSize: '1px', /* near-zero to collapse text nodes */
|
||||
lineHeight: '0',
|
||||
overflow: 'hidden',
|
||||
position: 'relative' as const,
|
||||
transition: 'background-color 120ms ease, border-color 120ms ease',
|
||||
},
|
||||
'.cm-search label:hover': {
|
||||
backgroundColor: theme.colors.interactive.hover,
|
||||
},
|
||||
'.cm-search label:has(input[type="checkbox"]:checked)': {
|
||||
backgroundColor: theme.colors.interactive.hover,
|
||||
borderColor: theme.colors.interactive.border,
|
||||
},
|
||||
|
||||
/* Hide the actual checkbox input inside labels */
|
||||
'.cm-search [type="checkbox"]': {
|
||||
appearance: 'none',
|
||||
WebkitAppearance: 'none',
|
||||
width: '0',
|
||||
height: '0',
|
||||
margin: '0',
|
||||
padding: '0',
|
||||
border: 'none',
|
||||
position: 'absolute' as const,
|
||||
opacity: '0',
|
||||
pointerEvents: 'none' as const,
|
||||
},
|
||||
|
||||
/* Icon via ::after on each label - absolutely positioned so text-hiding doesn't affect it */
|
||||
'.cm-search label::after': {
|
||||
position: 'absolute' as const,
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: '13px',
|
||||
fontWeight: '600',
|
||||
lineHeight: '1',
|
||||
fontFamily: 'inherit',
|
||||
color: theme.colors.surface.mutedForeground,
|
||||
pointerEvents: 'none' as const,
|
||||
},
|
||||
'.cm-search label:hover::after': {
|
||||
color: theme.colors.surface.foreground,
|
||||
},
|
||||
'.cm-search label:has(input[type="checkbox"]:checked)::after': {
|
||||
color: theme.colors.surface.foreground,
|
||||
},
|
||||
|
||||
/* Case sensitive: "Aa" */
|
||||
'.cm-search label:has(input[name="case"])::after': {
|
||||
content: '"Aa"',
|
||||
fontSize: '12px',
|
||||
letterSpacing: '-0.5px',
|
||||
},
|
||||
/* Regex: ".*" */
|
||||
'.cm-search label:has(input[name="re"])::after': {
|
||||
content: '".*"',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'monospace',
|
||||
},
|
||||
/* Whole word: "ab" with underline */
|
||||
'.cm-search label:has(input[name="word"])::after': {
|
||||
content: '"ab"',
|
||||
fontSize: '12px',
|
||||
textDecoration: 'underline',
|
||||
textUnderlineOffset: '2px',
|
||||
},
|
||||
|
||||
/* ────────────────────────────────────────────────────
|
||||
MATCH COUNT (search message - "X of Y")
|
||||
──────────────────────────────────────────────────── */
|
||||
'.cm-search .cm-search-message': {
|
||||
order: 3,
|
||||
color: theme.colors.surface.mutedForeground,
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
borderRadius: '0',
|
||||
padding: '0 6px',
|
||||
fontSize: '11px',
|
||||
whiteSpace: 'nowrap' as const,
|
||||
lineHeight: '26px',
|
||||
},
|
||||
|
||||
/* ────────────────────────────────────────────────────
|
||||
ICON-ONLY ACTION BUTTONS
|
||||
Hide button text with font-size:0, show icon via ::after
|
||||
──────────────────────────────────────────────────── */
|
||||
'.cm-search .cm-button': {
|
||||
appearance: 'none',
|
||||
WebkitAppearance: 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
minWidth: '26px',
|
||||
flexShrink: '0',
|
||||
margin: 0,
|
||||
marginLeft: '1px',
|
||||
padding: 0,
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
backgroundImage: 'none',
|
||||
color: 'transparent', /* hide button text */
|
||||
cursor: 'pointer',
|
||||
fontSize: '1px', /* near-zero to collapse text */
|
||||
fontWeight: '400',
|
||||
lineHeight: '0',
|
||||
overflow: 'hidden',
|
||||
position: 'relative' as const,
|
||||
boxShadow: 'none',
|
||||
transition: 'background-color 120ms ease',
|
||||
},
|
||||
'.cm-search .cm-button::after': {
|
||||
position: 'absolute' as const,
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: '15px',
|
||||
lineHeight: '1',
|
||||
color: theme.colors.surface.mutedForeground,
|
||||
pointerEvents: 'none' as const,
|
||||
},
|
||||
'.cm-search .cm-button:hover': {
|
||||
backgroundColor: theme.colors.interactive.hover,
|
||||
borderColor: theme.colors.interactive.borderHover,
|
||||
},
|
||||
'.cm-search .cm-button:hover::after': {
|
||||
color: theme.colors.surface.foreground,
|
||||
},
|
||||
'.cm-search .cm-button:active': {
|
||||
backgroundColor: theme.colors.interactive.active,
|
||||
borderColor: theme.colors.interactive.borderFocus,
|
||||
},
|
||||
'.cm-search .cm-button:focus, .cm-search .cm-button:focus-visible': {
|
||||
'.cm-search .cm-button:focus-visible': {
|
||||
outline: 'none',
|
||||
boxShadow: `0 0 0 2px ${theme.colors.interactive.focusRing}`,
|
||||
boxShadow: `0 0 0 1px ${theme.colors.interactive.focusRing}`,
|
||||
},
|
||||
'.cm-search .cm-button:disabled': {
|
||||
opacity: 0.55,
|
||||
cursor: 'not-allowed',
|
||||
backgroundColor: theme.colors.surface.muted,
|
||||
opacity: 0.4,
|
||||
cursor: 'default',
|
||||
pointerEvents: 'none' as const,
|
||||
},
|
||||
|
||||
/* prev → ↑ arrow (row 2) */
|
||||
'.cm-search .cm-button[name="prev"]': {
|
||||
order: 21,
|
||||
marginTop: '2px',
|
||||
},
|
||||
'.cm-search .cm-button[name="prev"]::after': {
|
||||
content: '"↑"',
|
||||
},
|
||||
/* next → ↓ arrow (row 2) */
|
||||
'.cm-search .cm-button[name="next"]': {
|
||||
order: 22,
|
||||
marginTop: '2px',
|
||||
},
|
||||
'.cm-search .cm-button[name="next"]::after': {
|
||||
content: '"↓"',
|
||||
},
|
||||
/* select all → ≡ (row 2) */
|
||||
'.cm-search .cm-button[name="select"]': {
|
||||
order: 23,
|
||||
marginTop: '2px',
|
||||
},
|
||||
'.cm-search .cm-button[name="select"]::after': {
|
||||
content: '"≡"',
|
||||
fontSize: '17px',
|
||||
},
|
||||
|
||||
/* ── Close button (×) - not a .cm-button, separate selector ── */
|
||||
'.cm-panel.cm-search button[name="close"]': {
|
||||
order: 5,
|
||||
appearance: 'none',
|
||||
WebkitAppearance: 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '26px',
|
||||
height: '26px',
|
||||
minWidth: '26px',
|
||||
flexShrink: '0',
|
||||
margin: '0 0 0 auto',
|
||||
padding: '0',
|
||||
borderRadius: '4px',
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
backgroundImage: 'none',
|
||||
color: theme.colors.surface.mutedForeground,
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
lineHeight: '1',
|
||||
boxShadow: 'none',
|
||||
position: 'relative' as const,
|
||||
overflow: 'visible',
|
||||
transition: 'background-color 120ms ease, color 120ms ease',
|
||||
},
|
||||
'.cm-panel.cm-search button[name="close"]:hover': {
|
||||
backgroundColor: theme.colors.interactive.hover,
|
||||
color: theme.colors.surface.foreground,
|
||||
},
|
||||
|
||||
/* ──────────────────────────────────────────────────
|
||||
ROW 2: [Replace input] [replace icon] [replace-all icon]
|
||||
────────────────────────────────────────────────── */
|
||||
|
||||
/* replace → swap icon (row 2) */
|
||||
'.cm-search .cm-button[name="replace"]': {
|
||||
order: 24,
|
||||
marginTop: '2px',
|
||||
},
|
||||
'.cm-search .cm-button[name="replace"]::after': {
|
||||
content: '"⇄"',
|
||||
},
|
||||
/* replace all → double arrow icon (row 2) */
|
||||
'.cm-search .cm-button[name="replaceAll"], .cm-search .cm-button[name="replace-all"]': {
|
||||
order: 25,
|
||||
marginTop: '2px',
|
||||
},
|
||||
'.cm-search .cm-button[name="replaceAll"]::after, .cm-search .cm-button[name="replace-all"]::after': {
|
||||
content: '"⇉"',
|
||||
},
|
||||
|
||||
/* ── Replace input: forces wrap to row 2, same width as find input ── */
|
||||
'.cm-search .cm-textfield[name="replace"]': {
|
||||
order: 20,
|
||||
flex: '1 1 100%',
|
||||
maxWidth: 'calc(100% - 142px)', /* room for ↑↓≡⇄⇉ (5 × 27px + gaps) */
|
||||
minWidth: '80px',
|
||||
marginTop: '2px',
|
||||
},
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: theme.colors.status.infoBackground,
|
||||
|
||||
Reference in New Issue
Block a user