Files
minstrel/web/src/lib/components/HistoryRow.svelte
T
bvandeusenandClaude Opus 5.5 94a9c8cbe3 chore(deps): SvelteKit 3 with adapter-static 4 and TypeScript 6, full-tree npm audit (#5021)
Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps,
plus the migration they need. The mechanical part is `sv migrate
sveltekit-3`, run one task at a time and reviewed:

- svelte.config.js is gone. Its options move into sveltekit() in
  vite.config.ts, exported as kitOptions so vitest.config.ts runs the
  same kit setup, including the $test-utils alias the tests import.
- $lib becomes #lib through package.json "imports". There is no
  src/lib/index, so only the "#lib/*" entry is kept.
- tsconfig extends $app/tsconfig.
- Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite
  ^8.0.12, svelte-check ^4.7.5.

By hand, from the codemod's list of non-automated tasks:

- goto's replaceState option is now replace; keepFocus becomes
  reset: false. For the search typeahead, reset: false also stops the
  scroll-to-top, which is wanted while typing.
- The test setup mocks drop pushState/replaceState and $app/paths
  base/assets, which kit 3 removed, and mock refreshAll in place of
  invalidateAll.
- The other flagged files only read page.url or goto internal routes,
  so they needed no change.

TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares
typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to
7 once both accept it.

With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0,
so the web lane now audits every dependency rather than only what
ships to browsers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:50:39 -04:00

65 lines
2.2 KiB
Svelte

<script lang="ts">
import type { HistoryEvent } from '#lib/api/history.js';
import { playQueue } from '#lib/player/store.svelte.js';
import { coverUrl } from '#lib/media/covers.js';
let { event } = $props<{ event: HistoryEvent }>();
function handleClick() {
playQueue([event.track], 0);
}
// Format played_at as a human-readable timestamp.
// Bands: <1h "23m ago" / <24h "3h ago" / <7d "Tue 14:32" /
// older same-year "May 1" / older diff-year "May 1, 2025".
function relativeTime(playedAtIso: string): string {
const playedAt = new Date(playedAtIso);
const now = new Date();
const ms = now.getTime() - playedAt.getTime();
const minutes = Math.floor(ms / 60000);
if (minutes < 60) return `${Math.max(1, minutes)}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
if (days < 7) {
const dayOfWeek = playedAt.toLocaleDateString(undefined, { weekday: 'short' });
const time = playedAt.toLocaleTimeString(undefined, {
hour: '2-digit', minute: '2-digit', hour12: false
});
return `${dayOfWeek} ${time}`;
}
if (playedAt.getFullYear() === now.getFullYear()) {
return playedAt.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}
return playedAt.toLocaleDateString(undefined, {
month: 'short', day: 'numeric', year: 'numeric'
});
}
const timestamp = $derived(relativeTime(event.played_at));
const tooltip = $derived(new Date(event.played_at).toISOString());
</script>
<button
type="button"
onclick={handleClick}
aria-label={`Play ${event.track.title}`}
class="flex w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-surface-hover"
>
<img
src={coverUrl(event.track.album_id)}
alt=""
class="h-11 w-11 shrink-0 rounded object-cover"
loading="lazy"
/>
<div class="min-w-0 flex-1">
<div class="truncate text-sm font-medium text-text-primary">{event.track.title}</div>
<div class="truncate text-xs text-text-secondary">
{event.track.artist_name} · {event.track.album_title}
</div>
</div>
<span title={tooltip} class="shrink-0 text-xs text-text-secondary">
{timestamp}
</span>
</button>