Files
Oxicloud/frontend/src/lib/components/round13.bench.test.ts
T
Claude f58d72a780 perf: round 13 — grouped-view virtualization, notification/login query narrowing, HTTP dedup, locale precompute
Benchmark-gated (BEFORE/AFTER + equivalence/safety gate per change), same
discipline as rounds 2-12. Full write-up in benches/ROUND13.md.

Shipped:
- V1 Grouped views windowed (files route + ResourceList). The grid arm was
  the last unwindowed path (trash is grouped-by-default in grid): each
  swimlane now feeds its own VirtualList, outer container a flex stack.
  vitest gate: 800-item grouped grid mounts <120 .file-item (was 800).
- Q1 get_users_by_ids drops the <=512 KiB avatar image + ui_preferences
  JSONB (notification path never reads them). 30-member fan-out 8.60 ->
  0.25 ms (34.3x), ~7.7 MB off the wire.
- Q2 Login provisioning is_empty() -> SELECT EXISTS for calendar + address
  book (every login). 0.193 -> 0.170 ms, widens with owned-row count.
- Q3 Recent-access prunes only when the upsert inserted (RETURNING xmax=0)
  — a re-access can't grow the set. 0.567 -> 0.324 ms (1.75x).
- L1 Locale supported-codes precomputed once vs rebuilt per anonymous
  request. 616 -> 17.3 ns (35.7x), 18 -> 1 allocs.
- H1 Duplicate /api TraceLayer removed (global stack already wraps it).
  1.86 -> 1.42 us/request, -6 allocs.
- H2 client_ip span field: borrow-only ClientIpDisplay vs owned String.
  187 -> 173 ns, -1 alloc.

Not shipped (discipline): the "media hooks read the blob 3x" lead was a
correctness bug, not a perf dup — the raw-path metadata/faces readers
resolve only for local+unencrypted+single-chunk blobs and silently produce
nothing otherwise. Flagged for maintainers; routing through read_blob_bytes
is a correctness fix (perf-neutral-to-negative), not a benchmark-gated
perf change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfidAJD5AHw23jtvBUNamB
2026-07-19 08:17:48 +00:00

125 lines
3.8 KiB
TypeScript

// Round-13 §V1 — grouped views are windowed (benches/ROUND13.md).
//
// Before this round, the grouped GRID path mounted EVERY card:
// `{#each sections}{#each section.rows}{@render row}` with no windowing
// (the grouped-by-default trash grid, and the files route's grouped grid,
// were the last unwindowed paths). Now each swimlane feeds its own windowed
// <VirtualList> — a flex stack of (header + windowed card grid) per section
// — so only a viewport-bounded slice of `.file-item` cards is realized,
// regardless of group size.
//
// Gate: render the real ResourceList in grouped GRID mode with N=800 items
// in one bucket and assert the mounted card count is viewport-bounded, not
// N. jsdom does no layout, so VirtualList's visible band is a small constant
// — the same lever the round-12 files page test documents.
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render } from '@testing-library/svelte';
vi.mock('$lib/api/endpoints/files', () => ({
fileThumbnailUrl: () => '/thumb',
thumbSizeForView: () => 'preview' as const
}));
import ResourceList from './ResourceList.svelte';
import type { GroupByDef } from './ResourceList.svelte';
import { files as filesStore } from '$lib/stores/files.svelte';
interface TestFile {
category: string;
created_at: number;
icon_class: string;
icon_special_class: string;
id: string;
mime_type: string;
modified_at: number;
name: string;
created_by: string;
updated_by: string;
folder_id: string;
path: string;
size: number;
size_formatted: string;
sort_date: number;
etag: string;
content_hash: string;
}
function fileItem(i: number): TestFile {
return {
category: 'Document',
created_at: 0,
icon_class: 'fa-file',
icon_special_class: '',
id: `f${i}`,
mime_type: 'text/plain',
modified_at: 0,
name: `file-${i}.txt`,
created_by: 'me',
updated_by: 'me',
folder_id: 'home',
path: `/file-${i}.txt`,
size: 4,
size_formatted: '4 B',
sort_date: 0,
etag: 'e',
content_hash: 'h'
};
}
// Single bucket → one big swimlane (the worst case the old grid mounted whole).
const groupBys: GroupByDef[] = [
{
key: 'type',
label: 'Type',
orderBy: 'name',
bucketOf: (item) => (item as TestFile).category ?? 'other',
labelOf: (k) => k
}
];
describe('round13 §V1 — grouped grid is windowed', () => {
beforeEach(() => {
filesStore.viewMode = 'grid';
});
it('mounts a viewport-bounded slice of cards, not all N, in grouped grid', () => {
const N = 800;
const items = Array.from({ length: N }, (_, i) => fileItem(i));
const { container } = render(ResourceList, {
props: {
title: 'Round13',
items,
groupBys,
groupBy: 'type',
selectable: true,
actions: undefined
}
});
const mounted = container.querySelectorAll('.file-item').length;
// A swimlane header confirms we are on the grouped path.
expect(container.querySelectorAll('.rl-swimlane-header').length).toBeGreaterThan(0);
// Windowed: the visible band is viewport+overscan bounded, far below N.
// (The pre-fix grid-grouped path mounted all 800.)
expect(mounted).toBeGreaterThan(0);
expect(mounted).toBeLessThan(120);
expect(mounted).toBeLessThan(N / 4);
});
it('full scroll height is still reserved (windowing spacer, not truncation)', () => {
const N = 800;
const items = Array.from({ length: N }, (_, i) => fileItem(i));
const { container } = render(ResourceList, {
props: { title: 'Round13', items, groupBys, groupBy: 'type', selectable: true }
});
// The VirtualList reserves total height via its `.vlist` spacer so the
// scrollbar / end-of-list sentinel keep working — height must scale with
// N, proving cards weren't simply dropped.
const vlist = container.querySelector('.vlist') as HTMLElement | null;
expect(vlist).not.toBeNull();
const reserved = parseFloat(vlist!.style.height || '0');
expect(reserved).toBeGreaterThan(1000);
});
});