Commit Graph

7 Commits

Author SHA1 Message Date
Claude f831636cf9 perf(frontend): parallelize video thumbs, batch owner-cache writes, minor tweaks
- photos: generate + upload the three video-thumbnail sizes in parallel via
  Promise.allSettled instead of a sequential await loop; previewData is still
  captured before its upload so the local preview survives a failed upload.
- useOwnerCache: resolve the batch, then apply a single reactive assignment
  to `#names` instead of spread-copying the record once per id (fewer copies
  and fewer derive re-runs on large resolves).
- admin: migration status polling slowed from 2s to 5s.
- files store: soft cap (10k) on the per-item selection toggle. Bulk
  "select all" in the views is intentionally left uncapped.
2026-06-19 23:06:12 +00:00
Claude c60506cc36 perf(frontend): lazy-load heavy components to shrink initial bundle
Defer loading components that are off the initial render path until they
are first needed, keeping them out of the bundle that loads on page entry.

- Add `lazyComponent` composable: a tiny rune-based holder that dynamic-
  imports a component on first `load()` and exposes it for `{@const}`
  rendering, with the component type inferred from the module so prop/
  binding type-checking stays intact.
- CommandPalette: loaded on the first Cmd/Ctrl+K and mounted open via a
  new `autoOpen` prop. It was previously imported by AppShell, i.e. in the
  initial chunk of every authenticated route (~27 KB). AppShell now owns
  the shortcut that triggers the load.
- FileViewer + WopiEditor: loaded when a preview/editor first opens, across
  files, recent, favorites and shared-with-me.
- PhotoLightbox / PlacesMap / PeopleView: loaded on first lightbox open or
  when the places/people tab is selected (the latter also defers maplibre).

Once loaded a component stays mounted and behaves exactly as before, so
behavior is unchanged on the second use onward.
2026-06-19 22:56:00 +00:00
Claude ccb85f53c0 perf(frontend): virtualize the photos timeline (square + justified)
The Photos "moments" grid rendered every tile into the DOM, so a 20k-photo
library mounted ~140k nodes / 20k <img> elements, held ~196MB JS heap, took
~3.5s to first paint and scrolled at ~6fps. It also ran the justified-layout
maths inside the template (recomputed on every reactive change) and generated
client-side video thumbnails for every off-screen video, not just visible ones.

Introduce `VirtualRows` — a variable-height, section-aware sibling of
`VirtualList` — and flatten the grouped timeline into one list of fixed-height
rows (a date header or a strip of explicitly-sized tiles) shared by both the
square and justified layouts. Only the rows near the viewport are mounted; a
prefix-sum offset table + binary search find the visible band, and a spacer
reserves the full height so the sticky header and load-more sentinel are
unchanged. The justified packing now runs once per groups/width/layout change
in a $derived, not per render.

To avoid duplicating the scroll-tracking logic across the two windowing
components, extract it into a `useVirtualWindow` composable (scroll-ancestor
detection + rAF-throttled aboveBy/viewportH signals); `VirtualList` is
refactored onto it with identical measured numbers.

Measured in headless Chromium (1280x900), synthetic photos, before/after:

  SQUARE      | mount→tiles | DOM nodes | <img> | JS heap | scroll frame
  ------------+-------------+-----------+-------+---------+-------------
   2000       | 416→94 ms   | 14k→629   |2000→96| 21→5 MB | 29→29 ms
   5000       | 916→114 ms  | 35k→629   |5000→96| 50→9 MB | 62→26 ms
  20000       | 3455→220 ms | 140k→629  |20k→96 |196→29 MB|152→33 ms

  JUSTIFIED 20000: mount 245 ms · DOM 315 · <img> 44 · heap 33 MB · ~60fps

Rendered DOM, mounted <img> count and heap are now flat (O(visible)) regardless
of library size; mount is ~16x faster and scroll jank drops from 413 to ≤24
frames. Off-screen video-thumbnail generation no longer fires for non-visible
tiles. Correctness verified by probing a deep scroll in both layouts (tiles
land within the viewport band; square cells equal-width, justified rows
aspect-preserving).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8Vb9QHmLZnEMzHz7MrFy6
2026-06-19 14:25:12 +00:00
Claude 5494efea35 feat(frontend): port Places, People & photo tabs to the Svelte app
Bring the Photos/People/Places UI that main added (in the legacy vanilla
frontend) into the SvelteKit rewrite, wired to the now-merged backend
(/api/photos/geo, /api/people/*).

Photos page (routes/photos/+page.svelte):
- Moments | Places | People sub-tabs (the People tab appears only when the
  faces feature is enabled, via a /api/people capability probe), mirroring
  the vanilla photos sub-nav.
- Square ↔ justified layout toggle. Justified uses a Flickr-style
  row-packer over the width/height the photos list endpoint returns
  (PhotoItem), falling back to 1:1 when dimensions are missing.

New components:
- PhotoLightbox.svelte — the lightbox extracted from the photos page into a
  reusable component (items + bindable index, onDelete callback) so the
  grid, People and Places all share one implementation (no duplication).
- PlacesMap.svelte — MapLibre GL map with server-clustered markers; the
  vector basemap is optional (probed at /basemaps/basemap.pmtiles, themed
  fallback otherwise). Cluster click zooms in or opens the lightbox.
- PeopleView.svelte — identity-cluster grid → per-person photo grid, with
  rename via the in-app prompt dialog.

Supporting:
- api/endpoints/people.ts (+ peopleEnabled probe); photos.ts gains
  fetchPhotosGeo + GeoCluster + PhotoItem; fileThumbnailUrl takes a size.
- lib/vendor/maplibre.ts — minimal typings + lazy loader for the vendored
  MapLibre GL + pmtiles globals (kept any-free for ESLint).
- utils/media.ts — shared isVideo / photoTimestamp / minimalPhotoItem.
- Vendored maplibre-gl 5.24.0 + pmtiles 4.4.1 under static/vendors and an
  optional static/basemaps dir, matching the PR's vendored-asset pattern.
- New photos.tab_*/layout_*/map_* + people.* keys in en.json.

Verified: npm run check (svelte-check + eslint + stylelint + prettier),
npm run test:unit (36 pass), and npm run build all green.
2026-06-19 12:59:16 +00:00
Bradley Nelson e6279da5ce svelte refactor 2026-06-17 22:30:56 -06:00
Bradley Nelson 89e14f8f9e visual continunity 2026-06-17 22:07:18 -06:00
Bradley Nelson daa3010458 init new frontend 2026-06-17 17:06:30 -06:00