playlists(#37): playlist view fetches its videos (fix deep-link showing recent feed)

On a DIRECT load of a playlist URL the video cache is empty, so the view
resolved zero videos and the shared #videoList kept showing the default recent
feed. The view now fetches the referenced creators' videos from relays
(nostrService.fetchVideosByAuthors) before resolving, so a cold/deep-linked
playlist renders its ACTUAL videos in order. Resolution runs against the fetched
pool + the active cache; the grid is always rendered (even empty) so it can't
fall through to the recent feed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-06 14:48:03 -04:00
1 parent f46b1ce7f3
commit 2a543c3ba9
1 file changed
+48 -14
+48 -14
View File
@@ -25,22 +25,15 @@ function readParams() {
};
}
// Resolve the playlist's items to the app's active video objects, in playlist
// order. Unresolved refs (video not in the cache) are skipped — the count line
// Resolve the playlist's items to video objects, in playlist order, from the
// given pool. Unresolved refs (video missing) are skipped — the count line
// reflects how many resolved.
function resolveOrderedVideos(playlist) {
let active = [];
try {
active = nostrClient.getActiveVideos() || [];
} catch (error) {
active = [];
}
function resolveOrderedVideos(playlist, videoPool) {
const byCoordinate = new Map();
const byEventId = new Map();
for (const video of active) {
for (const video of videoPool) {
const coordinate = buildVideoAddressPointer(video);
if (coordinate) {
if (coordinate && !byCoordinate.has(coordinate)) {
byCoordinate.set(coordinate, video);
}
if (video && typeof video.id === "string" && video.id) {
@@ -61,6 +54,39 @@ function resolveOrderedVideos(playlist) {
return ordered;
}
// The videos to resolve against: the referenced creators' videos (fetched from
// relays so a DIRECT load / cold cache still works) plus whatever's already
// active. Without the fetch, a deep-linked playlist resolves nothing and the
// shared grid shows the default feed instead.
async function gatherVideoPool(playlist, app) {
const authors = [
...new Set(
playlist.items
.filter((item) => item.type === "a")
.map((item) => item.value.split(":")[1])
.filter(Boolean),
),
];
let fetched = [];
if (authors.length && typeof app?.nostrService?.fetchVideosByAuthors === "function") {
try {
fetched = (await app.nostrService.fetchVideosByAuthors(authors)) || [];
} catch (error) {
devLogger.warn("[playlistView] Failed to fetch playlist videos:", error);
}
}
let active = [];
try {
active = nostrClient.getActiveVideos() || [];
} catch (error) {
active = [];
}
return [...fetched, ...active];
}
function setText(id, text) {
const el = document.getElementById(id);
if (el) {
@@ -103,7 +129,8 @@ export async function initPlaylistView({ getApp } = {}) {
setText("playlistTitle", playlist.title);
const videos = resolveOrderedVideos(playlist);
const pool = await gatherVideoPool(playlist, app);
const videos = resolveOrderedVideos(playlist, pool);
const total = playlist.items.length;
setText(
"playlistMeta",
@@ -112,6 +139,9 @@ export async function initPlaylistView({ getApp } = {}) {
: `${videos.length} of ${total} videos available`,
);
// Always render into the shared grid — even an empty playlist must OVERRIDE
// the default feed that populates #videoList, so a deep-linked playlist never
// shows unrelated recent videos.
if (app && typeof app.mountVideoListView === "function") {
app.mountVideoListView({ includeTags: false });
}
@@ -119,7 +149,11 @@ export async function initPlaylistView({ getApp } = {}) {
try {
await app.renderVideoList({
videos,
metadata: { reason: "playlist", preserveOrder: true },
metadata: {
reason: "playlist",
preserveOrder: true,
emptyStateMessage: "None of this playlist's videos could be loaded.",
},
});
} catch (error) {
devLogger.warn("[playlistView] Failed to render playlist videos:", error);