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
@@ -0,0 +1,107 @@
|
||||
import type { Session } from '@opencode-ai/sdk/v2';
|
||||
|
||||
export const ACTIVE_NOW_STORAGE_KEY = 'oc.sessions.activeNow';
|
||||
export const ACTIVE_NOW_MAX_AGE_MS = 36 * 60 * 60 * 1000;
|
||||
|
||||
export type ActiveNowEntry = {
|
||||
sessionId: string;
|
||||
};
|
||||
|
||||
const isSubtaskSession = (session: Session): boolean => {
|
||||
return Boolean((session as Session & { parentID?: string | null }).parentID);
|
||||
};
|
||||
|
||||
const isArchivedSession = (session: Session): boolean => {
|
||||
return Boolean(session.time?.archived);
|
||||
};
|
||||
|
||||
const getSessionUpdatedAt = (session: Session): number => {
|
||||
const updated = session.time?.updated;
|
||||
const created = session.time?.created;
|
||||
if (typeof updated === 'number' && Number.isFinite(updated)) {
|
||||
return updated;
|
||||
}
|
||||
if (typeof created === 'number' && Number.isFinite(created)) {
|
||||
return created;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const readActiveNowEntries = (storage: Storage): ActiveNowEntry[] => {
|
||||
try {
|
||||
const raw = storage.getItem(ACTIVE_NOW_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
return [];
|
||||
}
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (!Array.isArray(parsed)) {
|
||||
return [];
|
||||
}
|
||||
const seen = new Set<string>();
|
||||
const next: ActiveNowEntry[] = [];
|
||||
parsed.forEach((item) => {
|
||||
const sessionId = typeof item === 'string'
|
||||
? item
|
||||
: (item && typeof item === 'object' && 'sessionId' in item && typeof item.sessionId === 'string' ? item.sessionId : null);
|
||||
if (!sessionId || seen.has(sessionId)) {
|
||||
return;
|
||||
}
|
||||
seen.add(sessionId);
|
||||
next.push({ sessionId });
|
||||
});
|
||||
return next;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const persistActiveNowEntries = (storage: Storage, entries: ActiveNowEntry[]): void => {
|
||||
try {
|
||||
storage.setItem(ACTIVE_NOW_STORAGE_KEY, JSON.stringify(entries));
|
||||
} catch {
|
||||
// ignored
|
||||
}
|
||||
};
|
||||
|
||||
export const pruneActiveNowEntries = (
|
||||
entries: ActiveNowEntry[],
|
||||
sessionsById: Map<string, Session>,
|
||||
now = Date.now(),
|
||||
): ActiveNowEntry[] => {
|
||||
const minUpdatedAt = now - ACTIVE_NOW_MAX_AGE_MS;
|
||||
return entries.filter((entry) => {
|
||||
const session = sessionsById.get(entry.sessionId);
|
||||
if (!session) {
|
||||
return true;
|
||||
}
|
||||
if (isArchivedSession(session)) {
|
||||
return false;
|
||||
}
|
||||
return getSessionUpdatedAt(session) >= minUpdatedAt;
|
||||
});
|
||||
};
|
||||
|
||||
export const addActiveNowSession = (entries: ActiveNowEntry[], sessionId: string): ActiveNowEntry[] => {
|
||||
if (!sessionId || entries.some((entry) => entry.sessionId === sessionId)) {
|
||||
return entries;
|
||||
}
|
||||
return [{ sessionId }, ...entries];
|
||||
};
|
||||
|
||||
export const sortSessionsByUpdated = (sessions: Session[]): Session[] => {
|
||||
return [...sessions].sort((a, b) => getSessionUpdatedAt(b) - getSessionUpdatedAt(a));
|
||||
};
|
||||
|
||||
export const deriveActiveNowSessions = (
|
||||
entries: ActiveNowEntry[],
|
||||
sessionsById: Map<string, Session>,
|
||||
): Session[] => {
|
||||
const sessions = entries
|
||||
.map((entry) => sessionsById.get(entry.sessionId) ?? null)
|
||||
.filter((session): session is Session => Boolean(session))
|
||||
.filter((session) => !isArchivedSession(session))
|
||||
.filter((session) => !isSubtaskSession(session));
|
||||
return sortSessionsByUpdated(sessions);
|
||||
};
|
||||
|
||||
export const getSessionUpdatedAtMs = getSessionUpdatedAt;
|
||||
Reference in New Issue
Block a user