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.
- About modal overlay used a raw z-index: 1200, below the semantic
--z-modal (3000)/--z-toast (4000) layers, so token-based surfaces would
cover it. Consume var(--z-modal) instead.
- .btn-primary and .search-button sit on the accent gradient but coloured
their text with --color-danger-text (only correct by coincidence, #fff).
Use the purpose-built --color-on-accent so retinting danger can't break
them. The other --color-danger-text uses are white-on-red and stay.
- --color-item-hover-blue/--color-item-hover-sky were light-only literals
that would read wrong in dark mode; wrap them in light-dark().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013J5koaCrHDMvS7uwpawLBN
The per-section icon colours used :nth-child(1..6), but the nav grew to 8
items (files, shared, shared-with-me, recent, favorites, photos, music,
trash). The colours had drifted out of sync with their labels, and music
and trash got no colour at all.
Map each item's colour off a stable `data-section` key instead, covering
all eight sections with distinct curated calendar-dot hues (sibling blues
for the two share directions, purple for music, red for trash). Reordering
or adding nav items can no longer desync the colours.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013J5koaCrHDMvS7uwpawLBN
Two related parity gaps from the VanillaJS → Svelte migration (issue #500):
internal-vs-external users weren't badged, and external users in a share's
member list rendered as a bare UUID with a static icon — no avatar, no email.
Both share one root cause: there was no shared user vignette and no resolver
for non-directory (external) users (the system address book lists internal
users only, and ShareDialog hardcoded isExternal=false).
- lib/api/endpoints/users.ts: resolveUser(id) — cached GET /api/users/{id}
(the authenticated per-user profile lookup) → {name, email, image,
isExternal}; returns null when the profile isn't visible so callers keep
their fallback label.
- lib/components/UserVignette.svelte: reusable identity chip — avatar (photo
or coloured initials), name, email, and a building-circle-xmark badge for
external users; resolves lazily and falls back to a caller-supplied label.
- lib/utils/avatar.ts: userInitials() + avatarColorIndex() extracted from
AppShell (now shared by both — no duplicated logic) so vignette and account
button render identically.
- ShareDialog: user member rows now render <UserVignette>; groups keep their
icon+label. Drops the dead hardcoded isExternal.
Backend already exposes everything (UserDto.email/image/is_external via
GET /api/users/{id}); no backend change. Frontend gate green (svelte-check
0/0, eslint, stylelint, prettier) + 47 Vitest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>