feat(playlists): #417 — "Refreshed …" subtitle on system tiles

Closes the last buildable item of the #411 system-playlists-v2
umbrella. System playlists atomic-replace on rebuild, so created_at
(already on the wire — no server change) is the last-rotated time.
Surface it as a small tile subtitle so users see how fresh a mix
is: "Refreshed just now / today / yesterday / N days ago / Mon D".

- web PlaylistCard: refreshedLabel() + a muted footer line, shown
  only when system_variant != null. Unparseable/empty timestamp →
  suppressed (web test fixtures use created_at:'' so no test churn).
- flutter PlaylistCard: mirrored _refreshedLabel() + subtitle under
  the system badge for isSystem playlists.

Friendly wording deliberately distinct from HistoryRow's "m/h ago";
per-surface helper per the project's existing relative-time
convention. CI-pending; closes with the umbrella on device-check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 23:03:41 -04:00
parent 2d9775244c
commit ce36760819
2 changed files with 55 additions and 0 deletions
@@ -17,6 +17,27 @@
// (#411 R2) — no hardcoded kind list here.
const refreshable = $derived(playlist.refreshable === true);
// #417: system playlists atomic-replace on rebuild, so created_at
// is the last-rotated time. Surface it so users know how fresh the
// mix is. Friendly wording, not the HistoryRow "m/h ago" style.
function refreshedLabel(iso: string): string {
const t = new Date(iso);
if (Number.isNaN(t.getTime())) return '';
const now = new Date();
const mins = Math.floor((now.getTime() - t.getTime()) / 60000);
if (mins < 5) return 'Refreshed just now';
const startOfToday = new Date(now);
startOfToday.setHours(0, 0, 0, 0);
const days = Math.floor((startOfToday.getTime() - t.getTime()) / 86400000) + 1;
if (t.getTime() >= startOfToday.getTime()) return 'Refreshed today';
if (days <= 1) return 'Refreshed yesterday';
if (days < 7) return `Refreshed ${days} days ago`;
return `Refreshed ${t.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })}`;
}
const refreshed = $derived(
playlist.system_variant != null ? refreshedLabel(playlist.created_at) : ''
);
const queryClient = useQueryClient();
let starting = $state(false);
@@ -153,6 +174,9 @@
· by {playlist.owner_username}
{/if}
</div>
{#if refreshed}
<div class="truncate text-xs text-text-muted">{refreshed}</div>
{/if}
</div>
</a>
</div>