Skip to content

feat: UI tweaks: inside-label dropdowns, menu arrows, Fit-mode gallery tiles, queue and project polish - #10136

Open
joshistoast wants to merge 15 commits into
mainfrom
feat/ui-tweaks-06
Open

joshistoast wants to merge 15 commits into
mainfrom
feat/ui-tweaks-06

Conversation

@joshistoast

@joshistoast joshistoast commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A batch of UI refinements across forms, menus, gallery, queue and projects:

  • Compact dropdowns: Field gains labelPlacement="inside". Select, Combobox and ModelSelect then show the label at the start of the box, the value right-aligned, and help text below. Converted:
    • Generate: scheduler, VAE and encoders, inpaint adapter, presets, provider options.
    • Canvas: Render at, Infill, Coherence.
    • Widget quick settings: now a label-left row with a value-width select.
  • Quick settings: the Queue, Workflow and Image Map cogs open the mini-settings popover instead of the full dialog.
  • Menu and popover arrows: every menu or popover opened from a trigger now points at it. Context menus (right-click or keyboard), submenus and field dropdowns get no arrow. The arrow never covers content or rounded corners and never blocks clicks, and right-click menus open at the pointer as before.
  • Gallery:
    • In Fit thumbnail mode, the tile border, selection ring and focus ring wrap the image's own rect. Hover tints the square cell, and badges stay on the cell corners.
    • In-progress tiles use the job's target ratio.
    • The destination board uses the "Saving to" icon.
    • The boards toggle shows the sidebar icon whenever the boards sit beside the grid (it used to show the stacked icon in the bottom panel).
    • The picker popover's board labels match the gallery's, with more room below its search.
  • Queue: the filter tabs scroll horizontally on narrow widths, with edge fades and wheel support, and the padding around them is tighter. SegmentTabs gains an opt-in isScrollable.
  • Preview: after deleting an image from its context menu, the menu no longer reopens, and focus returns to the opener or the Preview.
  • Open project dialog: it uses the shared virtualized List with dividers. Incompatible projects show dimmed with their reason. The dialog loads lazily.
  • Queue status: the loading-model chip showed the raw widgets.queueStatus.loadingModel key; it now shows English text, with or without a known model name.

Related Issues / Discussions

None.

QA Instructions

Commands run from invokeai/frontend/webv2 with Node 22.14 and pnpm 10:

  • pnpm check:release:
    • Lint, typecheck and architecture checks passed.
    • 12,423 unit tests and 4,269 browser tests passed.
    • The architecture and browser performance gates passed, with editor startup back at 70 requests / 54 scripts.
    • The project-file journeys passed.
    • The accessibility journeys passed (21/21) after the video-badge locator update.
  • An earlier full run had two browser tests time out under load: InvokeButton "leaves focus where it is when a card it never held closes" and ModelSelect "leads a required picker … to the Model Manager". Both pass when run alone (repeated twice). The ModelSelect one has flaked in batch runs before.
  • pnpm lint:tsc passes on every commit.

Manual checks against the mock backend (pnpm dev:mock):

  • Generate → Render: Scheduler shows an inside label, lines up with Steps/CFG, and search still filters.
  • Preview gear: "Comparison mode" is a label-left row with a bordered value-width select, and its menu opens at the end.
  • Status bar "…" and the "Saving to" menu: the arrow points at the trigger and stays clear of the corners. Right-clicking an image shows no arrow.
  • Gallery settings → thumbnail shape "Aspect": portrait, landscape and square images each get a border that hugs the image, the cell tints on hover, and multi-select and keyboard focus look right. A click beside a narrow image selects and focuses it, and thumbnails still drag to boards and the canvas.
  • The Queue, Workflow and Image Map cogs open quick settings.
  • Topbar → Open project: the list has dividers, an incompatible project is dimmed with its badge, and arrow keys and Enter work.

Screenshots aren't attached yet; I'll add them in a follow-up edit.

Review

Material findings resolved:

  • A gallery mouse drag stopped starting: a middle-click handler overrode the drag sensor's mousedown. Fixed, with a test that uses the production mouse sensor.
  • A click beside an image didn't move focus. Workflow progress tiles took Generate's ratio. Extreme ratios collapsed into slivers. All fixed.
  • Menu arrows had an invisible box that swallowed clicks and shifted context menus sideways. Popover arrows sat 1px off-centre. Keyboard-opened layer menus showed an arrow. All fixed.
  • The queue tab focus ring was clipped, and a programmatic tab change could leave the selected tab scrolled away. Both fixed.
  • Preview delete with confirmation dropped focus to <body> when opened from a filmstrip thumb or the header ⋮. Fixed through returnFocus.
  • Open project: focus could land on <body> after a retry, the focus ring on disabled rows was dimmed, and isDisabled rows weren't fully inert. All fixed.
  • The editor startup request count rose by 3. Fixed by keeping the gallery layout context in the eager provider and lazy-mounting the Open project dialog. Only owner-set entries were edited in the baselines.

Remaining limits:

  • A truncated inside label has no tooltip.
  • The Gallery grid still drops focus to the page after a delete (unchanged from before).
  • webv2 has no typed i18n, so template-literal keys can still miss without a compile error. The queue-status chip now uses literal keys.

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)

Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

Field gains labelPlacement="inside"; Select, Combobox and ModelSelect render the label at the start of their box.
Generate and canvas panels use inside labels for their dropdowns.
Quick settings show selects as a labelled row with a value-width select.
Queue offers its job scope, Workflow its snap, minimap and connection style, and Image Map its cluster options.
The board list marks the destination board with the Saving to icon; the docs describe it.
The boards toggle icon follows the gallery body's measured layout instead of its region.
Picker board labels keep the list's text size, and the search field gets space below it.
SegmentTabs gains isScrollable: wheel scrolling, edge fades and the selected tab kept in view.
Scroll-edge measurement moves from the shell's overflow edges to a shared platform module.
The Queue widget's filter strip and lists share the gallery rail's inset.
Menus, popovers and hover cards draw an arrow kept clear of the content's rounded corners.
Context menus anchored to the pointer and submenus show none; the layers keyboard menu anchors to a point.
The menu renders outside the item branches, so its leftover target no longer reopens it on the next item.
Focus returns to the opener when it survives the deletion, otherwise to Preview, via a new returnFocus prop.
The dialog lists projects with the virtualized List, dims incompatible ones and recovers from load errors.
List gains defaultFocusKey and ListItem isDisabled; the project switcher lazy-loads the dialog.
The chip resolves one literal translation key per state instead of a missing interpolated key.
Adds the loadingModel and loadingModelUnnamed strings, the latter for loads without a model name.
In Fit mode the tile border, selection and focus ring wrap the image's own rect inside the square cell; hover tints the cell and badges stay on its corners.
Generate, canvas, upscale and video progress tiles use the job's target ratio; ratios clamp to 1:3–3:1.
Clicks beside the image select and focus the tile, and mouse drags still start from anywhere in the cell.
@github-actions github-actions Bot added frontend PRs that change frontend files docs PRs that change docs labels Nov 3, 2026

@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.

Reviewed at 7a09ad9. The gallery, performance baselines, Open project dialog, quick-settings cogs, queue-status strings and the converted fields all held up. I found four low-severity issues, three of them confirmed in the browser, plus one focus issue I could not reproduce.

Confirmed in the browser

1. Hint cards on inside-label dropdowns open the dropdown.
FieldInsideLabel (src/platform/ui/Field.tsx:105) renders the FeatureHint inside the Select/ModelSelect trigger <button>. The hint card is portalled, but React synthetic events still bubble through the portal to the trigger. Clicking "Don't show me these" or "Learn more" in the card also opens the dropdown. Pressing Enter on them is caught by the trigger's keydown handler, so the dropdown opens and the link or button does nothing. This affects VAE, VAE precision, PID mode, encoder quantization, DyPE preset and Scheduler. With the outside label the bubble stopped at a plain <label>, so this is new.
Suggested fix: stop click/keydown/pointerdown from the hint card's content reaching the trigger, and add a test that interacts with the card.

2. Clicking a popover or menu arrow tip dismisses it.
arrowClearOfCorners (src/platform/ui/theme/recipes.ts:51) sets pointerEvents: 'none' on the arrow box. The hover-card arrowTip restores auto (:133); the popover (:159) and menu (:648) arrowTip recipes don't, so the tip inherits none. A click on the visible tip falls through, Zag treats it as an outside interaction, and the popover closes (or toggles, if it lands on the trigger). Before this PR the tip was part of the content's hit area.
Suggested fix: pointerEvents: 'auto' on the popover and menu arrowTip; the clip-path already limits the hit area to the visible half.
Separately, the tip is very hard to see in the dark theme: its fill matches the popover background and only a 1px outline shows.

3. Clicking the Scheduler's inside label while its list is open closes and reopens it, and the typed filter is lost.
The inside <label> (src/platform/ui/Combobox.tsx:231) sits inside Combobox.Control, but Zag's dismiss layer only excludes the input, trigger and clear trigger. Pointerdown on the label counts as outside: handleOpenChange(false) clears the query, then the label activates the input and openOnClick reopens an unfiltered list. To reproduce, open Scheduler, type dpm, click the word "Scheduler". The existing test only clicks the label while the list is closed.
Suggested fix: preventDefault the label's pointerdown while open, or add the label to the dismiss layer's exclusions.

From code reading, not verified

4. Hovering an invalid Combobox with an inside label tints the input part twice.
INSIDE_INPUT_CSS sets bg: 'transparent' on the input, but that doesn't cancel the recipe's nested _hover._invalid background from formControlInteraction (combobox.input in recipes.ts). The Control also gets bg.error/60 on hover, so the input region shows two stacked tints and the label region one, with a seam between them. I couldn't produce an error state on Scheduler to check it on screen.

Could not reproduce, may depend on environment

Preview: deleting without confirmation from a filmstrip thumbnail or the header ⋮ may drop focus to <body>.
closeContextMenu (src/workbench/widgets/preview/PreviewWidgetView.tsx:500) resolves the return target through getContextMenuReturnFocus (:485) synchronously when the menu closes. With confirmation off, deleteItems applies the optimistic removal in the same click, but React hasn't committed yet, so opener.isConnected is still true and focus goes to the thumb or ⋮. The commit then removes that element. I tested both paths on Linux (Cinnamon, click-to-focus) and focus stayed in Preview, so something in the real ordering saves it there. Because the code relies on timing rather than resolving the target after the commit, it may still fail in other browsers or under different load. The tests only cover the thumb and ⋮ paths with confirmation on; a {asks: false} variant of those would settle it.

…card

Hint cards stop pressed, clicked and keyed events from reaching the field that holds their label.
An open Combobox no longer dismisses, and loses its filter, when its inside label is pressed.
The visible tip takes the pointer; a menu ignores outside presses on its own arrow and keeps focus.
…tion

The menu's return target is resolved after the removal commits, and a removed filmstrip thumb hands focus to Preview.
@joshistoast
joshistoast requested a review from lstein November 5, 2026 00:20

@Pfannkuchensack Pfannkuchensack left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed at 31bafcd, with lstein's review at 7a09ad9 as the starting point. No blockers. Findings come from reading the code and tests; nothing was run, so "fixed" below rests on the code and the new tests as written.

Status of lstein's points

  1. Hint card reaching the trigger: fixed. CARD_EVENT_BOUNDARY in FeatureHint.tsx, with pointer and Enter tests for both dropdown types.
  2. Arrow tip: the click is fixed (pointerEvents: 'auto' in recipes.ts:73, plus the Menu.tsx guard). The dark-theme visibility is unchanged: recipes.ts has no colour or contrast change since 7a09ad9.
  3. Combobox inside label while open: fixed in Combobox.tsx handlePointerDownOutside. The test asserts the list, filter, aria-expanded and focus survive.
  4. Double tint on invalid hover: fixed through INSIDE_INPUT_CSS, with a computed-style test.
  5. Preview delete without confirmation: fixed for the tested paths, with {asks: true/false} variants for stage, thumb and header ⋮. Those tests delete synchronously. Removal is optimistic in useImageActions, so the real path should match, but that is not confirmed.

Low

  • PreviewWidgetView.tsx:486-534: a later removal of the header ⋮ has no focus handoff of its own, while thumbs have releaseFocusOnRemoval. Only a removal landing in the same click is covered.
  • PreviewWidgetView.tsx, closeContextMenu (about :521): if onClose is ever reached from an effect, flushSync logs and does not flush. The possible route is the useRegisterWidgetOverlay mount effect calling setOpen(false). Not verified; checking whether closeWidgetOverlays can fire while this menu is open would settle it.
  • CARD_EVENT_BOUNDARY stops onKeyDown, onFocus and onBlur for all React ancestors of every hint card. It is documented and Zag's capture listeners are unaffected, so this is acceptable. It is worth keeping in mind for any Escape or focus handler above a hint label.

Sound: the lazy Open project dialog, the baseline edits (they match the lazy split), the Select and ModelSelect inside-label wiring, the new en.json keys, and the accessibility script locator. Gallery tiles, SegmentTabs and the queue changes are unchanged since lstein's review, and I did not review them in depth.

Thumbs, the header ⋮ and the video return focus to Preview when removed while focused, so closing the context menu no longer flushes React.
…us regions

Moves releaseFocusOnRemoval into the eagerly loaded focusRegions module instead of a new startup source.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs PRs that change docs frontend PRs that change frontend files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants