Skip to content

feat(ui): reliability pass plus canvas, gallery and frontend layout features - #9682

Merged
joshistoast merged 114 commits into
mainfrom
plan/webv2-quality
Oct 8, 2026
Merged

joshistoast merged 114 commits into
mainfrom
plan/webv2-quality

Conversation

@joshistoast

@joshistoast joshistoast commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A reliability, accessibility and responsiveness pass over the new frontend, followed by a set of Canvas, Gallery, Generate and workbench features. Defects were reproduced before being fixed, with one exception noted under Review.

Accounts and saved work

  • A tab's requests carry an in-memory credential bound to its account. Another tab signing in or out can no longer make this tab send one account's work under another's token; other tabs follow the shared sign-in through an explicit transition. X-Refreshed-Token is adopted (sliding renewal and own-password change keep the session), and sockets authenticate each connect with the current token. A password change announces itself to the other tabs of that account, which hold the 401 the revocation causes until the replacement token arrives instead of signing out and deleting their local recovery data; a deliberate sign-out in any tab ends every tab at once, even one mid-change; a mistyped password in a signed-out tab leaves the other tabs alone.
  • Leaving the editor saves the latest committed state, pending field drafts and Canvas pixels instead of cancelling the autosave. Hiding the page saves immediately. Project create/open/switch/close flush drafts at the transition itself, so an edit typed during a slow open stays on its own project.
  • An edit made just before a reload or tab close is kept: the latest state is journaled locally during unload and reconciled on the next open. Only entries of pages that are gone are recovered: a page proves it is still running through its editor-session Web Lock, so a hidden tab's unsaved edit is never adopted by another tab (where an undo could not retire it). One failed journal operation no longer disables journaling for the session, and an edit committed while the last tab's session was closing is saved when the session reopens.
  • The model manager keeps unsaved edits per model across tab and model switches.

Canvas

  • Shape tools paint into the selected mask instead of creating a raster layer.
  • An inpaint mask stays selected after a staged result is accepted.
  • Accepting a staged result while its batch is still queued or running also cancels the rest of that batch. The button keeps its ordinary "Accept to Layer" label.
  • A staged result can be saved to its destination board, with a "Saved to [board]" notice, Show in Gallery, and Retry when only the board assignment failed; the notice is dismissed with the account that saved.
  • Anima ControlNet-LLLite control layers. Anima layers saved as ControlNet offer a one-click switch that keeps model, weight and step range.
  • The Shape and Select tool slots open a labelled menu (icon, name, shortcut) instead of a row of icon buttons; click, hold-release, right-click and ArrowRight still work. The Move tool shows a cursor icon.
  • Tool properties (brush/eraser size, opacity and hardness; shape stroke width; gradient angle and stop offset; text size, line height and font axes) and layer properties (adjustments, control and reference weights, mask modifiers, filter parameters) use the scrubber slider. Brush and text size keep log-scale tracks. Each drag or held key records one history step, and an edit landing mid-drag (a hotkey, undo, upload) is kept: the engine's preview session restores what it previewed whenever a replay or a commit from elsewhere ends it, and the next release or choice records from the live document.
  • Hovering or arrow-keying a blend mode previews it on the canvas; closing restores the original and choosing records one step. Layer opacity keeps its compact number input and gains a drag handle like the Transform fields.
  • Adjustments can be dragged onto another raster layer or group, or among its adjustments, as one undoable move. A child row's "Move to layer" submenu does the same for adjustments and reference images.
  • The keyboard focus ring frames the layer row itself, so the thumbnail and name sit centred in it.

Generate

  • In Canvas mode, "Scale before processing" moves into the Size section as a "Render at" select (Frame size, Optimal for model, Custom). The badge, status line and preview outline show the size the model generates at. Stored values and graph compilation are unchanged.
  • The model-switch confirmation no longer empties while it closes.
  • In Canvas mode, Denoising strength sits in the Render section under the guidance scale; its percentage and wave join the section's header badges. Scrubbing re-renders only the field and badge.
  • Dynamic Prompts in Random sample mode draw a new sample after every Invoke; a "New sample for every Invoke" switch (default on) turns this off for a repeatable sample. The queued item keeps the previewed draw and the preview moves on to the next one. All combinations mode is unchanged.

Gallery and Preview

  • Double-click or Enter opens an item in the centre Preview.
  • Image menu: "Add to Current Canvas" and "New Project from Image(s)".
  • Delete confirmation has "Don't ask me again".
  • Arrow keys step from the current item while a generation is in progress, and from a starred selection beyond the starred strip, in both the grid and Preview. Preview's filmstrip now runs starred, in-progress, then the listing, matching the grid.
  • Load failures show an error with Retry instead of empty or unrelated results; keyboard focus stays with the selection across virtualized rows; Shift+Arrow extends, Ctrl/Cmd+Arrow moves focus only.

Workbench

  • Contextual keyboard shortcuts and Canvas gestures move out of Properties into a movable status-bar widget, placed immediately before Notifications in built-in layouts. The whole chip opens the guide; it shows configured Invoke/Cancel shortcuts outside editing contexts, fits complete hints into available space, and preserves editing focus. Existing saved layouts can enable it through Bottom Widgets.
  • Alt+Shift+Arrow (Ctrl+Option+Arrow on macOS, where Option+Shift+Arrow selects text and Ctrl+Arrow chords belong to Mission Control) moves focus between visible regions without changing their selected widgets; Alt+PageUp / Alt+PageDown cycle the focused region's enabled docked panel views with wraparound. All six shortcuts can be rebound in Settings → Keyboard shortcuts, work from editable fields, and pause while a dialog is open.
  • Keyboard navigation handles hidden panels, multiple instances of the same widget, lazy loading and overlapping floating windows. Highlighted frames show one accent outline; buttons and inputs keep their own focus indicators, and disabling region highlighting restores the native frame indicator.
  • Each layout preset keeps its own unsaved arrangement across switches, marked with a dot, and can be reverted.
  • "Scan models folder" in the model manager.
  • The app menu's "Home" item is now "Launchpad".
  • Models, Nodes, Fonts and Preferences share one list/detail layout: side by side when both fit, one pane with Back below 50rem. Narrow windows get an icon nav rail, so the managers are usable at 200% zoom (previously the content area had zero height).

Queue and results

  • Result videos keep the board a node or the user put them on; only unassigned ones join the destination board.
  • The queue widget's item lists scroll under fixed stats and filter tabs.
  • Reconnect/visibility sweeps are bounded to 16 concurrent reads. Home polls queue status only (1 request per poll instead of 5 plus a 50-item hydration).
  • New covering index on session_queue; batch lookups and the bulk cancel, clear and prune statements are pinned to the status and batch indexes rather than walking the queue's history.
  • The queue item-id listing takes an optional limit; the frontend requests the 50 most recent. Queue status counts are computed in one grouped query.

Workflows

  • Library records are served by revision and fenced against refills from reads that span an invalidation.
  • Image export plans its scale against pixel/side budgets (reduce, then refuse) and waits for nodes to be measured. That wait fixes nodes missing from the PNG, which was the cause of the intermittent export pixel test.
  • Form, form builder, add-node dialog, node and editor strings are localized; "legacy Linear UI" wording is gone; the add-node dialog can retry a failed load.
  • "Update library template" acts on the workflow being edited: reopening the library starts on the active workflow, and the confirmation names only the template it replaces. "Save as new" renames the workflow after the template it created, so a later update reads and writes that template, not the one the workflow was first opened from; a rename made while the save is in flight stands, and the adopted name is its own undo step.
  • Selecting another workflow on a Call Saved Workflow node keeps connections the new workflow still accepts (including reroutes) and takes the new workflow's input values; a refresh of the same workflow keeps edited values. A failed load after a switch clears the previous inputs, and removed connections are reported. Library and export JSON written mid-switch carry no inputs for that node, so a reopen builds them from the selected workflow.

Interaction and accessibility

  • Any open dialog suspends global, widget and canvas shortcuts, including on the key press that dismisses it and while a lazily loaded dialog's code is still downloading. Dialogs share one root; direct Chakra dialog/drawer imports are lint-restricted.
  • Model picker: valid combobox/listbox semantics, field label association, IME-safe keys, windowed list that opens at the selection.
  • Scrubber sliders fill to the frame edge at their maximum, and a drag starting from a typed value beyond the track no longer snaps to the track end.
  • Video model switches that would discard media, LoRAs or components ask first and list the losses.

Internals

  • Generate sections read drafts through immutable snapshots and typed selectors instead of a live proxy typed as settings.

Related Issues / Discussions

Resolved issues:

QA Instructions

  • Local pnpm -C invokeai/frontend/webv2 check:release passed on 5ee4e13a79: lint/types/architecture, 9,909 unit tests, 331 Chromium files / 2,689 tests, fixtures, build/browser performance gates, project-file integration and accessibility journeys.
  • All applicable hosted CI checks passed on 5ee4e13a79, including the full frontend release gate and Linux/macOS/Windows Python suites, frontend/API/type-generation checks, Python lint/lock checks, LFS and docs/build. Deployment is intentionally skipped for this PR.
  • Shortcut guide: tests cover custom and reserved bindings, empty state, modal hide/restore, source cleanup, lock caching, and crowded status bars. An 18-step Chromium visual journey checked classic/light themes, 700px/900px layouts, 6px edge padding, whole-chip activation, focus restoration, movement and hiding.
  • Video confirmation and floating-window navigation regressions now assert against real lifecycle/interaction boundaries. The dialog test still fails if closing-text retention is removed; navigation verifies a deliberate pointer return survives the next animation frame. No retries, fixed sleeps or assertion removals were added.
  • The changed Canvas, Generate, Layers and Queue surfaces were checked in Chromium against the representative mock backend: tool menus and the cursor icon, the Render section badge and strength field, the app menu, the blend/opacity row, brush scrubbers at 100%, the focused layer row at 3x, and the queue list scrolling under fixed stats.
  • A fresh production build was visually checked in Chromium with the representative mock backend: region movement, widget cycling, the single frame outline, editable controls, shortcut settings and modal suppression.
  • pytest tests/app/services/session_queue tests/app/services/shared/sqlite_migrator tests/app/routers/test_session_queue*.py tests/test_sqlite_migrator.py tests/app/invocations/test_frontend_anima_lllite_graphs.py: 422 passed, 2 deselected, on the merged tree.
  • Not run locally: the full backend suite, Firefox/Safari, a screen reader, a real IME, a real backend with GPU.

Not exercised against a real backend (covered by browser tests with intercepted routes or by graph validation only):

  • A generation with an Anima model and an LLLite adapter. The compiled graph is checked against the backend's node classes; no model was loaded.
  • The cancel request behind accepting a result while its batch runs, and its failure notice.
  • The canvas pixels of a hovered blend-mode preview (the mock fixture's layer images do not decode) and dragging adjustments between layers (browser tests with real pointer drags only).
  • The image and board writes behind saving a staged result.

Measurements (local, unthrottled, production build against the mock backend, interleaved before/after):

Before After
First Workflow editor activation, 100 nodes (longest task) 452 ms 259 ms
Project switch to a small project (longest task) 150 ms under 50 ms
Model picker open, 2,000 models 833 ms 65 ms
Preview filmstrip thumbs re-rendered per Gallery arrow key 96 2
Home queue poll 5 requests + 50 items 1 request
Edit kept after a reload right after typing (Chromium, 55 runs) 12 55

Queue index, 10k rows / 316 MB on-disk SQLite, warm (measured before the limit and grouped-count changes):

Before After
Item id listing ~60 ms ~3 ms
Status counts (per user) 39–92 ms 1–2 ms
Worker status transition 42 ms 9 ms
Enqueue 100 items at 110k rows 132 ms 24 ms

Editor cold start and first Canvas activation were profiled and are unchanged.

Manual checks worth repeating:

  1. Two tabs, multi-user: sign out in one → the other lands on login; sign in as another user → the other follows.

  2. Type in a Generate field and immediately reload → the edit is there. Repeat going Home and reopening the project.

  3. Open a Gallery delete confirmation and press Escape → the selection is kept; Ctrl/Cmd+Enter does not invoke while it is open.

  4. Models/Nodes/Fonts at 720 px wide and at 200% zoom → list, detail, primary actions and Back are all reachable.

  5. Canvas with a small frame: Size shows frame → render size; switch "Render at" between the three modes and invoke.

  6. Start a generation with starred images in the Gallery, click an image and press the arrow keys in the grid and in Preview.

  7. Rearrange a layout preset, switch to another and back → the arrangement and its dot are kept.

  8. From a prompt field, use Alt+Shift+Arrows to move between regions → the selected widgets stay the same and only one accent frame outline shows. Close a side panel and repeat; try leaving a floating window that overlaps the docks.

  9. Use Alt+PageUp / Alt+PageDown → the focused region's panel views wrap, Tab enters the selected widget after it loads, and a region with one eligible widget stays open. Rebind the commands in Settings → Keyboard shortcuts; confirm a dialog suspends them.

  10. Open "Text to Image - SDXL" from the library, edit it, save it to the library as "demo", edit again, then "Update library template" → the workflow is now named "demo", the confirmation names only "demo", and the update lands there. (Checked against the mock backend: one create, then one update of the new template.)

  11. Wire a Call Saved Workflow node, then select a copy of its workflow → compatible connections stay; select an unrelated workflow → a notice reports the removed connections.

  12. Drag brush size, opacity and a layer adjustment while pressing a tool hotkey or undo → the other edit survives the release; hover blend modes and press Escape → the original mode returns.

  13. In Canvas, change tools and focus Properties → hints follow the tool without taking Properties space. Open the status-bar guide by click, Enter or Space; Escape returns to the editing control. Open a modal → the chip disappears and returns after dismissal. Move/hide the widget, try custom bindings, and narrow the window → complete hints collapse while Notifications stays visible.

Screenshots were captured locally for the UI changes but are not attached here.

Review

Resolved before merge-readiness:

  • The floating-window navigation fixture omitted production pointer activation, so a pending focus-restoration task could override its programmatic focus. It now uses the actual pointer interaction and checks focus across an animation frame before the next directional command.
  • The video-model confirmation browser test could inspect an already-unmounted dialog after awaiting a browser click. It now captures text at the real closing transition, then waits for removal before checking the captured text and final stored values.
  • Shortcut guidance measured neighboring widget DOM, re-registered on Canvas lock changes, duplicated key formatting, and could display empty or reserved-key instructions. The status-bar host now owns capacity and modal visibility; source identity is stable, lock reads are cached, formatting is shared, and the guide selects executable bindings. Empty guidance retains a settings route, View lists Pan once, and obsolete Properties instruction code and locale keys are removed.
  • A timed-out export could permanently cache placeholder fonts in the capture library; the abort signal no longer reaches it.
  • Leaving the editor with a stalled backend could abandon unsaved edits without a recovery draft; the exit now saves committed state first and bounds the pixel flush.
  • The Escape that dismissed a dialog also fired background Escape commands.
  • Single-pane managers had no route to the Add tabs from a non-empty list, and the pane could flip when an empty library received its first item.
  • A single Gallery tab stop removed keyboard multi-select; restored with Shift/Ctrl+Arrow.
  • An unload journal entry could outlive the state it described and silently overwrite newer server state; entries are now marked settled and reconciled against the saved revision.
  • Invoking by hotkey with an invalid control layer showed a raw reason code instead of the localized message.
  • Toast actions from a staged save were not fenced to the account that created them.
  • Preview's arrow shortcuts fired while moving or resizing a floating window and inside the Details tabs.
  • The "Render at" select's legacy name was exposed to assistive technology only while its tooltip was open.
  • Scrubber gestures kept the handlers they started with, so adjustment, mask modifier, reference image, text, shape and gradient commits wrote snapshots from the gesture's start and reverted concurrent edits (an upload, a toggle, a hotkey, an undo). Each now patches only its fields against the live document and options.
  • A drag starting from a brush or text size above the track snapped it to the track end.
  • ArrowRight on a tool slot opened its menu and also nudged the selected layer.
  • Scrubbing denoising strength re-rendered the whole Render section on every step.
  • Call Saved Workflow switches left edges from unresolved reroutes pointing at removed inputs, carried the previous workflow's values into a different one, and kept the previous inputs after a failed load.
  • User docs still described the separate denoising section and the "Accept and Stop Batch" label.
  • A floating window spanning the docked regions could exclude every directional focus target; navigation now uses its center and can return to the underlying dock.
  • Replacing a lazy widget's loading frame dropped keyboard focus; navigation now focuses the owning region and waits for replaceable side frames to finish loading.

Resolved from the external review of this PR:

  • A deliberate sign-out in one tab during another tab's in-flight password change was deferred and then undone when the change stored its replacement token; it now ends every tab at once and the late replacement is discarded.
  • A password change in one tab revoked the other tabs' token before the replacement was stored; their next request signed them out and deleted the account's IndexedDB, closing the changing tab's connections. The change is announced in storage and other tabs hold that 401 (and a restoring tab its rejection) until the replacement arrives, for at most 30 s.
  • A sign-in attempt cleared the stored token before it succeeded, signing the other tabs out on a mistyped password.
  • An undo during a blend-mode hover or a scrubber drag was reversed when the preview ended, a hovered value leaked into another commit's inverse, an rAF-coalesced preview could overwrite an undo, and an undo that removed the scrubbed item toasted a spurious "missing" error. The engine now owns the preview baseline and restores it whenever a replay or a commit from elsewhere ends the session.
  • Another tab could adopt a hidden tab's unload-journal entry and bring back an edit that tab then undid; entries name the page that wrote them and are left alone while that page's Web Lock is held. The route peek ignores other running pages' entries, and a reload re-claims its session for up to half a second while the old document lets go.
  • One failed journal deletion disabled unload protection for the rest of the session.
  • An edit committed while the last tab's empty session was being written was never saved.
  • Alt+Shift+Arrow overrode Option+Shift+Arrow text selection on macOS; the macOS default is now Ctrl+Option+Arrow.
  • Staged-save notices outlived an account switch; the Gallery grid stepped from the first starred tile when a starred selection was beyond the strip; the paste and export dialogs let shortcuts through while their code loaded.
  • Bulk queue cancel, clear and prune statements scanned the whole queue history under the new listing index.
  • Saving or exporting a workflow while a Call Saved Workflow switch was pending kept the previous workflow's inputs under the new id; "Save as new" could overwrite a rename made while it was in flight and fold the adopted name into the user's typing step.

Remaining limits:

  • The arrow-key report (selection jumping to the first starred image during a generation) was not reproduced as described, because the mock backend cannot run a generation. Five related defects were found and fixed.
  • The unload journal skips projects over 8 MiB, and in one crash-timing case an undone edit can return; both are documented in the projects README. Only Chromium was measured. Without Web Locks (a plain-HTTP deployment) a page cannot prove it is still running, so every journal entry is reconciled as before; a duplicated tab (copied session storage) waits half a second before taking a session of its own.
  • Behind a stalled in-flight save, later edits are lost if the 10 s re-entry bound fires.
  • Queue status counts still grow with history, and a project with fewer than 50 queue items reads the whole item-id index; history is uncapped by default.
  • Arrow keys inside Preview's context menu also step the image.
  • PiD does not force "Render at" to Frame size.
  • Focus navigation retains the existing one-second wait for a lazy side frame to become ready.
  • Changing the selected layer while the blend trigger has focus drops focus. During a password change in another tab, this tab's requests fail with 401 errors until the replacement token is stored (queries that errored stay errored until retried); if the announcing tab never settles, the session expires after 30 s. A legacy-frontend tab open alongside does not know the announcement and clears the shared token on its own 401, which signs every tab out (the password change still applies).
  • Call Saved Workflow connections survive a switch only between workflows that share child node ids (copies or versions of one template). A transient load failure right after a switch clears the previous inputs; undo restores them.
  • The "Move to layer" submenu lists groups and their layers at one level.
  • Arrow and Page keys on brush/text size step up to the typed limit (2000px) rather than stopping at the 600px track end. The gradient stop offset has no live preview while dragging.
  • Earlier full-suite runs exposed timing-sensitive browser tests (InvocationFlowNode, QueueDataRuntime, IntermediatesManager, Gallery Show all, and AddNodeDialog) that passed in isolation. The video-confirmation exit assertion and floating-window navigation fixture now exercise their actual lifecycle/interaction boundaries. The current full local and hosted release gates passed.
  • The review fixes were verified by the unit and browser suites and the release journeys; the macOS chord rests on documented bindings (no macOS machine was available), and the cross-tab credential and journal behaviour on real storage events and Web Locks in Chromium only.

Compatibility / Rollout

  • The shortcut guide adds no persisted hint state, API changes or migration. Built-in layouts place it before Notifications; saved/custom layouts and deliberately hidden widgets retain their placement and visibility. Performance source inventories include the guide; numeric budgets are unchanged.
  • New forward-only migration 2026_10_04_add_session_queue_listing_index: one CREATE INDEX IF NOT EXISTS, no data rewrite. Build time measured at ~0.1–0.3 s on a 316 MB database; expect seconds on multi-GB databases. As with any migration, an older build will not open a migrated database.
  • API, additive: GET /api/v1/queue/{queue_id}/item_ids accepts an optional limit (1–1000). With a limit, total_count is the number of ids returned; without one the response is unchanged. anima_lllite is a new control kind. openapi.json and schema.ts are regenerated.
  • Project documents gain an optional presetWorkingLayouts list (up to 16 entries). Older documents load unchanged.
  • Call Saved Workflow nodes carry an optional callSavedWorkflowFieldsFrom marker while a workflow switch is pending; documents without it behave as before, and library/export JSON and backend graphs never include it.
  • A new per-account IndexedDB database holds the unload journal. It is separate from the existing draft store and safe to clear. Journal entries gain an optional ownerEditorSessionId; entries written without it are reconciled as a gone page's.
  • A new localStorage key auth_token_rotation announces an in-flight own password change to other tabs ({at, userId}); it is written before the change and removed after, and holds nothing a same-origin reader could not already take from auth_token.
  • Canvas scaling settings keep their stored keys; only their place in the UI changed.
  • Performance baselines were re-recorded. Startup request counts match main; the launchpad and editor initial sources gain the editor-session lock probe (about 4 KB raw, 1.6 KB compressed), grouped into the route-shared chunk so no request is added.
  • Behaviour changes users will notice: other tabs follow sign-out/account switch; the Gallery grid is one Tab stop (star with .); the picker's search resets each time it opens; the Gallery toggle-focused-item command ships unbound; switching layout presets keeps unsaved arrangements; Preview's filmstrip order; the "Scale before processing" section is now "Render at" under Size; configurable shortcuts move between regions and cycle widgets (Ctrl+Option+Arrow on macOS); Denoising strength moved into Render; tool slots open labelled menus; tool and layer properties use scrubbers; the staged accept button keeps "Accept to Layer"; "Home" in the app menu is "Launchpad"; font axes are named without their tag ("Weight"), and the shape "Width" row reads "Stroke width".

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Meaningful regression coverage added / updated where needed; obsolete tests/code removed
  • Persisted-state and API changes include required migrations / compatibility validation
  • Relevant performance/efficiency opportunities considered; material claims have evidence
  • Material review findings resolved and relevant checks rerun
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

Result videos are read before routing, so only unassigned ones join the destination board and repeated settlement no longer moves them.
Reconnect, visibility and interval sweeps reuse the 16-read bound instead of reading every tracked slot at once.
…d reads

Open, replace, fork, download and browse enrichment pass the list's revision, so a refreshed list never uses an older cached graph.
Reads in flight across an invalidation no longer repopulate the cache; retention is bounded and the unused page cache is removed.
…reshed tokens

Requests capture an in-memory token with its account lifetime, so another tab's sign-in can no longer send one account's work under another's token; other tabs follow the stored credential through an explicit sign-out or account transition.
Refreshed tokens are adopted request-fenced, own password changes keep the session, and sockets authenticate each connect with the current token.
Performance baselines re-recorded for the changed initial module set; request counts unchanged.
The export plans its scale against pixel and side budgets before any work, reducing resolution or refusing oversized workflows with a clear notice.
Capture waits for nodes to be measured and visible, which fixes nodes missing from the PNG; failed image decodes fall back to alt text instead of hanging, and one export runs at a time.
…ject transitions

Leaving the editor now saves the latest committed state, pending drafts and Canvas pixels instead of cancelling the autosave; re-entry waits for that save, and hiding the page saves immediately.
Project create, open, switch and close flush pending drafts at the transition itself, so an edit typed during a slow open or close stays on its own project.
The paragraph still described Workbench and the Features as one transitional cycle; the ownership test now fails on any cycle and no migration exceptions remain.
…s on demand

The Home queue badge makes one status request per poll instead of loading the full queue read model.
Generate's seed history and run-time estimate observe the project queue only while a control showing them is mounted.
Performance baselines re-recorded on the merged tree: identity's initial module swap, one fewer editor script request, about +4.5 KB raw on the launchpad and +9 KB raw on editor routes.
Dialogs are built on one shared root that announces modal presence itself, so every dialog blocks global, widget and canvas shortcuts without per-dialog registration; the key that dismisses a dialog no longer reaches commands behind it.
Direct dialog and drawer imports are lint-restricted, the per-caller modal layer is removed, and performance baselines are re-recorded for the changed initial modules with request counts unchanged.
…fast with large catalogs

The picker's search, list and options follow the combobox/listbox pattern with instance ids, the model select takes its name and description from its field, and composing keys no longer select a model or insert a node.
The option list is windowed and opens at the selected model; truncated names expose their full text to assistive technology. Performance baselines re-recorded for two added initial modules with request counts unchanged.
…ated results

Gallery and the media picker report loading, empty, failed and stale states per scope from Query, so a failed listing, page, board list or starred strip shows an error with Retry and never another scope's items.
Retry keeps the scroll position and selection, failures are announced through a live region, and the compact chip no longer claims zero items.
Hiding or leaving the page commits drafts still held in fields before saving, so an edit typed just before switching away is no longer left out; a failing draft commit is logged without blocking the save.
Also keeps a just-opened tooltip off-screen until it is placed, and stops the status bar's bottom trigger from overflowing the page by a pixel.
Edits to a model's details and default settings are retained per model for the account's session, so switching manager tabs or models no longer discards them; saving, cancelling, resetting or removing the model clears them.
A save still in flight is tracked per model, server changes rebase untouched fields, and the library row marks models with unsaved changes.
Choosing a video model that would remove references, the initial video, conditioning clips, frames, LoRAs or component choices now lists those losses and applies only on confirm; lossless switches stay immediate and report adjusted values in a toast.
The switch is recomputed against current settings at confirm, and pending concept drafts are committed first.
…wording

Form, form builder, add-node dialog, toolbar and node strings go through localization with one vocabulary (form, workflow editor, pin), and empty states say what to do next instead of referring to the legacy Linear UI.
The add-node dialog can retry a failed node list load, and icon buttons use the platform tooltip instead of native titles. Performance baseline re-recorded for the larger English locale file.
…s and explicit selectors

Sections subscribe to the draft store with typed field selections instead of a live proxy typed as settings, so a value read in one render stays what it was and handlers read the latest draft explicitly.
Section-only re-renders on scrub and the concept-row behavior are unchanged; the unused aggregate model policy is removed.
…ed rows

Focus no longer falls to the page when the focused thumbnail scrolls out of the rendered rows, is deleted, or the board changes; arrow keys move focus with the selection and the grid has one thumbnail Tab stop.
Shift+Arrow extends the selection, Ctrl/Cmd+Arrow moves focus only and Ctrl/Cmd+Space toggles the focused tile; the hotkey resolver compares mod and ctrl bindings in platform form so scope decides between them.
…y load-more error at the list end

The picker scrolls to its active option after rows are measured, so a selection near the fold or at the end is fully visible on open.
The gallery's scroll content no longer shrinks to the viewport, so the load-more failure row sits after the last items and retry keeps the scroll position; a starter-bundle region and the default-settings number inputs get proper accessible names.
Cmd+Space is Spotlight on macOS and the legacy gallery has no such shortcut; Ctrl+Space on the focused thumbnail still toggles it, and the command can be assigned in the hotkey settings.
…n the batch index

A covering index serves the item listing and status counts without reading session blobs, and the listing orders ties by item id explicitly.
Enqueue's batch probe and readback, the owner-scoped batch status, the pending count and summaries are pinned to their selective indexes so they no longer scan a user's or queue's whole history; bulk cancel ids are sorted, and the legacy list cursor no longer bypasses its filters.
… font managers

The managers and preferences use one platform layout that shows library and detail side by side when both fit and one pane at a time with a Back control when they do not, keeping selection, scroll position, drafts and focus across the switch.
Narrow windows get an icon navigation rail so the page stays reachable at 200% zoom, the library header offers the add action, and install and activity status stays visible in both panes.
…ts own flow store

A newly shown graph that will fit below the detail zoom mounts its nodes as skeletons instead of rendering every field and discarding it at the first fit, roughly halving the first activation's long task on a 100-node workflow.
Each workflow gets its own React Flow store, so switching projects no longer notifies the outgoing graph's nodes; preview filmstrip thumbnails re-render only when their own selection changes.
@github-actions github-actions Bot added python PRs that change python files services PRs that change app services labels Oct 5, 2026
@joshistoast
joshistoast requested a review from lstein October 6, 2026 22:49
Capture confirmation text at the closing transition before the dialog exits, preserving the loss-list and stored-state assertions.
Match floating-window pointer activation so deliberate navigation cancels pending focus restoration. Verify focus remains on the window before the next directional command.
Random sample mode reused one sample seed for every submission, so the same draw repeated until Shuffle.
A "New sample for every Invoke" switch (default on) rotates the seed at enqueue, alongside the image seed advance, so the queued item keeps the previewed draw and the preview moves to the next one.
Keep the multi-user sharing notice and omit known bugs fixed by this branch.
Include main's updated code ownership rules.
…ued them

Capture the account scope and principal when a credential rotation is queued and reject it unsent if that lifetime ends before dispatch, so a queued reset never carries the next account's token.
Add regression tests for a cross-tab account switch and a same-user re-login, and document the queue fencing.

@lstein lstein left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at fc820d3. Thanks for the thorough round. Every round-1 finding is fixed or partly fixed, and each fix comes with a test that fails when the fix is reverted. One new issue blocks merge, in the dynamic-prompt resampling added since round 1. Everything else below is a non-blocking follow-up. Paths are relative to invokeai/frontend/webv2/src/ unless noted.

Round-1 status

Finding Status
1. Sign-out undone by another tab's password change Fixed
2. Another tab's 401 during a password change signs out and deletes the account databases Fixed for a single rotation (see follow-up 1)
3. Undo during a blend/scrubber preview reversed Fixed
4. Another tab adopts a live tab's unload-journal entry Fixed in secure contexts; see follow-up 7
5. macOS Option+Shift+Arrow taken over Fixed (see follow-up 6)
Login attempt signs out other tabs; staged-save toasts outlive the account; blend preview leaking into another snapshot; coalesced adjustment preview overwriting an undo; sticky isJournalUnavailable; edit made while closing the last tab; bulk cancel/clear queries on the listing index; Call Saved Workflow saved mid-switch; Save as new overwriting a rename; starred selection beyond the strip; lazy dialogs not suspending shortcuts Fixed
Spurious "missing" toast when undo removes the scrubbed item Fixed for removal (see follow-up 2)

The queue plans check out with EXPLAIN QUERY PLAN. Every statement listed in round 1 now uses the round-robin or batch index, rowcount gives the same per-user count the old COUNT(*) did, and the session_queue tests pass.

Blocking: a quick repeat Invoke queues the same dynamic-prompt sample twice and records the wrong sample seed

Introduced in a304936. Code: workbench/workbenchState.ts:3583-3599, the submitResolvedInvocationSnapshot reducer at :4718-4736, and workbench/invocationSubmit.ts:89-96.

Prompts are expanded against the dynamicPromptsSampleSeed captured in the click-time snapshot. The reducer, though, builds the submission from the current project, and its rotation guard compares values.dynamicPromptsSampleSeed with dynamicPromptsSettings.dynamicPromptsSampleSeed, which is read from that same current project. The guard therefore always passes and rotates the seed no matter which seed the expansion actually used.

Sequence (random mode, resample on, seed S1):

  1. Invoke #1 is served from the expansion cache, queues P(S1), and rotates to S2.
  2. Invoke #2 captures S2, and its expansion goes to the network.
  3. While #2 is in flight, Invoke #3 (a double-click, or a repeated Ctrl+Enter) also captures S2.
  4. #2 dispatches P(S2) and rotates to S3.
  5. #3 dispatches P(S2) again, records dynamicPromptsSampleSeed = S3 in its recall metadata, and rotates again.

Impact:

  • Two queued items carry the same sample, which breaks "a new sample every Invoke".
  • The second item's recall or remix reproduces a different prompt from the one it generated. Before this commit the seed never changed on Invoke, so recall always matched.

Suggested fix: carry the seed the expansion used on the action, then build the recall metadata and run the rotation guard against that seed. Alternatively, serialize Generate submits the way Canvas does.

Tests: the "rejected" test also passes without the feature, because steps: NaN returns before enqueue. Mutating the rotation guard to true, or dropping the canvas→generate mapping, leaves the suite green. A reducer test that interleaves two submits with the same captured seed would cover it.

Non-blocking follow-ups

  1. Concurrent password changes share one announcement slot.

    • Where: features/identity/tokenStorage.ts:3 (one key); session.ts:409 overwrites any existing marker; withdrawAnnouncedRotation (:505-518) never restores a marker it overwrote.
    • Sequence: two tabs of the same user submit password changes almost at once. Y2 overwrites Y1's marker. The server commits Y1, so Y2's PATCH gets a 401. Y2 withdraws its own marker, then handles that 401 with no marker in place.
    • Result: Y2 signs out and deletes :user:<id>. Y1 sees removed and aborts, and every tab ends up expired.
    • Fix: keep announcements per rotation, for example keyed by tab id.
  2. Spurious toast after undoing a conversion mid-drag.

    • widgets/canvas/useStructuralCommit.ts:317 silences only missing on an engine-ended session.
    • Undoing a raster→control conversion while dragging the Weight scrubber unmounts ControlLayerSettings. Its unmount settle then gets wrong-type and shows refusedWrongType. Undoing an unlock mid-drag does the same with locked.
    • Fix: treat any refusal on an engine-ended session as silent.
  3. Async edits now end an unrelated open preview. The recheck at ctx.getDocument() after an await ends any preview opened in the meantime:

    • copyLayerController.ts:64
    • cropLayerController.ts:95
    • layerMutationController.ts:153
    • mergeLayerController.ts:288,444
    • booleanMergeController.ts:122
    • extractMaskedAreaController.ts:152
    • generatedResultController.ts:71

    Example: press Ctrl+J on a layer that needs async preparation, then immediately hover a blend mode. The canvas reverts while the option stays highlighted. History stays correct, so this is cosmetic.

  4. commit/commitPrepared check staleness before ending the preview (structuralLayerController.ts:149-156,165-172). endPreview() bumps the revision after staleRevision has already passed. I found no UI path to this; the exposed callers are the useStructuralCommit.ts:311 fallback and replaceAllFontReferences. Fix: end the preview first.

  5. The starred-selection fix also fires for a selection outside the current scope (features/gallery/ui/GalleryImageGrid.tsx:348-360).

    • A board switch keeps selectedImage (selectGalleryBoard); search, filters and paging can also hide the selection.
    • Select a starred image on board A, switch to board B, and press Right. The first arrow skips B's whole starred strip.
    • Fix: gate on the selection matching the current board and filters, as revealTargetPage does.
  6. The macOS default Ctrl+Option+Arrow is VoiceOver's navigation chord (VO+Arrow). Screen-reader users, who most need region navigation, cannot use the default. The code comment's "no stock system shortcut" is inaccurate.

  7. Unload journal residuals.

    • Web Locks need a secure context, so on plain-HTTP LAN deployments, which are common for multi-user installs, round-1 Major 4 reproduces unchanged (disclosed in the README).
    • The same symptom remains through the pre-existing staged-draft adoption path (durableSyncedPersistence.ts:757-778), which the new README paragraph implies is closed.
    • Two timing windows in editorSession.ts:69-88:
      • A duplicated tab whose claim is retrying can take the original's session when the original reloads, and recover its edit.
      • If the old document holds its lock past the ~500 ms retry budget, the reload's own journaled edit is skipped, so "its own entries are always reconciled" is too strong.
  8. Shortcut guide (0ee832d).

    • hintSources.tsx:30-38 updates only on focusin. After clicking non-focusable background, the guide keeps describing the old element and hides "Cancel generation", while the runtime resolves from BODY.
    • ShortcutGuide.tsx:87-99 can show "On Canvas" with only global hints when the canvas has no registered hint source.
    • Passing null as the resolver target at :109 leaves the guide tests green.
  9. Test value. useStagedResultGallerySave.browser.test.tsx's "takes a plain report away with the account too" passes without the dismiss listener, because the toast's ~2s default duration expires inside the 3s wait. Assert right after activate() or use fake timers.

Concurrent own-password changes in several tabs no longer overwrite one another's rotation marker, so a tab whose change lost the race waits for and adopts the other replacement instead of signing every tab out.
The hold now ends thirty seconds after the latest live announcement, future-dated markers stay bounded from first sight, and expired or legacy single-key markers are cleared when a tab announces.
A load no longer adopts, auto-opens, or offers for recovery a staged draft whose editor session another running page still holds, matching how unload journal entries are reconciled; without Web Locks every draft is adopted as before.
One memoised liveness check now backs the journal reconcile, the draft filter, and the editor route, and the README states the claim-retry limits and the handed-on lineage exception plainly.
…at drew it

Every active submission now takes the preparation lease before its first await, so a repeat Invoke during prompt expansion or generator resolution is dropped instead of queueing the same sample twice; the Invoke button and the graph preview dialog show the preparing state for all routes.
The expanded prompts travel with the sample seed they were drawn from, which the queue item records for recall and which gates the rotation, so a late submission never rotates again or records a seed it did not use.
Canvas submissions now forward their expanded prompts, which previously never reached the queue.
…gation

A starred selection left over from another board, search, or filter no longer becomes the starred strip's last arrow-key entry, so the first Right on a new board selects that board's first starred tile.
The scope comparison that reveal paging already used now lives in one helper shared with the grid.
…t switch itself

The test waited long enough for the toast's own timeout to expire, so it passed without the dismiss listener; it now checks the toast is open right before the switch and closed right after it.
…ord off VoiceOver

The status-bar guide now updates when focus leaves for the page background, titles itself for the canvas only when a canvas hint source is mounted, and no longer hands the page body to popovers as a focus-restore target.
Region focus on macOS defaults to Control+Command+Arrow, since Control+Option+Arrow is VoiceOver's navigation chord, and the hotkey recorder now captures both Control and Command when held so that default can be recorded.
…lence engine-ended gestures

Async layer edits recheck staleness against what they actually read, so a hover preview opened while a copy, duplicate, merge, or extract prepares stays visible until the edit lands and ends it; a preview on the source itself still refuses.
A commit from a gesture the engine already ended no longer reports a refusal, a stale commit from a superseded session ends the other gesture's preview before preparing, and commits end their own preview before the stale check so previewed values never reach history.
Ending a preview on admission releases the history admission if the restore throws.
…rywhere

macOS keycaps now read Control, Option, Shift, Command, so the region chord shows as Control then Command.
The layer context menu and the command palette footer render keys through the shared glyph component instead of plain text.
… and palette

The glyph and word labels for bindings, and the aria-keyshortcuts form, now live with the other key formatting, so the command palette button's tooltip reads ⌘K on macOS and Ctrl+K elsewhere like the top bar does.
Resolves the video settings import conflicts by keeping both sides' helpers and dropping imports whose last uses each side removed.
Preserve gallery focus, thumbnail refresh, and compact Help donations.
Update browser fixtures for current workbench and router exports.
Match the app provider required by the donation menu.
Isolate and clear configuration query state for each shell test.
Prevent delayed dialog autofocus from redirecting Enter to the search field.
Gallery maintenance pulled Button and ConfirmDialog through the platform UI
barrel, pushing direct importers to 161 against the budget of 160 and
failing the architecture performance gate.

@lstein lstein left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 59f6200. The round-2 blocker is fixed, so I'm approving.

Dynamic-prompt sample seed (a5a9108): fixed.

  • Each expansion now carries the seed it was drawn from. Recall records that seed, and the rotation fires only if the project still holds it.
  • The per-project preparation lease drops a repeat Invoke while the first one is still preparing.
  • An end-to-end run of the round-1 sequence against the real store and submit path gives two queue items, each recording its own draw, with a single rotation. Reverting the fix brings back the original double rotation and the wrong recall seed.
  • Each part of the fix is covered by a test that fails when that part is reverted.
  • The lease is released before the next click on the synchronous routes (Generate, Upscale, Video), so rapid repeat Invokes there still queue normally.

Forwarding the expanded prompts on the Canvas route also fixes Canvas queuing literal {a|b} syntax, which it still does on main.

Most of the round-2 follow-ups are addressed in this round as well. Thanks.

Non-blocking notes:

  1. Generate recall can pair an edited prompt with the earlier draw (workbench/workbenchState.ts:4729-4740 with :3512-3526). The Generate reducer compiles from the project as it is at dispatch time, but positivePrompts and their seed come from the snapshot taken at click time.
    • Sequence: edit the prompt while its /dynamicprompts request is in flight.
    • Result: the backend receives the expansion of the old text, while positivePrompt, the queue presentation and recall.generateValues.positivePrompt show the new text.
    • This behaviour predates the PR and the window is one round trip, often cached. Canvas is unaffected because it compiles from click-time values.
  2. One stalled preparation now disables Invoke on every route in the project (workbench/activeInvocationSubmission.ts:82-112, invocationPreparation.ts:23, useInvocationState.ts:117).
    • The lease is keyed only by project, and apiFetchJson has no timeout.
    • A Canvas Invoke whose paint upload or compose stalls, or a workflow generator request that hangs, leaves Generate, Upscale and Video showing "Preparing generation…" until that request settles or the page reloads. Before this change only the Canvas route was gated this way.
    • It does clear in finally, but the wait has no upper limit. Bounding the preparation, or keying the lease by route, would contain it.

@joshistoast
joshistoast merged commit c7a1b55 into main Oct 8, 2026
14 checks passed
@joshistoast
joshistoast deleted the plan/webv2-quality branch October 8, 2026 13:08
Pfannkuchensack added a commit to Pfannkuchensack/InvokeAI that referenced this pull request Oct 9, 2026
Ports invoke-ai#9700 (gallery maintenance: image locations, Database.backup with upstream's reserve-and-clean-up, no file backup of a server database), invoke-ai#9682 (queue listing index as a portable migration with the same id, SQLite only; planner guards as dialect.Unindexed; limited and one-pass status reads) and invoke-ai#9669's tests onto the database layer.
The access guard lists the two SQLite scratch files of gallery maintenance and the output-folder walk, which are not the app's database.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api backend PRs that change backend files docs PRs that change docs frontend PRs that change frontend files python PRs that change python files python-tests PRs that change python tests services PRs that change app services

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: [v7.0.0a] Wildcards do not cycle/shuffle on invoke v7 delete confirmation has no "Don't ask me again" option

2 participants