Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
eebcefb
fix(ui): resolve the viewer preview on the thumbnail and stop it stic…
lstein Aug 1, 2026
d86b6f6
fix(ui): stop auto-switch flashing the previous image over the next p…
lstein Aug 3, 2026
edbe017
Merge branch 'main' into fix/viewer-progress-image-handoff
JPPhoto Aug 6, 2026
54c76c3
fix(ui): don't strand the viewer under the video progress overlay
lstein Aug 6, 2026
dd524d2
fix(ui): tile concurrent session previews in the video viewer (multi-…
lstein Aug 7, 2026
f282f0f
Merge branch 'main' into fix/viewer-progress-image-handoff
lstein Aug 7, 2026
e854d32
Merge branch 'main' into fix/viewer-progress-image-handoff
lstein Aug 8, 2026
fb8d80c
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 8, 2026
f54d2dd
Merge branch 'main' into fix/viewer-progress-image-handoff
JPPhoto Aug 8, 2026
978092a
chore(ui): resolve knip warnings
JPPhoto Aug 8, 2026
f17a07b
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 9, 2026
c0da00a
Merge branch 'main' into fix/viewer-progress-image-handoff
JPPhoto Aug 9, 2026
68f9c00
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 10, 2026
bb90d5e
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 13, 2026
031a8a9
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 13, 2026
99220bd
Merge branch 'main' into fix/viewer-progress-image-handoff
JPPhoto Aug 18, 2026
b536b7b
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 18, 2026
87ee5e0
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 18, 2026
421fa0d
Merge branch 'main' into fix/viewer-progress-image-handoff
JPPhoto Aug 18, 2026
71217f4
fix(ui): don't treat an auto-switch to a finished video as a user reveal
lstein Aug 18, 2026
dbdf632
Merge branch 'main' into fix/viewer-video-progress-overlay
lstein Aug 18, 2026
fafcdf8
test(ui): pin the promoted session's preview against a stale resolve …
lstein Aug 18, 2026
450897d
fix(ui): address review — deadline ownership handoff + duplicate-comp…
lstein Aug 18, 2026
0452cfd
docs(ui): drop a comment reference to a symbol this tree doesn't have
lstein Aug 18, 2026
ab79d80
fix(ui): close four holes an adversarial review found in the video re…
lstein Aug 18, 2026
c2b6fab
Merge branch 'main' into fix/viewer-progress-image-handoff
lstein Aug 19, 2026
6e263ff
fix(ui): address review round 2 — selection-scoped auto-switch marker…
lstein Aug 19, 2026
2f56661
Merge branch 'main' into fix/viewer-video-progress-overlay
JPPhoto Aug 19, 2026
5e8e3a0
Merge branch 'main' into fix/viewer-progress-image-handoff
JPPhoto Aug 19, 2026
f407cdc
test(ui): cover the reveal suppression, and narrow the retry to galle…
lstein Aug 19, 2026
ff9ff8f
Merge remote-tracking branch 'fork/fix/viewer-progress-image-handoff'…
lstein Aug 19, 2026
f8e6dc9
fix(ui): stop exporting a type nothing imports
lstein Aug 19, 2026
baaa895
fix(ui): address review round 2 — selection-scoped marker, completion…
lstein Aug 19, 2026
7d110c6
fix(ui): carry #9434's per-output retry, and unblock knip
lstein Aug 19, 2026
6e66c73
fix(ui): repair five defects an adversarial review found in the retry…
lstein Aug 19, 2026
c52bfb0
fix(ui): don't put the overlay back over a reveal the user already ea…
lstein Aug 19, 2026
b46cb1b
fix(ui): repair five defects an adversarial review found in the retry…
lstein Aug 19, 2026
75bc243
fix(ui): cancel the board probe on any selection, not just imageSelected
lstein Aug 20, 2026
52174b4
fix(ui): cancel the board probe on any selection, not just imageSelected
lstein Aug 20, 2026
7a17be0
fix(ui): cancel the probe on any selection write, and stop losing out…
lstein Aug 20, 2026
c9c62d3
fix(ui): cancel the probe on any selection write, and stop losing out…
lstein Aug 20, 2026
cef16e0
Merge #9434 (viewer progress-image handoff) into the video overlay fix
lstein Aug 20, 2026
f44c07a
test(ui): pin the reveal's connection to the overlay atom
lstein Aug 20, 2026
b874b90
fix(ui): don't let retries outlive their session, or swallow the firs…
lstein Aug 21, 2026
042350c
fix(ui): end a completion handler's session properly, and restore the…
lstein Aug 21, 2026
cd09bab
fix(ui): close the disposal escapes a self-review found before the ne…
lstein Aug 22, 2026
f2853a6
test(ui): mount the reveal wiring in a real DOM instead of grepping f…
lstein Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions invokeai/frontend/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"eslint-plugin-storybook": "^10.3.6",
"eslint-plugin-unused-imports": "^4.4.1",
"globals": "^16.5.0",
"happy-dom": "^20.11.6",
"knip": "^5.77.4",
"magic-string": "^0.30.21",
"openapi-types": "^12.1.3",
Expand Down
77 changes: 72 additions & 5 deletions invokeai/frontend/web/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { configureStore, createListenerMiddleware } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/store';
import { autoSwitchedImages } from 'features/gallery/store/autoSwitchedImages';
import {
boardIdSelected,
gallerySliceConfig,
imageSelected,
selectionChanged,
} from 'features/gallery/store/gallerySlice';
import { beforeEach, describe, expect, it } from 'vitest';

import { addAutoSwitchedSelectionListener } from './autoSwitchedSelection';

// A store with the real gallery reducer and the real listener, so the predicate is exercised
// against actual selection-writing actions rather than a hand-built state pair.
const buildStore = () => {
const listenerMiddleware = createListenerMiddleware();
addAutoSwitchedSelectionListener(listenerMiddleware.startListening as unknown as AppStartListening);
return configureStore({
reducer: { gallery: gallerySliceConfig.slice.reducer },
middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});
};

describe('addAutoSwitchedSelectionListener', () => {
beforeEach(() => {
// The marker is a module singleton; drop anything a previous test left on it.
autoSwitchedImages.settle(null);
});

it('keeps the marker when the auto-switch selection lands', () => {
const store = buildStore();
autoSwitchedImages.record('a.png');
store.dispatch(imageSelected('a.png'));
expect(autoSwitchedImages.consume('a.png')).toBe(true);
});

it('drops the marker once the user selects something else', () => {
// The dead click this exists to prevent: the auto-switch to A never rendered because the user
// clicked B first, so their later click on A must still get its reveal.
const store = buildStore();
autoSwitchedImages.record('a.png');
store.dispatch(imageSelected('a.png'));
store.dispatch(imageSelected('b.png'));
store.dispatch(imageSelected('a.png'));
expect(autoSwitchedImages.consume('a.png')).toBe(false);
});

it('settles on every action that writes the selection, not just imageSelected', () => {
const store = buildStore();

autoSwitchedImages.record('a.png');
store.dispatch(imageSelected('a.png'));
store.dispatch(selectionChanged(['b.png']));
expect(autoSwitchedImages.consume('a.png')).toBe(false);

autoSwitchedImages.record('c.png');
store.dispatch(imageSelected('c.png'));
store.dispatch(boardIdSelected({ boardId: 'other', select: { selection: ['d.png'], galleryView: 'images' } }));
expect(autoSwitchedImages.consume('c.png')).toBe(false);
});

it('leaves the marker alone when an action does not move the selection', () => {
const store = buildStore();
autoSwitchedImages.record('a.png');
store.dispatch(imageSelected('a.png'));
// Selecting the same item again, and a board switch that carries no selection, must not
// discard a marker whose image has not rendered yet.
store.dispatch(imageSelected('a.png'));
store.dispatch(boardIdSelected({ boardId: 'other' }));
expect(autoSwitchedImages.consume('a.png')).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { AppStartListening } from 'app/store/store';
import { autoSwitchedImages } from 'features/gallery/store/autoSwitchedImages';
import { selectLastSelectedItem } from 'features/gallery/store/gallerySelectors';

/**
* Keeps the auto-switch marker scoped to the selection it was recorded for.
*
* onInvocationComplete records the item it is about to auto-switch to, so the viewer's reveal
* effect can tell that handoff apart from a user's gallery click. The marker is only meaningful
* while that selection stands: once the selection moves on, the recorded auto-switch will never
* render, and leaving the marker behind would make the user's next click on that item read as an
* auto-switch and get no reveal.
*
* Matched by state rather than by action type on purpose — the selection is written by several
* reducers (imageSelected, selectionChanged, boardIdSelected, comparedImagesSwapped,
* showVirtualBoardsChanged, logout), and a new one added later would silently escape an
* action-type list, leaving exactly the stale marker this exists to prevent.
*/
export const addAutoSwitchedSelectionListener = (startAppListening: AppStartListening) => {
startAppListening({
predicate: (_action, currentState, previousState) =>
selectLastSelectedItem(currentState) !== selectLastSelectedItem(previousState),
effect: (_action, { getState }) => {
autoSwitchedImages.settle(selectLastSelectedItem(getState()) ?? null);
},
});
};
Loading
Loading