To reproduce create a playlist file like random100.smartpl with the following contents:
"Random 100" {
media_kind is music
order by random desc
limit 100
}
Update the Owntone library to scan this playlist.
Then visit the playlist page. The tracks do not appear and there is a console error in Chrome.
Here is another AI solution to the issue, and I've confirmed it's working but also don't want to assume it's not overreaching and might impact other scenarios negatively:
Bug: TypeError: i.value.map is not a function (Playlist tracks view)
Summary
When opening a playlist's tracks in the web UI the browser console showed:
TypeError: i.value.map is not a function
This occurred in the playlist tracks views when the code assumed the reactive tracks value was an Array and called .map() on it.
Root cause
Frontend uses a GroupedList wrapper for API list responses. GroupedList stores the underlying array under the items property and implements a generator for grouped rendering, but it is not an Array and does not implement Array.prototype.map.
Code in several playlist views called tracks.value.map(...) which raises the TypeError when tracks.value is a GroupedList instance.
Files changed (code-only)
web-src/src/pages/PagePlaylistTracks.vue
- Fixed computed
uris to handle GroupedList defensively:
- if (playlist.value.random) {
- return tracks.value.map((item) => item.uri).join()
- }
if (playlist.value.random) {
const list = Array.isArray(tracks.value) ? tracks.value : tracks.value.items || []
return list.map((item) => item.uri).join()
}
web-src/src/pages/PagePlaylistTracksSpotify.vue
- Fixed code that previously assumed
tracks.value was an array when computing position and in the template v-if.
- Example changes:
- let position = Math.max(-1, ...tracks.value.map((item) => item.position).filter(Boolean))
const existing = Array.isArray(tracks.value) ? tracks.value : tracks.value?.items || []
let position = Math.max(-1, ...existing.map((item) => item.position).filter(Boolean))
and updated the template v-if from v-if="tracks.length" to v-if="tracks.length || tracks.items?.length".
Why this fix is correct
- Respects the
GroupedList API (uses .items for the raw array).
- Adds defensive handling for both array and
GroupedList cases so components work with either raw arrays (unit tests / future callers) and the grouped wrapper.
How to reproduce (frontend)
- Build and serve the web UI (or deploy the built
htdocs to your server).
- Open the app and navigate to a playlist that has
random set.
- Open browser DevTools Console; previously you would see
TypeError: i.value.map is not a function.
After the patch the error should not appear and the playlist actions (shuffle/play URIs) will work.
Suggested follow-ups
- Consider adding a small utility on
GroupedList like toArray() or implement map()/forEach() proxies so callers don't need to check items.
- Add a unit/integration test to ensure playlist components tolerate both raw arrays and
GroupedList instances.
Files modified contain the minimal defensive changes shown above. This summary excludes any deployment steps (build/rsync/service restart) — those were performed separately.
To reproduce create a playlist file like random100.smartpl with the following contents:
Update the Owntone library to scan this playlist.
Then visit the playlist page. The tracks do not appear and there is a console error in Chrome.
Here is another AI solution to the issue, and I've confirmed it's working but also don't want to assume it's not overreaching and might impact other scenarios negatively:
Bug: TypeError: i.value.map is not a function (Playlist tracks view)
Summary
When opening a playlist's tracks in the web UI the browser console showed:
This occurred in the playlist tracks views when the code assumed the reactive
tracksvalue was anArrayand called.map()on it.Root cause
Frontend uses a
GroupedListwrapper for API list responses.GroupedListstores the underlying array under theitemsproperty and implements a generator for grouped rendering, but it is not anArrayand does not implementArray.prototype.map.Code in several playlist views called
tracks.value.map(...)which raises the TypeError whentracks.valueis aGroupedListinstance.Files changed (code-only)
web-src/src/pages/PagePlaylistTracks.vueuristo handleGroupedListdefensively:web-src/src/pages/PagePlaylistTracksSpotify.vuetracks.valuewas an array when computingpositionand in the templatev-if.- let position = Math.max(-1, ...tracks.value.map((item) => item.position).filter(Boolean)) const existing = Array.isArray(tracks.value) ? tracks.value : tracks.value?.items || [] let position = Math.max(-1, ...existing.map((item) => item.position).filter(Boolean))and updated the template
v-iffromv-if="tracks.length"tov-if="tracks.length || tracks.items?.length".Why this fix is correct
GroupedListAPI (uses.itemsfor the raw array).GroupedListcases so components work with either raw arrays (unit tests / future callers) and the grouped wrapper.How to reproduce (frontend)
htdocsto your server).randomset.TypeError: i.value.map is not a function.After the patch the error should not appear and the playlist actions (shuffle/play URIs) will work.
Suggested follow-ups
GroupedListliketoArray()or implementmap()/forEach()proxies so callers don't need to checkitems.GroupedListinstances.Files modified contain the minimal defensive changes shown above. This summary excludes any deployment steps (build/rsync/service restart) — those were performed separately.