fix(ui): preserve upstream behavior after performance rebase

This commit is contained in:
c_w_xiaohei
2026-08-26 00:42:36 +08:00
parent 9b9d7069c7
commit 532bdab5e8
34 changed files with 752 additions and 361 deletions
@@ -14,7 +14,7 @@ import { formatDirectoryName, formatPathForDisplay } from '@/lib/utils';
import type { SessionGroup } from '../types';
import { ProjectHeaderIdentity, SortableGroupItem, SortableProjectItem } from './sortableItems';
import { SessionGroupSection, type SessionGroupSectionProps } from './SessionGroupSection';
import { buildGroupRenderDescriptors, type ProjectSection } from './sessionProjectRender';
import { buildGroupRenderDescriptors, selectRenderedProjectSections, type ProjectSection } from './sessionProjectRender';
import { formatProjectLabel } from '../utils';
import { useI18n } from '@/lib/i18n';
import type { ProjectSortOrder } from '@/stores/useSessionDisplayStore';
@@ -78,6 +78,8 @@ type SessionProjectScrollerModel = {
sectionsForRender: ProjectSection[];
projectSections: ProjectSection[];
activeProjectId: string | null;
singleProjectMode: boolean;
singleProjectId: string | null;
emptyState: React.ReactNode;
searchEmptyState: React.ReactNode;
projectRepoStatus: Map<string, boolean | null>;
@@ -114,6 +116,7 @@ type SessionProjectScrollerActions = {
reorderProjects: (fromIndex: number, toIndex: number) => void;
setGroupOrderByProject: React.Dispatch<React.SetStateAction<Map<string, string[]>>>;
renderProjectStatusIndicator?: (projectId: string, groups: SessionGroup[]) => React.ReactNode;
setSingleProjectId: (id: string) => void;
};
type Props = {
@@ -139,7 +142,7 @@ function SessionProjectScrollerComponent(props: Props): React.ReactNode {
const { t } = useI18n();
const { model, view, actions } = props;
const isInlineEditing = model.state.editingId !== null;
const enableStickyFade = view.isDesktopShellRuntime && view.stickyZoneHeaders;
const enableStickyFade = view.isDesktopShellRuntime && view.stickyZoneHeaders && !model.singleProjectMode;
const projectSensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
@@ -180,7 +183,12 @@ function SessionProjectScrollerComponent(props: Props): React.ReactNode {
event.preventDefault();
event.stopPropagation();
}, []);
const hasProjectScroller = model.projectSections.length > 0 && model.sectionsForRender.length > 0;
const renderedSections = selectRenderedProjectSections(
model.sectionsForRender,
model.singleProjectMode,
model.singleProjectId,
);
const hasProjectScroller = model.projectSections.length > 0 && renderedSections.length > 0;
React.useLayoutEffect(() => {
if (enableStickyFade && hasProjectScroller && scrollContainerRef.current) {
syncTopFade(scrollContainerRef.current);
@@ -199,8 +207,17 @@ function SessionProjectScrollerComponent(props: Props): React.ReactNode {
// ready in the same frame; the observer then corrects it. When shared sessions
// lead the list, the Recent fallback below owns the top instead of a project.
const leadingProject =
stuckProject ?? (model.hasSharedSessions ? null : model.sectionsForRender[0]?.project ?? null);
stuckProject ?? (model.hasSharedSessions ? null : renderedSections[0]?.project ?? null);
const leadingProjectLabel = leadingProject ? getProjectLabel(leadingProject, view.homeDirectory) : null;
const projectPickerOptions = React.useMemo(() => model.projectSections.map((section) => ({
id: section.project.id,
projectLabel: getProjectLabel(section.project, view.homeDirectory),
projectDescription: formatPathForDisplay(section.project.normalizedPath, view.homeDirectory),
projectIcon: section.project.icon,
projectColor: section.project.color,
projectIconImage: section.project.iconImage,
projectIconBackground: section.project.iconBackground,
})), [model.projectSections, view.homeDirectory]);
if (model.projectSections.length === 0) {
return <ScrollableOverlay useScrollShadow scrollShadowSize={96} outerClassName="flex-1 min-h-0" className="space-y-1 pb-1 pl-2.5 pr-2">{model.topContent}{model.emptyState}</ScrollableOverlay>;
@@ -237,7 +254,7 @@ function SessionProjectScrollerComponent(props: Props): React.ReactNode {
{view.showOnlyMainWorkspace ? (
<div className="space-y-[0.6rem] py-1">
{(() => {
const activeSection = model.sectionsForRender.find((section) => section.project.id === model.activeProjectId) ?? model.sectionsForRender[0];
const activeSection = renderedSections.find((section) => section.project.id === model.activeProjectId) ?? renderedSections[0];
if (!activeSection) {
return view.hasSessionSearchQuery ? model.searchEmptyState : model.emptyState;
}
@@ -270,20 +287,20 @@ function SessionProjectScrollerComponent(props: Props): React.ReactNode {
actions.reorderProjects(oldIndex, newIndex);
}}
>
<SortableContext items={model.sectionsForRender.map((section) => section.project.id)} strategy={verticalListSortingStrategy}>
{model.sectionsForRender.map((section) => {
<SortableContext items={renderedSections.map((section) => section.project.id)} strategy={verticalListSortingStrategy}>
{renderedSections.map((section) => {
const project = section.project;
const projectKey = project.id;
const projectLabel = getProjectLabel(project, view.homeDirectory);
const projectDescription = formatPathForDisplay(project.normalizedPath, view.homeDirectory);
const isCollapsed = view.collapsedProjects.has(projectKey);
const isCollapsed = model.singleProjectMode ? false : view.collapsedProjects.has(projectKey);
const isRepo = model.projectRepoStatus.get(projectKey);
return (
<SortableProjectItem
key={projectKey}
id={projectKey}
disabled={view.projectSortOrder !== 'manual'}
disabled={model.singleProjectMode || view.projectSortOrder !== 'manual'}
projectLabel={projectLabel}
projectDescription={projectDescription}
projectIcon={project.icon}
@@ -298,8 +315,10 @@ function SessionProjectScrollerComponent(props: Props): React.ReactNode {
alwaysShowActions={view.alwaysShowActions}
statusIndicator={isCollapsed ? actions.renderProjectStatusIndicator?.(projectKey, section.groups) : null}
openSidebarMenuKey={model.state.openSidebarMenuKey}
setOpenSidebarMenuKey={model.state.setOpenSidebarMenuKey}
onToggle={() => actions.toggleProject(projectKey)}
setOpenSidebarMenuKey={model.state.setOpenSidebarMenuKey}
projectPickerOptions={model.singleProjectMode ? projectPickerOptions : undefined}
onProjectSelect={model.singleProjectMode ? actions.setSingleProjectId : undefined}
onToggle={() => { if (!model.singleProjectMode) actions.toggleProject(projectKey); }}
onNewSession={() => {
if (projectKey !== model.activeProjectId) actions.setActiveProjectIdOnly(projectKey);
if (view.mobileVariant) actions.setSessionSwitcherOpen(false);