diff --git a/launcher/jellyfin.ts b/launcher/jellyfin.ts index 7ab7e6d..1bcedce 100644 --- a/launcher/jellyfin.ts +++ b/launcher/jellyfin.ts @@ -344,6 +344,22 @@ export function buildRootSearchGroups(items: JellyfinItemEntry[]): JellyfinGroup return groups; } +export type JellyfinChildSelection = + | { kind: 'playable'; id: string } + | { kind: 'container'; id: string }; + +export function classifyJellyfinChildSelection( + selectedChild: Pick, +): JellyfinChildSelection { + if (isJellyfinPlayableType(selectedChild.type)) { + return { kind: 'playable', id: selectedChild.id }; + } + if (isJellyfinContainerType(selectedChild.type)) { + return { kind: 'container', id: selectedChild.id }; + } + fail('Selected Jellyfin item is not playable.'); +} + async function runAppJellyfinListCommand( appPath: string, args: Args, @@ -695,13 +711,11 @@ async function resolveJellyfinSelectionViaApp( if (!selectedChildId) fail('No Jellyfin folder/file selected.'); const selectedChild = childById.get(selectedChildId); if (!selectedChild) fail('Invalid Jellyfin item selection.'); - if (isJellyfinPlayableType(selectedChild.type)) { - return selectedChild.id; + const selection = classifyJellyfinChildSelection(selectedChild); + if (selection.kind === 'playable') { + return selection.id; } - if (isJellyfinContainerType(selectedChild.type)) { - return await pickPlayableDescendants(selectedChild.id); - } - fail('Selected Jellyfin item is not playable.'); + currentContainerId = selection.id; } } diff --git a/launcher/main.test.ts b/launcher/main.test.ts index 9fc0034..513c043 100644 --- a/launcher/main.test.ts +++ b/launcher/main.test.ts @@ -16,6 +16,7 @@ import { readUtf8FileAppendedSince, parseEpisodePathFromDisplay, buildRootSearchGroups, + classifyJellyfinChildSelection, } from './jellyfin.js'; type RunResult = { @@ -443,3 +444,11 @@ test('buildRootSearchGroups excludes episodes and keeps containers/movies', () = }, ]); }); + +test('classifyJellyfinChildSelection keeps container drilldown state instead of flattening', () => { + const next = classifyJellyfinChildSelection({ id: 'season-2', type: 'Season' }); + assert.deepEqual(next, { + kind: 'container', + id: 'season-2', + }); +}); diff --git a/src/cli/args.test.ts b/src/cli/args.test.ts index 55bb04e..2351b4c 100644 --- a/src/cli/args.test.ts +++ b/src/cli/args.test.ts @@ -60,6 +60,12 @@ test('parseArgs handles space-separated jellyfin recursive control', () => { assert.equal(args.jellyfinRecursive, false); }); +test('parseArgs ignores unrecognized space-separated jellyfin recursive values', () => { + const args = parseArgs(['--jellyfin-items', '--jellyfin-recursive', '--start']); + assert.equal(args.jellyfinRecursive, undefined); + assert.equal(args.start, true); +}); + test('hasExplicitCommand and shouldStartApp preserve command intent', () => { const stopOnly = parseArgs(['--stop']); assert.equal(hasExplicitCommand(stopOnly), true); diff --git a/src/cli/args.ts b/src/cli/args.ts index 0932fae..4ecd900 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -245,8 +245,6 @@ export function parseArgs(argv: string[]): CliArgs { args.jellyfinRecursive = false; } else if (value === 'true' || value === '1' || value === 'yes') { args.jellyfinRecursive = true; - } else { - args.jellyfinRecursive = true; } } else if (arg === '--jellyfin-non-recursive') { args.jellyfinRecursive = false;