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:
committed by
GitHub
parent
4c69bccf56
commit
10851bd7ac
@@ -24,7 +24,7 @@ export const AIHighlightsBox: React.FC<AIHighlightsBoxProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2 rounded-xl border border-border/60 bg-background/60 px-3 py-2">
|
||||
<div className="space-y-2 rounded-xl border border-border/60 bg-transparent px-3 py-2">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="typography-micro text-muted-foreground">AI highlights</p>
|
||||
<Tooltip delayDuration={1000}>
|
||||
|
||||
@@ -96,14 +96,13 @@ export const ChangeRow = React.memo<ChangeRowProps>(function ChangeRow({
|
||||
);
|
||||
|
||||
return (
|
||||
<li>
|
||||
<div
|
||||
className="group flex items-center gap-2 px-3 py-1.5 hover:bg-sidebar/40 cursor-pointer"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onViewDiff}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<div
|
||||
className="group flex items-center gap-2 px-3 py-1.5 hover:bg-sidebar/40 cursor-pointer"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onViewDiff}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggleClick}
|
||||
@@ -175,7 +174,6 @@ export const ChangeRow = React.memo<ChangeRowProps>(function ChangeRow({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={8}>Revert changes</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ScrollShadow } from '@/components/ui/ScrollShadow';
|
||||
import { OverlayScrollbar } from '@/components/ui/OverlayScrollbar';
|
||||
@@ -18,8 +19,12 @@ interface ChangesSectionProps {
|
||||
onRevertFile: (path: string) => void;
|
||||
variant?: 'framed' | 'plain';
|
||||
maxListHeightClassName?: string;
|
||||
onVisiblePathsChange?: (paths: string[]) => void;
|
||||
}
|
||||
|
||||
const CHANGE_LIST_VIRTUALIZE_THRESHOLD = 120;
|
||||
const CHANGE_ROW_ESTIMATE_PX = 34;
|
||||
|
||||
export const ChangesSection: React.FC<ChangesSectionProps> = ({
|
||||
changeEntries,
|
||||
selectedPaths,
|
||||
@@ -32,10 +37,43 @@ export const ChangesSection: React.FC<ChangesSectionProps> = ({
|
||||
onRevertFile,
|
||||
variant = 'framed',
|
||||
maxListHeightClassName,
|
||||
onVisiblePathsChange,
|
||||
}) => {
|
||||
const scrollRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const selectedCount = selectedPaths.size;
|
||||
const totalCount = changeEntries.length;
|
||||
const shouldVirtualize = totalCount >= CHANGE_LIST_VIRTUALIZE_THRESHOLD;
|
||||
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: totalCount,
|
||||
getScrollElement: () => scrollRef.current,
|
||||
estimateSize: () => CHANGE_ROW_ESTIMATE_PX,
|
||||
overscan: 10,
|
||||
enabled: shouldVirtualize,
|
||||
});
|
||||
|
||||
const virtualRows = React.useMemo(
|
||||
() => (shouldVirtualize ? rowVirtualizer.getVirtualItems() : []),
|
||||
[rowVirtualizer, shouldVirtualize],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!onVisiblePathsChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (totalCount === 0) {
|
||||
onVisiblePathsChange([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldVirtualize) {
|
||||
onVisiblePathsChange(changeEntries.slice(0, Math.min(30, totalCount)).map((entry) => entry.path));
|
||||
return;
|
||||
}
|
||||
|
||||
onVisiblePathsChange(virtualRows.map((row) => changeEntries[row.index]?.path).filter((value): value is string => Boolean(value)));
|
||||
}, [changeEntries, onVisiblePathsChange, shouldVirtualize, totalCount, virtualRows]);
|
||||
|
||||
const containerClassName =
|
||||
variant === 'framed'
|
||||
@@ -86,20 +124,54 @@ export const ChangesSection: React.FC<ChangesSectionProps> = ({
|
||||
ref={scrollRef}
|
||||
className="overlay-scrollbar-target overlay-scrollbar-container flex-1 min-h-0 w-full overflow-y-auto overflow-x-hidden"
|
||||
>
|
||||
<ul className="divide-y divide-border/60">
|
||||
{changeEntries.map((file) => (
|
||||
<ChangeRow
|
||||
key={file.path}
|
||||
file={file}
|
||||
checked={selectedPaths.has(file.path)}
|
||||
stats={diffStats?.[file.path]}
|
||||
onToggle={() => onToggleFile(file.path)}
|
||||
onViewDiff={() => onViewDiff(file.path)}
|
||||
onRevert={() => onRevertFile(file.path)}
|
||||
isReverting={revertingPaths.has(file.path)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{shouldVirtualize ? (
|
||||
<div
|
||||
className="relative w-full divide-y divide-border/60"
|
||||
style={{ height: `${rowVirtualizer.getTotalSize()}px` }}
|
||||
>
|
||||
{virtualRows.map((row) => {
|
||||
const file = changeEntries[row.index];
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={file.path}
|
||||
ref={rowVirtualizer.measureElement}
|
||||
data-index={row.index}
|
||||
className="absolute left-0 top-0 w-full"
|
||||
style={{ transform: `translateY(${row.start}px)` }}
|
||||
>
|
||||
<ChangeRow
|
||||
file={file}
|
||||
checked={selectedPaths.has(file.path)}
|
||||
stats={diffStats?.[file.path]}
|
||||
onToggle={() => onToggleFile(file.path)}
|
||||
onViewDiff={() => onViewDiff(file.path)}
|
||||
onRevert={() => onRevertFile(file.path)}
|
||||
isReverting={revertingPaths.has(file.path)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="divide-y divide-border/60" role="list" aria-label="Changed files">
|
||||
{changeEntries.map((file) => (
|
||||
<ChangeRow
|
||||
key={file.path}
|
||||
file={file}
|
||||
checked={selectedPaths.has(file.path)}
|
||||
stats={diffStats?.[file.path]}
|
||||
onToggle={() => onToggleFile(file.path)}
|
||||
onViewDiff={() => onViewDiff(file.path)}
|
||||
onRevert={() => onRevertFile(file.path)}
|
||||
isReverting={revertingPaths.has(file.path)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollShadow>
|
||||
<OverlayScrollbar containerRef={scrollRef} disableHorizontal />
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@ export const CommitInput: React.FC<CommitInputProps> = ({
|
||||
autoCapitalize={hasTouchInput ? 'sentences' : 'off'}
|
||||
spellCheck={hasTouchInput ? true : false}
|
||||
className={cn(
|
||||
'rounded-lg bg-background/80 resize-none overflow-y-auto',
|
||||
'rounded-lg bg-transparent resize-none overflow-y-auto',
|
||||
disabled && 'opacity-50'
|
||||
)}
|
||||
style={{ minHeight: MIN_HEIGHT, maxHeight: MAX_HEIGHT }}
|
||||
|
||||
@@ -285,7 +285,7 @@ export const GitHeader: React.FC<GitHeaderProps> = ({
|
||||
|
||||
if (useTwoRowHeader) {
|
||||
return (
|
||||
<header className="@container/git-header border-b border-border/40 px-3 py-2 bg-background">
|
||||
<header className={`@container/git-header border-b border-border/40 px-3 py-2 ${isSidebarMode ? 'bg-transparent' : 'bg-background'}`}>
|
||||
<div className="flex items-center justify-between gap-2 min-w-0">
|
||||
<div className="min-w-0 flex-1">
|
||||
{isWorktreeMode ? (
|
||||
@@ -320,7 +320,7 @@ export const GitHeader: React.FC<GitHeaderProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="@container/git-header flex items-center gap-2 border-b border-border/40 px-3 py-2 bg-background">
|
||||
<header className={`@container/git-header flex items-center gap-2 border-b border-border/40 px-3 py-2 ${isSidebarMode ? 'bg-transparent' : 'bg-background'}`}>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
|
||||
{isWorktreeMode ? (
|
||||
<WorktreeBranchDisplay
|
||||
|
||||
@@ -698,7 +698,7 @@ export const PullRequestSection: React.FC<{
|
||||
</div>
|
||||
) : null}
|
||||
{run.output?.text ? (
|
||||
<div className="rounded border border-border/40 bg-background/40 px-2 py-2 typography-micro text-muted-foreground whitespace-pre-wrap max-h-48 overflow-y-auto">
|
||||
<div className="rounded border border-border/40 bg-transparent px-2 py-2 typography-micro text-muted-foreground whitespace-pre-wrap max-h-48 overflow-y-auto">
|
||||
{run.output.text}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -776,7 +776,7 @@ export const PullRequestSection: React.FC<{
|
||||
{step.conclusion ? <span className="ml-auto flex-shrink-0">{step.conclusion}</span> : null}
|
||||
</button>
|
||||
<CollapsibleContent>
|
||||
<div className="ml-6 mt-1 rounded border border-border/40 bg-background/40 px-2 py-2 typography-micro text-muted-foreground space-y-1">
|
||||
<div className="ml-6 mt-1 rounded border border-border/40 bg-transparent px-2 py-2 typography-micro text-muted-foreground space-y-1">
|
||||
{typeof step.number === 'number' ? <div>Step: {step.number}</div> : null}
|
||||
{step.status ? <div>Status: {step.status}</div> : null}
|
||||
{step.conclusion ? <div>Conclusion: {step.conclusion}</div> : null}
|
||||
@@ -1304,7 +1304,7 @@ export const PullRequestSection: React.FC<{
|
||||
|
||||
const containerClassName =
|
||||
variant === 'framed'
|
||||
? 'rounded-xl border border-border/60 bg-background/70 overflow-hidden'
|
||||
? 'rounded-xl border border-border/60 bg-transparent overflow-hidden'
|
||||
: 'border-0 bg-transparent rounded-none';
|
||||
const headerClassName =
|
||||
variant === 'framed'
|
||||
|
||||
Reference in New Issue
Block a user