History

Optimize sidebar sorting and favicon reuse

- Avoid cloning thread arrays before sorting - Cache loaded project favicons across sidebar renders - Update sidebar sort labels for clearer wording

Julius Marminge committed 7 months ago

Commit d9ef0ca1 parent eac49092 files · +15 −10Browse files

Unified · Split

2 files
  1. apps/web/src/components/Sidebar.logic.ts +1 −1
  2. apps/web/src/components/Sidebar.tsx +14 −9
apps/web/src/components/Sidebar.logic.ts+1−1
@@ -224,7 +224,7 @@ export function getThreadSortTimestamp( · 223 unmodified lines
224export function sortThreadsForSidebar<224export function sortThreadsForSidebar<
225 T extends Pick<Thread, "id" | "createdAt" | "updatedAt" | "messages">,225 T extends Pick<Thread, "id" | "createdAt" | "updatedAt" | "messages">,
226>(threads: readonly T[], sortOrder: SidebarThreadSortOrder): T[] {226>(threads: readonly T[], sortOrder: SidebarThreadSortOrder): T[] {
227 return [...threads].toSorted((left, right) => {227 return threads.toSorted((left, right) => {
228 const byTimestamp =228 const byTimestamp =
229 getThreadSortTimestamp(right, sortOrder) - getThreadSortTimestamp(left, sortOrder);229 getThreadSortTimestamp(right, sortOrder) - getThreadSortTimestamp(left, sortOrder);
230 if (byTimestamp !== 0) return byTimestamp;230 if (byTimestamp !== 0) return byTimestamp;
apps/web/src/components/Sidebar.tsx+14−9
@@ -105,17 +105,18 @@ const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = []; · 104 unmodified lines
105const THREAD_PREVIEW_LIMIT = 6;105const THREAD_PREVIEW_LIMIT = 6;
106const SIDEBAR_SORT_LABELS: Record<SidebarProjectSortOrder, string> = {106const SIDEBAR_SORT_LABELS: Record<SidebarProjectSortOrder, string> = {
107 updated_at: "Last user message",107 updated_at: "Last user message",
108 created_at: "Created",108 created_at: "Created at",
109 manual: "Manual",109 manual: "Manual",
110};110};
111const SIDEBAR_THREAD_SORT_LABELS: Record<SidebarThreadSortOrder, string> = {111const SIDEBAR_THREAD_SORT_LABELS: Record<SidebarThreadSortOrder, string> = {
112 updated_at: "Last user message",112 updated_at: "Last user message",
113 created_at: "Created",113 created_at: "Created at",
114};114};
115const SIDEBAR_LIST_ANIMATION_OPTIONS = {115const SIDEBAR_LIST_ANIMATION_OPTIONS = {
116 duration: 180,116 duration: 180,
117 easing: "ease-out",117 easing: "ease-out",
118} as const;118} as const;
119const loadedProjectFaviconSrcs = new Set<string>();
119 120
120function formatRelativeTime(iso: string): string {121function formatRelativeTime(iso: string): string {
121 const diff = Date.now() - new Date(iso).getTime();122 const diff = Date.now() - new Date(iso).getTime();
@@ -226,9 +227,10 @@ function getServerHttpOrigin(): string { · 104 unmodified lines
226const serverHttpOrigin = getServerHttpOrigin();227const serverHttpOrigin = getServerHttpOrigin();
227 228
228function ProjectFavicon({ cwd }: { cwd: string }) {229function ProjectFavicon({ cwd }: { cwd: string }) {
229 const [status, setStatus] = useState<"loading" | "loaded" | "error">("loading");
230
231 const src = `${serverHttpOrigin}/api/project-favicon?cwd=${encodeURIComponent(cwd)}`;230 const src = `${serverHttpOrigin}/api/project-favicon?cwd=${encodeURIComponent(cwd)}`;
231 const [status, setStatus] = useState<"loading" | "loaded" | "error">(() =>
232 loadedProjectFaviconSrcs.has(src) ? "loaded" : "loading",
233 );
232 234
233 if (status === "error") {235 if (status === "error") {
234 return <FolderIcon className="size-3.5 shrink-0 text-muted-foreground/50" />;236 return <FolderIcon className="size-3.5 shrink-0 text-muted-foreground/50" />;
@@ -239,7 +241,10 @@ function ProjectFavicon({ cwd }: { cwd: string }) { · 4 unmodified lines
239 src={src}241 src={src}
240 alt=""242 alt=""
241 className={`size-3.5 shrink-0 rounded-sm object-contain ${status === "loading" ? "hidden" : ""}`}243 className={`size-3.5 shrink-0 rounded-sm object-contain ${status === "loading" ? "hidden" : ""}`}
242 onLoad={() => setStatus("loaded")}244 onLoad={() => {
245 loadedProjectFaviconSrcs.add(src);
246 setStatus("loaded");
247 }}
243 onError={() => setStatus("error")}248 onError={() => setStatus("error")}
244 />249 />
245 );250 );
@@ -275,7 +280,7 @@ function ProjectSortMenu({ · 29 unmodified lines
275 </Tooltip>280 </Tooltip>
276 <MenuPopup align="end" side="bottom" className="min-w-44">281 <MenuPopup align="end" side="bottom" className="min-w-44">
277 <MenuGroup>282 <MenuGroup>
278 <div className="px-2 py-1 text-[11px] font-medium text-muted-foreground">283 <div className="px-2 py-1 sm:text-xs font-medium text-muted-foreground">
279 Sort projects284 Sort projects
280 </div>285 </div>
281 <MenuRadioGroup286 <MenuRadioGroup
@@ -286,7 +291,7 @@ function ProjectSortMenu({ · 4 unmodified lines
286 >291 >
287 {(Object.entries(SIDEBAR_SORT_LABELS) as Array<[SidebarProjectSortOrder, string]>).map(292 {(Object.entries(SIDEBAR_SORT_LABELS) as Array<[SidebarProjectSortOrder, string]>).map(
288 ([value, label]) => (293 ([value, label]) => (
289 <MenuRadioItem key={value} value={value} className="min-h-7 py-1 text-xs">294 <MenuRadioItem key={value} value={value} className="min-h-7 py-1 sm:text-xs">
290 {label}295 {label}
291 </MenuRadioItem>296 </MenuRadioItem>
292 ),297 ),
@@ -294,7 +299,7 @@ function ProjectSortMenu({ · 1 unmodified line
294 </MenuRadioGroup>299 </MenuRadioGroup>
295 </MenuGroup>300 </MenuGroup>
296 <MenuGroup>301 <MenuGroup>
297 <div className="px-2 pt-2 pb-1 text-[11px] font-medium text-muted-foreground">302 <div className="px-2 pt-2 pb-1 sm:text-xs font-medium text-muted-foreground">
298 Sort threads303 Sort threads
299 </div>304 </div>
300 <MenuRadioGroup305 <MenuRadioGroup
@@ -306,7 +311,7 @@ function ProjectSortMenu({ · 5 unmodified lines
306 {(311 {(
307 Object.entries(SIDEBAR_THREAD_SORT_LABELS) as Array<[SidebarThreadSortOrder, string]>312 Object.entries(SIDEBAR_THREAD_SORT_LABELS) as Array<[SidebarThreadSortOrder, string]>
308 ).map(([value, label]) => (313 ).map(([value, label]) => (
309 <MenuRadioItem key={value} value={value} className="min-h-7 py-1 text-xs">314 <MenuRadioItem key={value} value={value} className="min-h-7 py-1 sm:text-xs">
310 {label}315 {label}
311 </MenuRadioItem>316 </MenuRadioItem>
312 ))}317 ))}