- Avoid cloning thread arrays before sorting - Cache loaded project favicons across sidebar renders - Update sidebar sort labels for clearer wording
Unified · Split
| @@ -224,7 +224,7 @@ export function getThreadSortTimestamp( · 223 unmodified lines | ||
| 224 | 224 | export function sortThreadsForSidebar< |
| 225 | 225 | T extends Pick<Thread, "id" | "createdAt" | "updatedAt" | "messages">, |
| 226 | 226 | >(threads: readonly T[], sortOrder: SidebarThreadSortOrder): T[] { |
| 227 | − return [...threads].toSorted((left, right) => { | |
| 227 | + return threads.toSorted((left, right) => { | |
| 228 | 228 | const byTimestamp = |
| 229 | 229 | getThreadSortTimestamp(right, sortOrder) - getThreadSortTimestamp(left, sortOrder); |
| 230 | 230 | if (byTimestamp !== 0) return byTimestamp; |
| @@ -105,17 +105,18 @@ const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = []; · 104 unmodified lines | ||
| 105 | 105 | const THREAD_PREVIEW_LIMIT = 6; |
| 106 | 106 | const SIDEBAR_SORT_LABELS: Record<SidebarProjectSortOrder, string> = { |
| 107 | 107 | updated_at: "Last user message", |
| 108 | − created_at: "Created", | |
| 108 | + created_at: "Created at", | |
| 109 | 109 | manual: "Manual", |
| 110 | 110 | }; |
| 111 | 111 | const SIDEBAR_THREAD_SORT_LABELS: Record<SidebarThreadSortOrder, string> = { |
| 112 | 112 | updated_at: "Last user message", |
| 113 | − created_at: "Created", | |
| 113 | + created_at: "Created at", | |
| 114 | 114 | }; |
| 115 | 115 | const SIDEBAR_LIST_ANIMATION_OPTIONS = { |
| 116 | 116 | duration: 180, |
| 117 | 117 | easing: "ease-out", |
| 118 | 118 | } as const; |
| 119 | +const loadedProjectFaviconSrcs = new Set<string>(); | |
| 119 | 120 | |
| 120 | 121 | function formatRelativeTime(iso: string): string { |
| 121 | 122 | const diff = Date.now() - new Date(iso).getTime(); |
| @@ -226,9 +227,10 @@ function getServerHttpOrigin(): string { · 104 unmodified lines | ||
| 226 | 227 | const serverHttpOrigin = getServerHttpOrigin(); |
| 227 | 228 | |
| 228 | 229 | function ProjectFavicon({ cwd }: { cwd: string }) { |
| 229 | − const [status, setStatus] = useState<"loading" | "loaded" | "error">("loading"); | |
| 230 | − | |
| 231 | 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 | 235 | if (status === "error") { |
| 234 | 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 | 241 | src={src} |
| 240 | 242 | alt="" |
| 241 | 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 | 248 | onError={() => setStatus("error")} |
| 244 | 249 | /> |
| 245 | 250 | ); |
| @@ -275,7 +280,7 @@ function ProjectSortMenu({ · 29 unmodified lines | ||
| 275 | 280 | </Tooltip> |
| 276 | 281 | <MenuPopup align="end" side="bottom" className="min-w-44"> |
| 277 | 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 | 284 | Sort projects |
| 280 | 285 | </div> |
| 281 | 286 | <MenuRadioGroup |
| @@ -286,7 +291,7 @@ function ProjectSortMenu({ · 4 unmodified lines | ||
| 286 | 291 | > |
| 287 | 292 | {(Object.entries(SIDEBAR_SORT_LABELS) as Array<[SidebarProjectSortOrder, string]>).map( |
| 288 | 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 | 295 | {label} |
| 291 | 296 | </MenuRadioItem> |
| 292 | 297 | ), |
| @@ -294,7 +299,7 @@ function ProjectSortMenu({ · 1 unmodified line | ||
| 294 | 299 | </MenuRadioGroup> |
| 295 | 300 | </MenuGroup> |
| 296 | 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 | 303 | Sort threads |
| 299 | 304 | </div> |
| 300 | 305 | <MenuRadioGroup |
| @@ -306,7 +311,7 @@ function ProjectSortMenu({ · 5 unmodified lines | ||
| 306 | 311 | {( |
| 307 | 312 | Object.entries(SIDEBAR_THREAD_SORT_LABELS) as Array<[SidebarThreadSortOrder, string]> |
| 308 | 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 | 315 | {label} |
| 311 | 316 | </MenuRadioItem> |
| 312 | 317 | ))} |