From c070ff13e043c1849e4b0641e8580265215275a9 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:00:45 +0200 Subject: [PATCH] feat(session): always launch into the first category; remember only sub-pages The persisted category is gone from the session: the app now always opens on the rail's first tab (Images). What each category remembers is its own last operation, carried in lastOperation (which setOperation already keeps current, and the snapshot now folds the open category's operation into) - so Images still opens on Generate if that is where it was, and switching to Video lands on its remembered sub-page rather than the default. Closes #16 Co-Authored-By: Claude Opus 5 --- src/renderer/src/state.ts | 23 ++++++++++++----------- test/session-persist.test.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/state.ts b/src/renderer/src/state.ts index 0a04937..42d3bab 100644 --- a/src/renderer/src/state.ts +++ b/src/renderer/src/state.ts @@ -207,9 +207,10 @@ export function sessionSnapshot(state: AppState, genResults: string[]): unknown } return { version: SESSION_VERSION, - category: state.category, - operation: state.operation, - lastOperation: state.lastOperation, + // The category and its operation are deliberately NOT persisted as such: + // the app always launches into the first category, and each category's + // sub-page rides in lastOperation (kept current by setOperation). + lastOperation: { ...state.lastOperation, [state.category]: state.operation }, options: state.options, queues, genResults: genResults.slice(0, MAX_GEN) @@ -231,8 +232,6 @@ function isValidItem(i: unknown): i is QueueItem { } interface PersistedSession { - category: CategoryId - operation: string lastOperation: Partial> options: Record queues: AppState['queues'] @@ -254,12 +253,14 @@ export function parseSession(raw: unknown): { state: AppState; genResults: strin return false } } - // Two independent checks: a renamed OPERATION must not also discard the - // persisted category. - const category = validCat(p.category) ? p.category : FIRST_CATEGORY - const operation = findOperation(category, p.operation) - ? p.operation - : defaultOperation(category) + // Always launch into the FIRST category (owner decision: coming back to + // the remembered one felt wrong). What IS remembered is each category's + // last sub-page: Images still opens on Generate if that is where it was, + // and switching to Video lands on its own remembered operation. + const category = FIRST_CATEGORY + const remembered = (p.lastOperation ?? {})[category] + const operation = + remembered && findOperation(category, remembered) ? remembered : defaultOperation(category) // Drop queues for categories no longer in the catalog (a removed/renamed tool). const queues: AppState['queues'] = {} for (const [cat, q] of Object.entries(p.queues ?? {})) { diff --git a/test/session-persist.test.ts b/test/session-persist.test.ts index 7c0e9f7..a933588 100644 --- a/test/session-persist.test.ts +++ b/test/session-persist.test.ts @@ -105,10 +105,33 @@ describe('session persistence round-trip', () => { expect(pruned.state.queues.images!.selected).toEqual([]) }) - it('falls back to a valid category/operation when the persisted one is invalid', () => { - const snap = sessionSnapshot(initialState, []) as Record - const parsed = parseSession({ ...snap, category: 'nonsense', operation: 'nope' }) + it('always launches into the first category, whatever was open at close', () => { + // The category itself is not restored - only each category's sub-page is. + const state: AppState = { ...initialState, category: 'video', operation: 'compress' } + const parsed = parseSession(sessionSnapshot(state, [])) expect(parsed).not.toBeNull() expect(parsed!.state.category).toBe('images') }) + + it("remembers each category's last sub-page, including the one open at close", () => { + const state: AppState = { + ...initialState, + category: 'video', + operation: 'compress', + lastOperation: { images: 'generate', video: 'compress' } + } + const parsed = parseSession(sessionSnapshot(state, [])) + // Launches into Images ON its remembered sub-page... + expect(parsed!.state.category).toBe('images') + expect(parsed!.state.operation).toBe('generate') + // ...and Video's sub-page survives for when the user switches to it. + expect(parsed!.state.lastOperation.video).toBe('compress') + }) + + it('falls back to the default operation when the remembered one is gone', () => { + const state: AppState = { ...initialState, lastOperation: { images: 'renamed-away' } } + const parsed = parseSession(sessionSnapshot(state, [])) + expect(parsed!.state.category).toBe('images') + expect(parsed!.state.operation).toBe('convert') + }) })