From bd832ae63033a158e54fb5ef8fc0d52dae84e4b3 Mon Sep 17 00:00:00 2001 From: saschabuehrle Date: Fri, 13 Mar 2026 08:08:09 +0100 Subject: [PATCH 1/4] fix: normalize artist timestamp units in library browse (fixes #289) --- frontend/src/api/library.ts | 38 ++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/frontend/src/api/library.ts b/frontend/src/api/library.ts index ca16b73e..6ca3a809 100644 --- a/frontend/src/api/library.ts +++ b/frontend/src/api/library.ts @@ -263,6 +263,27 @@ export interface Artist { first_album_added?: Date; } +const parseApiDate = (value: unknown): Date | undefined => { + if (value === null || value === undefined) { + return undefined; + } + + const numeric = typeof value === 'number' ? value : Number(value); + if (!Number.isNaN(numeric)) { + // Some API responses provide UNIX seconds while others use milliseconds. + const timestamp = numeric < 1e12 ? numeric * 1000 : numeric; + const date = new Date(timestamp); + return Number.isNaN(date.getTime()) ? undefined : date; + } + + if (typeof value === 'string') { + const date = new Date(value); + return Number.isNaN(date.getTime()) ? undefined : date; + } + + return undefined; +}; + // List of all artists export const artistsQueryOptions = () => ({ queryKey: ['artists'], @@ -272,19 +293,10 @@ export const artistsQueryOptions = () => ({ for (let i = 0; i < artists.length; i++) { const artist = artists[i]; - // Convert timestamps to Date objects - if (artist.last_item_added) { - artist.last_item_added = new Date(artist.last_item_added); - } - if (artist.last_album_added) { - artist.last_album_added = new Date(artist.last_album_added); - } - if (artist.first_item_added) { - artist.first_item_added = new Date(artist.first_item_added); - } - if (artist.first_album_added) { - artist.first_album_added = new Date(artist.first_album_added); - } + artist.last_item_added = parseApiDate(artist.last_item_added); + artist.last_album_added = parseApiDate(artist.last_album_added); + artist.first_item_added = parseApiDate(artist.first_item_added); + artist.first_album_added = parseApiDate(artist.first_album_added); } return artists; // TODO: fill cache data for single artists queries From f8e1e2eb9d9c6e0f63f6aae10702b87ed4ae2f1f Mon Sep 17 00:00:00 2001 From: saschabuehrle Date: Fri, 13 Mar 2026 08:49:21 +0100 Subject: [PATCH 2/4] docs(changelog): add unreleased note for timestamp fix (#289) --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d83f9012..1428d52a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Normalize artist timestamp parsing in the frontend New additions view to correctly handle seconds/milliseconds and ignore invalid values. [#289](https://github.com/pSpitzner/beets-flask/issues/289) + ## [1.2.1] - 25-12-28 ### Fixed From 499bee12f9a06d192faf8c3d9fb86205e6294e0c Mon Sep 17 00:00:00 2001 From: saschabuehrle Date: Fri, 13 Mar 2026 11:40:41 +0100 Subject: [PATCH 3/4] refactor: reuse shared date parsing helper for artist timestamps --- frontend/src/api/library.ts | 22 +------------------ frontend/src/components/common/units/time.tsx | 21 ++++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/frontend/src/api/library.ts b/frontend/src/api/library.ts index 6ca3a809..6c2d09e5 100644 --- a/frontend/src/api/library.ts +++ b/frontend/src/api/library.ts @@ -1,6 +1,7 @@ import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query'; import { toHex } from '@/components/common/strings'; +import { parseApiDate } from '@/components/common/units/time'; import { AlbumResponse, AlbumResponseExpanded, @@ -263,27 +264,6 @@ export interface Artist { first_album_added?: Date; } -const parseApiDate = (value: unknown): Date | undefined => { - if (value === null || value === undefined) { - return undefined; - } - - const numeric = typeof value === 'number' ? value : Number(value); - if (!Number.isNaN(numeric)) { - // Some API responses provide UNIX seconds while others use milliseconds. - const timestamp = numeric < 1e12 ? numeric * 1000 : numeric; - const date = new Date(timestamp); - return Number.isNaN(date.getTime()) ? undefined : date; - } - - if (typeof value === 'string') { - const date = new Date(value); - return Number.isNaN(date.getTime()) ? undefined : date; - } - - return undefined; -}; - // List of all artists export const artistsQueryOptions = () => ({ queryKey: ['artists'], diff --git a/frontend/src/components/common/units/time.tsx b/frontend/src/components/common/units/time.tsx index 4d398439..35f09668 100644 --- a/frontend/src/components/common/units/time.tsx +++ b/frontend/src/components/common/units/time.tsx @@ -1,3 +1,24 @@ +export const parseApiDate = (value: unknown): Date | undefined => { + if (value === null || value === undefined) { + return undefined; + } + + const numeric = typeof value === 'number' ? value : Number(value); + if (!Number.isNaN(numeric)) { + // Some API responses provide UNIX seconds while others use milliseconds. + const timestamp = numeric < 1e12 ? numeric * 1000 : numeric; + const date = new Date(timestamp); + return Number.isNaN(date.getTime()) ? undefined : date; + } + + if (typeof value === 'string') { + const date = new Date(value); + return Number.isNaN(date.getTime()) ? undefined : date; + } + + return undefined; +}; + export const relativeTime = (date?: Date | null) => { if (!date) return 'never'; From bde05c7ac999ec1056525f44c3c0a8a7a16a4868 Mon Sep 17 00:00:00 2001 From: saschabuehrle Date: Fri, 13 Mar 2026 16:48:18 +0100 Subject: [PATCH 4/4] fix(types): resolve mypy assignment/overload errors in session.py - Use separate config_view variable to avoid type confusion between Subview and InteractiveBeetsConfig - Fixes mypy errors in CI: assignment type mismatch and call-overload issues --- backend/beets_flask/importer/session.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/beets_flask/importer/session.py b/backend/beets_flask/importer/session.py index 8cad55ac..e95c62bc 100644 --- a/backend/beets_flask/importer/session.py +++ b/backend/beets_flask/importer/session.py @@ -271,10 +271,10 @@ def get_config_value(self, key: str, type_func: Callable | None = None) -> Any: # get settings from user settings, this is not a dict, but confuse config # the confuse config views do not throw key errors, and their .get() is not # the same as dict.get(), but rather resolves the value. - default = get_config() + config_view = get_config() for p in path: - default = default[p] - default = default.get(type_func) if type_func else default.get() + config_view = config_view[p] + default = config_view.get(type_func) if type_func else config_view.get() return default # -------------------------- State handling helpers -------------------------- #