Fix M23/M27: cap per-source on re-index; per-batch kind for library enqueue

M23: replaceMediaItems wrote [...newItems, ...others] and the JSON store sliced
     the combined list to MAX_ITEMS, so re-indexing one large source could
     silently evict ANOTHER source's tail. Now other sources are always kept in
     full and only the source being (re)indexed is capped to the remaining
     budget (MAX_ITEMS - others.length).

M27: enqueueItems forced settings.defaultKind for every item, so a channel
     couldn't be queued as audio without flipping the global default. Added an
     optional `kind` override (falls back to the default) and a video/audio
     SegmentedControl in the Library action row, seeded from defaultKind so
     existing behavior is unchanged until toggled.

typecheck + 242 tests + eslint green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 15:51:33 -04:00
parent 8cbfc90f5d
commit 6ee4dbe78e
4 changed files with 36 additions and 18 deletions
+8 -6
View File
@@ -2,11 +2,11 @@
* Persistence for the media-manager index (Pinchflat-style; see
* ROADMAP-PINCHFLAT.md). Two plain-JSON stores in userData, mirroring the
* history.ts pattern (and the same deliberate choice to stay on JSON rather than
* better-sqlite3 revisit if a user indexes many large channels, see the
* better-sqlite3 -- revisit if a user indexes many large channels, see the
* Phase H risk note in the roadmap):
*
* sources.json one Source record per added channel/playlist
* media-items.json every MediaItem across all sources (queried by sourceId)
* sources.json -- one Source record per added channel/playlist
* media-items.json -- every MediaItem across all sources (queried by sourceId)
*
* Per-row validation on read (validation.ts) so a hand-edited or corrupted file
* can't feed the UI malformed records (same approach as history/errorlog).
@@ -68,12 +68,14 @@ export function listMediaItems(sourceId: string): MediaItem[] {
/**
* Replace all media items for one source with a fresh set (the result of a
* (re)index). Other sources' items are preserved; the new items are placed first
* so they survive the MAX_ITEMS cap.
* (re)index). Other sources' items are preserved in full; only the source being
* written here absorbs the MAX_ITEMS cap, so re-indexing one (large) source can
* never silently evict another source's items past the global limit (M23).
*/
export function replaceMediaItems(sourceId: string, items: MediaItem[]): void {
const others = listAllItems().filter((m) => m.sourceId !== sourceId)
itemsStore.write([...items, ...others])
const budget = Math.max(0, MAX_ITEMS - others.length)
itemsStore.write([...items.slice(0, budget), ...others])
}
/**