Merge pull request #625 from AtalayaLabs/claude/performance-optimization-analysis-raoezl

Round 16: incremental lanes/contextMap builders & alloc cuts
This commit is contained in:
Dionisio Pozo
2026-07-19 19:35:26 +02:00
committed by GitHub
20 changed files with 1388 additions and 123 deletions
+10 -6
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { SvelteSet } from 'svelte/reactivity';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import { primeContextPage } from '$lib/utils/listContext';
import Button from '$lib/components/Button.svelte';
import { useOwnerCache } from '$lib/composables/useOwnerCache.svelte';
import { errorToast } from '$lib/utils/errors';
@@ -52,11 +53,10 @@
// items on this page are favorites — pass every id in `favoriteIds`
// so the star widget lights up universally.
const items = $derived(raw.map((it) => it.resource as FileItem | FolderItem));
const contextMap = $derived(
new Map<string, ItemContext>(
raw.map((it) => [it.resource.id, { date: it.favorited_at } satisfies ItemContext])
)
);
// Persistent reactive map, primed per page in `load()` (benches/ROUND16.md §F2)
// instead of rebuilding a fresh Map that re-hashes the whole accumulated list
// on every infinite-scroll page. Mirrors the sibling `favoriteIds` SvelteSet.
const contextMap = new SvelteMap<string, ItemContext>();
// Persistent reactive set, updated in place per page (add the fresh page's
// ids; clear on reset) instead of rebuilding a brand-new SvelteSet over the
// whole accumulated list on every infinite-scroll page — that was O(N²)
@@ -117,6 +117,10 @@
// reset, then add only this page's ids (benches/ROUND14.md §F2).
if (reset) favoriteIds.clear();
for (const it of page.items) favoriteIds.add(it.resource.id);
primeContextPage(contextMap, reset, page.items, (it) => [
it.resource.id,
{ date: it.favorited_at }
]);
cursor = page.next_cursor;
void owners.resolve(page.items.map((i) => i.resource.created_by));
} catch (e) {
+10 -9
View File
@@ -5,7 +5,8 @@
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
import { SvelteSet } from 'svelte/reactivity';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import { primeContextPage } from '$lib/utils/listContext';
import { clearRecent, fetchRecentPage, type RecentResourceItem } from '$lib/api/endpoints/recent';
import {
addFavorite,
@@ -59,14 +60,10 @@
// shared `isDotfile` predicate purely for the empty-state message
// below (distinguishes "genuinely empty" from "everything filtered").
const items = $derived(raw.map((it) => it.resource as FileItem | FolderItem));
const contextMap = $derived(
new Map<string, ItemContext>(
raw.map((it) => [
it.resource.id,
{ date: it.accessed_at, ownerId: it.resource.updated_by ?? null } satisfies ItemContext
])
)
);
// Persistent reactive map, primed per page in `load()` (benches/ROUND16.md §F2)
// instead of rebuilding a fresh Map that re-hashes the whole accumulated list
// on every infinite-scroll page. Mirrors the sibling `favoriteIds` SvelteSet.
const contextMap = new SvelteMap<string, ItemContext>();
const hiddenCount = $derived(
preferences.hideDotfiles ? items.filter((i) => isDotfile(i.name)).length : 0
);
@@ -131,6 +128,10 @@
resourceTypes: ['file', 'folder']
});
raw = reset ? page.items : [...raw, ...page.items];
primeContextPage(contextMap, reset, page.items, (it) => [
it.resource.id,
{ date: it.accessed_at, ownerId: it.resource.updated_by ?? null }
]);
cursor = page.next_cursor;
void owners.resolve(page.items.map((i) => i.resource.updated_by));
} catch (e) {
@@ -1,5 +1,7 @@
<script lang="ts">
import { errorMessage } from '$lib/utils/errors';
import { SvelteMap } from 'svelte/reactivity';
import { primeContextPage } from '$lib/utils/listContext';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { onMount } from 'svelte';
@@ -43,14 +45,11 @@
// so the sharer shows up in the vignette (rather than the resource's
// intrinsic `created_by`, which is a stranger for grantees).
const items = $derived(fileFolderGrants.map((it) => it.resource as FileItem | FolderItem));
const contextMap = $derived(
new Map<string, ItemContext>(
fileFolderGrants.map((it) => [
it.resource.id,
{ date: it.granted_at, ownerId: it.granted_by ?? null } satisfies ItemContext
])
)
);
// Persistent reactive map, primed per page in `load()` (benches/ROUND16.md §F2)
// instead of rebuilding a fresh Map that re-hashes the whole accumulated list
// on every infinite-scroll page. Drives are skipped (they never reach the
// row UI), so the map covers exactly the displayed `fileFolderGrants`.
const contextMap = new SvelteMap<string, ItemContext>();
// Server-supported sort_by values (see grant_handler.rs:615):
// granted_at, granted_by, name, type
@@ -96,6 +95,11 @@
reverse: rev
});
raw = reset ? page.items : [...raw, ...page.items];
primeContextPage(contextMap, reset, page.items, (it) =>
it.resource_type === 'drive'
? null
: [it.resource.id, { date: it.granted_at, ownerId: it.granted_by ?? null }]
);
cursor = page.next_cursor;
// Warm the sharer-name cache so the "Shared by" group headers
// show real names instead of UUIDs.
+52 -40
View File
@@ -28,6 +28,7 @@
import { t } from '$lib/i18n/index.svelte';
import { ui } from '$lib/stores/ui.svelte';
import { formatDate, iconNameFromClass } from '$lib/utils/display';
import { SharedLanesBuilder, type LaneGrouping } from '$lib/utils/sharedLanes';
type GroupBy = 'items' | 'sharedWith';
@@ -178,47 +179,58 @@
rows: { grant: OutgoingResourceGrant; item: OutgoingGrantItem }[];
}
const lanes = $derived.by((): Lane[] => {
const out: Lane[] = [];
// Transient scratch map built inside $derived.by and discarded — not reactive state.
// eslint-disable-next-line svelte/prefer-svelte-reactivity
const byKey = new Map<string, Lane>();
const ensure = (key: string, header: Lane['header']): Lane => {
let lane = byKey.get(key);
if (!lane) {
lane = { key, header, rows: [] };
byKey.set(key, lane);
out.push(lane);
}
return lane;
};
for (const item of filteredRaw) {
if (groupBy === 'items') {
const lane = ensure(`resource:${item.resource.id}`, { kind: 'resource', item });
for (const grant of item.grants) lane.rows.push({ grant, item });
} else {
for (const grant of item.grants) {
let key: string;
let header: Lane['header'];
if (grant.subject_type === 'user') {
key = `user:${grant.subject_id}`;
header = { kind: 'user', id: grant.subject_id };
} else if (grant.subject_type === 'group') {
key = `group:${grant.subject_id}`;
header = { kind: 'group', id: grant.subject_id };
} else if (grant.has_password) {
key = 'links:password';
header = { kind: 'linkPassword' };
} else {
key = 'links:public';
header = { kind: 'linkPublic' };
type LaneRow = Lane['rows'][number];
// The active grouping as a stable-identity descriptor: `groupKey` changes
// only when the user switches group-by, so an infinite-scroll page (or a
// grant edit that reassigns `raw`) takes the builder's O(N) incremental path
// instead of re-bucketing the whole accumulated list. `emit` reproduces the
// old derive exactly — `open` is the old unconditional `ensure` (a by-files
// lane exists even with zero grants); `push` is `ensure(...).rows.push`.
const laneGrouping = $derived.by(
(): LaneGrouping<OutgoingGrantItem, Lane['header'], LaneRow> =>
groupBy === 'items'
? {
groupKey: 'items',
emit: (item, sink) => {
const key = `resource:${item.resource.id}`;
const header: Lane['header'] = { kind: 'resource', item };
sink.open(key, header);
for (const grant of item.grants) sink.push(key, header, { grant, item });
}
}
ensure(key, header).rows.push({ grant, item });
}
}
}
return out;
});
: {
groupKey: 'sharedWith',
emit: (item, sink) => {
for (const grant of item.grants) {
let key: string;
let header: Lane['header'];
if (grant.subject_type === 'user') {
key = `user:${grant.subject_id}`;
header = { kind: 'user', id: grant.subject_id };
} else if (grant.subject_type === 'group') {
key = `group:${grant.subject_id}`;
header = { kind: 'group', id: grant.subject_id };
} else if (grant.has_password) {
key = 'links:password';
header = { kind: 'linkPassword' };
} else {
key = 'links:public';
header = { kind: 'linkPublic' };
}
sink.push(key, header, { grant, item });
}
}
}
);
// Persistent across reactive ticks: re-buckets only the freshly-appended page
// and hands back the same rows-array reference for untouched lanes, falling
// back to a full rebuild (deep-equal to the pure `buildLanes` reference) on a
// group-by switch, grant edit or kind-filter toggle. Mirrors ResourceList's
// `sectionsBuilder` (benches/ROUND16.md §F1).
const lanesBuilder = new SharedLanesBuilder<OutgoingGrantItem, Lane['header'], LaneRow>();
const lanes = $derived.by(() => lanesBuilder.sync(filteredRaw, laneGrouping));
function laneTitle(header: Lane['header']): string {
switch (header.kind) {
+10 -14
View File
@@ -1,5 +1,7 @@
<script lang="ts">
import { errorToast } from '$lib/utils/errors';
import { SvelteMap } from 'svelte/reactivity';
import { primeContextPage } from '$lib/utils/listContext';
import { onMount } from 'svelte';
import {
deleteTrashItem,
@@ -44,20 +46,10 @@
// travel through `contextMap`, which page-provided group-by / render
// callbacks read via the `ctx` parameter.
const items = $derived(raw.map((it) => it.resource as FileItem | FolderItem));
const contextMap = $derived(
new Map<string, ItemContext>(
raw.map((it) => [
it.resource.id,
{
date: it.deletion_date,
extras: {
driveId: it.drive_id,
trashedAt: it.trashed_at
}
}
])
)
);
// Persistent reactive map, primed per page in `load()` (benches/ROUND16.md §F2)
// instead of rebuilding a fresh Map that re-hashes the whole accumulated list
// on every infinite-scroll page. Mirrors the shipped `favoriteIds` SvelteSet.
const contextMap = new SvelteMap<string, ItemContext>();
// "Drive" group rank: default-personal first, then secondary personal, then
// shared — matches `DrivePicker.svelte::sortedDrives` so the sidebar and
@@ -140,6 +132,10 @@
resourceTypes: ['file', 'folder']
});
raw = reset ? page.items : [...raw, ...page.items];
primeContextPage(contextMap, reset, page.items, (it) => [
it.resource.id,
{ date: it.deletion_date, extras: { driveId: it.drive_id, trashedAt: it.trashed_at } }
]);
cursor = page.next_cursor;
} catch (e) {
console.error('trash: load error', e);