Checkpoint of the in-progress frontend toolchain work (Vite pinned to ^6 after
the 7/8 rolldown build break, eslint-plugin-svelte v3 navigation/reactivity
fixes, CI/Dockerfile/manifest updates) together with three UI performance
optimizations (verified on the Vite 6 build):
- Critical CSS: move auth.css/music.css off the global path into their route
chunks (login/device/nextcloud-login, music) -- -25% gzipped critical CSS
(~5.4 KB) on every non-auth/non-music page load.
- relativeTimeAgo: cache the Intl.RelativeTimeFormat (was rebuilt per call, once
per row per render) -- 22.7x faster date formatting in large lists.
- Virtualize search results and grouped trash (list view) via VirtualList -- DOM
rows mounted stay ~constant (~27) instead of O(N) (94.6% fewer for 500 hits).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Large folders/collections rendered every row into the DOM, so memory and
scripting time grew linearly with item count. A 20k-row list took ~12.5s to
first paint, held ~380k DOM nodes / ~281MB JS heap, and scrolled at ~4fps.
Add a reusable, dependency-free `VirtualList` that windows rows against the
nearest scrollable ancestor (the existing `.content-area`), so the
single-scrollbar UX, sticky header and end-of-list sentinel are unchanged. It
reserves the full scroll height with a sized spacer and translates a small
window of rows; row height is auto-measured for the single-column case.
Wire it into ResourceList's flat list view (recent, favorites, shared,
shared-with-me, trash, search). Grouped sections and grid view are unchanged
for now and are the next callers of the same primitive.
Measured in headless Chromium (1280x900) with synthetic rows, before/after:
rows | mount→paint | DOM nodes | JS heap | scroll frame | jank frames
------+-------------+-----------+---------+--------------+------------
1000 | 632→70 ms | 19k→351 | 16→2 MB | 17→17 ms | 0→0
5000 | 4401→54 ms | 95k→351 | 72→3 MB | 47→17 ms | 136→0
20000 | 12577→56 ms | 380k→351 |281→6 MB |258→17 ms | 1443→0
Rendered DOM and heap are now flat (O(visible)) regardless of dataset size;
scroll holds 60fps with zero jank. Correctness verified by probing a mid-list
scroll (rows land at the expected indices, positioned within the viewport).
eslint: disable core `no-undef` for `.svelte` (TypeScript/svelte-check already
resolve identifiers, including `<script generics>` type params it can't see).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8Vb9QHmLZnEMzHz7MrFy6