perf(studio): speed up nitro scan and stop the cleanup panel freezing
CI / check (push) Successful in 4m13s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m0s

This commit is contained in:
openhands committed 2026-09-19 11:39:19 +02:00
1 parent 91e649398c
commit c916e42572
2 files changed
+77 -42

No files matched your search

@@ -63,6 +63,13 @@ type AssetKind = "nitro" | "swf" | "icon";
const DAY_MS = 24 * 60 * 60 * 1000;
/**
* How many rows a group renders at once. Directories can contain tens of
* thousands of leftover files; mounting every row as DOM nodes freezes the
* tab, so rows are added in chunks via a "load more" button.
*/
const RENDER_CHUNK = 500;
function ageLabel(lastModified: number): string {
if (!lastModified) return "age unknown";
const days = Math.floor((Date.now() - lastModified) / DAY_MS);
@@ -103,8 +110,11 @@ function CleanupGroup({
onToggle: (fileName: string) => void;
onToggleAll: (names: string[]) => void;
}) {
const [shown, setShown] = useState(RENDER_CHUNK);
const checked = entries.filter((e) => selected.has(e.fileName));
const allChecked = entries.length > 0 && checked.length === entries.length;
const visibleEntries = entries.slice(0, shown);
const toneClass =
tone === "error"
? "border-[var(--admin-error-border)] bg-[var(--admin-error-subtle)]"
@@ -148,45 +158,63 @@ function CleanupGroup({
No {label.toLowerCase()} found
</p>
) : (
<ul className="max-h-80 space-y-1 overflow-auto rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-2">
{entries.map((entry) => (
<li
key={entry.fileName}
className="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-[var(--admin-accent)]/5"
>
<Checkbox
style={{ display: "inline-flex", width: 16, height: 16 }}
className="border-[var(--admin-border)] bg-[var(--admin-surface)] data-checked:border-[var(--admin-accent)] data-checked:bg-[var(--admin-accent)] data-checked:text-[var(--admin-accent-foreground)]"
checked={selected.has(entry.fileName)}
<>
<ul className="max-h-80 space-y-1 overflow-auto rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] p-2">
{visibleEntries.map((entry) => (
<li
key={entry.fileName}
className="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-[var(--admin-accent)]/5"
>
<Checkbox
style={{ display: "inline-flex", width: 16, height: 16 }}
className="border-[var(--admin-border)] bg-[var(--admin-surface)] data-checked:border-[var(--admin-accent)] data-checked:bg-[var(--admin-accent)] data-checked:text-[var(--admin-accent-foreground)]"
checked={selected.has(entry.fileName)}
disabled={disabled}
onCheckedChange={() => onToggle(entry.fileName)}
aria-label={`Select ${entry.fileName}`}
/>
<code className="min-w-0 flex-1 truncate font-mono text-xs">
{entry.fileName}
</code>
<span className="shrink-0 text-xs text-[var(--admin-text-muted)]">
{formatBytes(entry.size)}
</span>
<span className="shrink-0 text-xs text-[var(--admin-text-muted)]">
{ageLabel(entry.lastModified)}
</span>
{entry.error && (
<span
title={entry.error}
className="hidden max-w-[320px] truncate text-xs text-[var(--admin-error)] lg:block"
>
{entry.error}
</span>
)}
{entry.dirs.length > 1 && (
<span className="shrink-0 rounded bg-[var(--admin-accent)]/10 px-1.5 py-0.5 text-[10px] font-medium text-[var(--admin-text-muted)]">
{entry.dirs.length} copies
</span>
)}
</li>
))}
</ul>
{entries.length > shown && (
<div className="flex items-center justify-between px-2 text-xs text-[var(--admin-text-muted)]">
<span>
Showing {shown} of {entries.length}
</span>
<Button
type="button"
variant="outline"
size="sm"
disabled={disabled}
onCheckedChange={() => onToggle(entry.fileName)}
aria-label={`Select ${entry.fileName}`}
/>
<code className="min-w-0 flex-1 truncate font-mono text-xs">
{entry.fileName}
</code>
<span className="shrink-0 text-xs text-[var(--admin-text-muted)]">
{formatBytes(entry.size)}
</span>
<span className="shrink-0 text-xs text-[var(--admin-text-muted)]">
{ageLabel(entry.lastModified)}
</span>
{entry.error && (
<span
title={entry.error}
className="hidden max-w-[320px] truncate text-xs text-[var(--admin-error)] lg:block"
>
{entry.error}
</span>
)}
{entry.dirs.length > 1 && (
<span className="shrink-0 rounded bg-[var(--admin-accent)]/10 px-1.5 py-0.5 text-[10px] font-medium text-[var(--admin-text-muted)]">
{entry.dirs.length} copies
</span>
)}
</li>
))}
</ul>
onClick={() => setShown((s) => s + RENDER_CHUNK)}
>
Load {Math.min(RENDER_CHUNK, entries.length - shown)} more
</Button>
</div>
)}
</>
)}
</section>
);