feat: move projects to sidebar rail and speed up session switching (#506)

* feat: move projects from header tabs to left sidebar rail

* refactor: remove variant from git generation session context

* feat: implemented drandrop action for navrails

* refactor: improve sidebar drag-and-drop smoothness and remove floating overlay

* fix: prevent mobile nav rail touch from closing drawer and opening menu

* fix: hide header tabs on desktop

* fix: prevent session flicker when switching projects

* style: soften chat panel divider borders

* style: improve icon contrast across core navigation

* fix: stabilize session selection during project switching

* style: unify sidebar surfaces and transparent section layers

* style: remove UI shadows and keep only scroll shadow

* perf: speed up project switching with cached session loading

* perf: speed up Git changes view and background refresh

* perf: make session switching lighter and less aggressive

* perf: optimized sessions list loading while project switching

* perf: smooth chat rendering and reduce interaction spikes

* fix: restore reliable load older messages visibility
This commit is contained in:
Bohdan Triapitsyn
2026-02-25 14:32:23 +02:00
committed by GitHub
parent 4c69bccf56
commit 10851bd7ac
51 changed files with 1995 additions and 1399 deletions
+44 -4
View File
@@ -97,6 +97,8 @@ type GitmojiCachePayload = {
const GITMOJI_CACHE_KEY = 'gitmojiCache';
const GITMOJI_CACHE_TTL_MS = 1000 * 60 * 60 * 24 * 7;
const GITMOJI_CACHE_VERSION = '1';
const GIT_DIFF_PRIORITY_PREFETCH_LIMIT = 40;
const GIT_DIFF_PRIORITY_BASELINE_LIMIT = 20;
const GITMOJI_SOURCE_URL =
'https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json';
@@ -245,6 +247,7 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
fetchBranches,
fetchLog,
fetchIdentity,
prefetchDiffs,
setLogMaxCount,
} = useGitStore();
const isMobile = useUIStore((state) => state.isMobile);
@@ -310,6 +313,7 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
const [commitMessage, setCommitMessage] = React.useState(
initialSnapshot?.commitMessage ?? ''
);
const [visibleChangePaths, setVisibleChangePaths] = React.useState<string[]>([]);
const [isGitmojiPickerOpen, setIsGitmojiPickerOpen] = React.useState(false);
const actionPanelScrollRef = React.useRef<HTMLElement | null>(null);
const [syncAction, setSyncAction] = React.useState<SyncAction>(null);
@@ -620,10 +624,12 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
const dirState = useGitStore.getState().directories.get(currentDirectory);
if (!dirState?.status) {
fetchAll(currentDirectory, git, { force: true });
void fetchAll(currentDirectory, git, { force: true });
} else {
void fetchStatus(currentDirectory, git, { silent: true });
}
}
}, [currentDirectory, setActiveDirectory, fetchAll, git]);
}, [currentDirectory, setActiveDirectory, fetchAll, fetchStatus, git]);
const refreshStatusAndBranches = React.useCallback(
async (showErrors = true) => {
@@ -706,6 +712,39 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
return Array.from(unique.values()).sort((a, b) => a.path.localeCompare(b.path));
}, [status]);
React.useEffect(() => {
if (!currentDirectory || changeEntries.length === 0) {
return;
}
const orderedPaths: string[] = [];
const seen = new Set<string>();
const pushPath = (path: string) => {
if (!path || seen.has(path)) {
return;
}
seen.add(path);
orderedPaths.push(path);
};
Array.from(selectedPaths).forEach(pushPath);
visibleChangePaths.forEach(pushPath);
changeEntries.slice(0, GIT_DIFF_PRIORITY_BASELINE_LIMIT).forEach((entry) => pushPath(entry.path));
if (orderedPaths.length === 0) {
return;
}
const timeoutId = window.setTimeout(() => {
void prefetchDiffs(currentDirectory, git, orderedPaths, { maxFiles: GIT_DIFF_PRIORITY_PREFETCH_LIMIT });
}, 120);
return () => {
window.clearTimeout(timeoutId);
};
}, [changeEntries, currentDirectory, git, prefetchDiffs, selectedPaths, visibleChangePaths]);
React.useEffect(() => {
if (!status || changeEntries.length === 0) {
@@ -1628,7 +1667,7 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
}
return (
<div className="flex h-full flex-col overflow-hidden bg-background" data-keyboard-avoid="true">
<div className={cn('flex h-full flex-col overflow-hidden', isSidebarMode ? 'bg-transparent' : 'bg-background')} data-keyboard-avoid="true">
<GitHeader
status={status}
localBranches={localBranches}
@@ -1670,7 +1709,7 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
<div className="flex-1 min-h-0 overflow-hidden">
<div className="h-full min-h-0 flex flex-col">
<div className={cn('min-w-0 min-h-0 h-full bg-muted/10 flex flex-col', isSidebarMode && 'border-t border-border/40')}>
<div className={cn('min-w-0 min-h-0 h-full flex flex-col', isSidebarMode ? 'bg-transparent border-t border-border/40' : 'bg-muted/10')}>
<div className="px-3 py-1.5">
<AnimatedTabs<ActionTab>
value={actionTab}
@@ -1704,6 +1743,7 @@ export const GitView: React.FC<GitViewProps> = ({ mode = 'full' }) => {
variant="plain"
maxListHeightClassName="max-h-[40vh]"
changeEntries={changeEntries}
onVisiblePathsChange={setVisibleChangePaths}
selectedPaths={selectedPaths}
diffStats={status?.diffStats}
revertingPaths={revertingPaths}