Repository navigation
feat(ui): Tons of workbench refinements and tweaks - #9656
Conversation
Extend the scrubber fill past its thumb with a rounded end and give the splash screen the preview dot grid. Wrap long model names in toasts, render the launchpad connection state as a Badge, and use the shared Spinner for unknown Invoke progress.
Keep the control corner radius on Invoke while its neighbours stay attached; Group styling is unchanged.
The iterations field and routing button hide their edge facing Invoke and extend their frame under its corners, so Invoke reads as sitting on top of the cluster; the routing tail carries hover and active fills.
Match the app menu's dropdown affordance instead of the up/down arrows.
The Krea-2 seed variance toggle sits inline beside its label instead of wrapping onto its own row.
The placeholder already says "Bundled VAE", so the help line beneath the field is removed.
ConceptRow and ConceptList replace three LoRA row implementations with thumbnails, a box fallback, a weight scrubber with copy/reset, and dividers; Generate keeps its debounced weight draft.
Match Generate and Video wording for the field, placeholder, and incompatible-removal notice.
ModelSelect distinguishes an exhausted picker from an empty library and accepts a scope label, which the concept pickers set to "concepts".
The badge reads "2/3 active" when some guidance is off and keeps "3 active" when all of it is on.
The splash no longer imports the preview stage, keeping that module out of the launchpad's initial load.
Remove the shared topbar style module from the editor's initial graph; each neighbour owns its border tail.
ListItem gains a detail slot on the row surface, outside the primary button, so it may hold controls; a context menu the detail handles itself takes precedence over the row's.
ListItem gains isMenuOpen, styled like hover; the Model Manager and node pack lists pass it. Removes the static-row hover rule, which never applied because Chakra's hover style overrode it.
A translucent foreground tint pulled toward the accent, pinned in the token contract; list rows, segment tab hover, and gallery board rows share it.
List tests derive row geometry from the gap constant instead of hardcoded pixels.
Stacked and virtualized lists fade the hairlines either side of a row showing its hover fill, including rows wrapped by their caller and rows with an open context menu.
The progress fill sits 2px inside the frame with concentric corners and uses bg.hover.
useOpenModelInManager focuses the model's details and navigates to the manager, loading the manager store lazily; it returns null for sessions that may not manage models.
Concept rows use ListStack and ListItem with the weight slider as row detail; one list-owned menu offers Open in Model Manager, reset weight, enable/disable, and remove, and removal moves focus to a neighbour.
Current batch and Recent sections show row dividers and bleed into the widget padding.
Add @chakra-ui/cli and a theme:typegen script, run from prepare and before tsc, so recipe size unions follow the theme instead of Chakra's stock defaults.
Controls share one height scale (xs 20px to 3xl 44px, md 28px) and type runs xs 10px to 9xl with md 12px; Chakra's stock recipes are renamed onto these names at startup so every style keeps its pixels. Call sites, wrapper size unions, and theme overrides move to the new names, and 2xs is retired.
Rescaled recipes default to md (heading, icon, and tabs keep theirs); Code's md becomes 12px. Auth forms, unavailable screens, and recovery banners pin lg where they need their previous size.
Removes size="md" from controls whose recipe now defaults to md, and the explicit scrollbar size from ScrollArea and Scrollable.
The mocked surface host rendered a visible output inside the tree's column, so the viewport height depended on that stand-in's line height; it is hidden like the harness's other outputs.
Body sets the md font size and keeps html's proportional line height. Titles that sat above smaller explicit text pin their sizes, and the backend-unavailable screen keeps its 16px copy.
Removes 58 fontSize/textStyle="md" props measured in the running app to render identically without them; props whose text renders differently without them, or that were not rendered, stay.
Fields, skeletons, footer buttons, and property rows that copied the 28px control height now use the control.md size token, and the canvas affix offset reads CONTROL_HEIGHT_PX.
Right-clicking a second field left both menus open and the next right-click closed them all; fields now share one open-menu owner, so opening a menu closes the previous one.
…ment The retiring worker now waits for in-flight requests, then starts the replacement encoder and releases the old one. Semantic search re-checks the encoder, so a reinstall is picked up without the image map open. While a replacement drains, map reads report computing and search explains the switch.
A repeated missing-encoder status no longer re-renders the map or discards an in-flight Check again. Status polling pauses while the page is hidden and checks again once it is visible.
Read a draining encoder replacement as a polled switching state instead of a disabled index.
Adding a concept no longer commits the rendered list over edits that landed since.
Pick the control before the removed list once the removal renders, so a picker enabled by the removal takes focus.
Upscale and Video no longer save an unchanged concept list when a removed row flushes its pending weight.
Explain name-based model lookup, deletion and rename, and automatic switching to a replacement.
Hover fills lean 55% toward the accent, and bg.hover matches the 10% button hover. Menu, select, picker, palette, and list highlights and inactive top-bar hovers use bg.hover instead of opaque grey.
Shorten bundle names (SD 1.5, FLUX.1, FLUX.2, Z-Image, Krea 2, LTX 2.5) and merge the Wan 2.2 T2V and I2V bundles. FLUX.2 adds Klein 9B, the PiD 2K decoder, and FLUX.2 [dev] Q4 with their encoders.
A menu-only row skips its menu only for a mouse click; keyboard, screen reader, touch, and pen activation open it. Rows whose only controls are their actions get the pointed hover and hide their neighbouring dividers.
Hosts keep closing dialogs mounted until their exit completes via useExitRetainedValue/useExitPresence, and Confirm/Rename dialogs keep their last text. Fixes the layer group Rename and layer Run workflow dialogs that never appeared, and releases modal hotkey layers at close.
Starter, Hugging Face, and scan rows share one right-click menu per list that offers the row's next step: Install, View queue, or View model. Rows stay out of the tab order beside their Install button, and the menu animates out on close.
Grouping it with ConfirmDialog avoids an extra startup request on the Launchpad and editor. Re-record the performance baselines for the reviewed startup growth from the dialog exit-animation fix.
…e reindexing The shared cache is keyed by model key, so an encoder reinstalled under the same key was served from its retired weights and stored those embeddings under the new hash. The swap now drops the retired and replacement keys from every per-device cache under the model-load write lock.
A status poll reporting computing with no active model now clears the retired points and labels, and reloads the map once the replacement reports its model.
… close The row menu keeps its anchor and actions through the exit animation. Context menus that replace one another no longer dismiss the next menu when the previous one's layer is torn down.
lstein
left a comment
There was a problem hiding this comment.
Thanks for the thorough follow-up. I re-reviewed at 055c439 and everything from the previous round is resolved or disclosed:
- Retired typography tokens: fixed. The merge with main is re-mapped, and the new architecture check rejects
fontSize/textStyle="2xs", including object, responsive andcss={}forms. - Stale concept actions: fixed. Add, remove, toggle and weight now apply to the latest settings in Generate, Video and Upscale.
- Canvas scale default: disclosed in Compatibility / Rollout, which is what I was after.
- Image Map encoder removed mid-session: fixed on both sides. The server swaps away from the removed encoder and evicts it from the model cache. The client polls status and shows the missing-encoder prompt, or Computing while a replacement drains.
- Smaller items: the cross-menu right-click race, the inert row button and the project-switch test gap are fixed, and the default-size pixel changes are documented.
Checks at this head:
pnpm lint: clean.pnpm test: 9510/9510 passed.pnpm test:browser: 2206/2209 passed.- The two
InvocationFlowNodeexport failures also fail on main. SettingsDialogTargetfailed once under full-suite load and passed 3/3 on its own.
- The two
- Ruff check and format: clean.
- Pytest for the image index, image map router, model cache and starter models: 789 passed.
Approving. One thing I'd ask you to address, either here or in a quick follow-up:
FLUX.2 starter bundle now pulls gated, non-commercial models (91db6b8)
The "FLUX.2" bundle (starter_models/__init__.py:588-599) used to be the Apache-2.0, ungated Klein 4B set. It now also installs:
- Klein 9B, plus its Qwen3 8B encoder from the gated
black-forest-labs/FLUX.2-klein-9Brepo; - the PiD decoder;
- FLUX.2 [dev] Q4 with cow-mistral.
That comes to roughly 74 GB.
The new comment says "[dev] is non-commercial; the Klein models are not", but the repo's own flux-2.mdx:34 lists Klein 9B under the FLUX Non-Commercial License and as gated on Black Forest Labs' repositories. A user without a Hugging Face token, or who hasn't accepted the 9B license, who clicks the bundle gets a failed ~16 GB encoder install and a 9B transformer that can't run.
I'd suggest keeping the default FLUX.2 bundle to the ungated, commercially usable Klein 4B set. The 9B and [dev] models could be individually installable, or a separately labelled bundle that says it is gated and non-commercial. Either way, please correct the comment.
Non-blocking notes, for a follow-up if useful
- Encoder swap holds the activation lock across a model load.
_swap_modelholds_activation_lockwhile waiting onMODEL_LOAD_LOCK.write. During a swap, map polls, searches andstop()therefore wait out any in-flight generation model load, so the 10 sstop()bound doesn't hold. This is documented and can't deadlock. - The drain test doesn't exercise the drain.
test_encoder_replacement_waits_for_inflight_embedding_and_discards_retired_batch[*-query-*]still passes with the drain loop in_finish_retirementdeleted: the query is released before the worker's next pass. Its_model_configassertion runs on a background thread, so a failure there would only surface as a pytest warning. The request-side_model_userscheck intry_activatehas no coverage. - A brief false "not installed" during a swap.
_replacement_pendingis cleared before the new_model_idis published, so a status read in that window reportsmodel_missingfor one poll. - Unconditional status polling. The map polls
/image_map/statusevery 5 s while visible, even with a healthy active model, and each poll hashes every accessible item. Gating the poll on the missing or draining states would avoid that. - Stale hover labels outside the map. Gallery and preview hover labels are only invalidated when the map itself reloads, so they can show the old encoder's labels until the map is reopened.
- Docs still use the old bundle names and contents. The Wan, FLUX and LTX starter-bundle docs (
wan-2.2.mdx,video-workflows.mdx, the Video Generation introduction,flux-1.mdx,flux-2.mdx,z-image.mdx,stable-diffusion-1-2.mdx,ltx-2.mdx) predate the renames and the merged Wan bundle.
Summary
The workbench ran on Chakra's marketing-sized scales:
mdcontrols were 36–40px and body text was 16px. As a result, nearly every call site opted down by hand withsize="xs"/"2xs"andfontSize="2xs"/"xs". This PR rebases the scales, polishes a set of workbench controls, makes dialogs and menus animate out instead of vanishing, and tidies the Model Manager's starter bundles.Size scale
mdis now the working size: 28px controls and 12px text. One control scale runsxs–3xlin 4px steps (20–44px), and the type scale extends to9xl.chakra typegen, which makes retired recipe sizes such assize="2xs"a type error. A separate architecture check rejects retiredfontSize/textStyle="2xs"literals, which Chakra's permissive CSS types cannot catch.md; redundant size and font-size props are removed. The intentional default-size pixel changes are listed under Compatibility / Rollout.Workbench polish
snuglist density, weight scrubber, toggle, remove action, and context menu. A permitted action opens the model in Model Manager.bg.hovertoken. Queue lists gain dividers aligned with their tabs.bg.hoveras ghost and outline buttons, so an open trigger and its highlighted option match. Inactive layout presets, widget rail items, and status-bar rows hover with it too; selected fills are unchanged.useExitRetainedValue/useExitPresence), and Confirm and Rename dialogs keep their last text while closing instead of falling back to empty names or another variant. Two layer dialogs never opened at all and now do: the group Rename and a layer's Run workflow. Modal hotkey layers release at close rather than after the animation.QA Instructions
Webv2 checks used Node 22.14 and pnpm 10. Run bare pnpm commands from
invokeai/frontend/webv2; commands with-Cand Python commands run from the repository root:pnpm lint: passed (formatting, oxlint, theme typegen, TypeScript, architecture).pnpm test: 9,509 passed.pnpm test:browser: latest full run 2,208/2,208 passed. Earlier runs intermittently failed the SINGLE/COLLECTIONInvocationFlowNodeexport pixel assertions, which fail the same way on baseb05ecdc; their expectations are unchanged.pnpm test:fixtures: 28 passed.pnpm test:performance:architecture: passed after re-recording the baselines. Request counts are unchanged on every route (minimal editor startup remains 89 requests / 84 scripts); the exit-retention hook is grouped withConfirmDialogso it adds no startup request. Editor startup-owned JavaScript is 148,443 B: the dialog exit fix adds about 600 B, and the 2026-10-02 baseline predated about 4 KB of earlier growth on this branch.pnpm test:project-files:browser: passed.pnpm test:accessibility:browser: all 18 journeys passed against the fresh build.pnpm check:releasestops at the known workflow-export browser failure; the remaining release stages listed above passed separately.pnpm -C invokeai/frontend/api lint, and its 2 generator tests passed. Legacypnpm -C invokeai/frontend/webv1 lint:tscreports 16 existing Qwen 3.5 model-union errors, reproduced with identical diagnostics againstb02f548.OMP_NUM_THREADS=2 uv run --no-sync pytest tests/app/services/image_index tests/app/routers/test_image_map.py -n 4: 322 passed. Starter model and bundle tests (tests/backend/model_manager/test_starter_models*.py, router starter tests): 23 passed. Ruff 0.11.2 check and format check passed for all changed Python files.Size scale: the original Playwright geometry/typography sweep covered about 28 states against the mock backend. The rename commit was pixel-identical. Later default changes include the visible Badge, Switch, Checkbox, and ColorPicker changes documented below; the rename result does not establish that those defaults are pixel-identical.
Polish items: checked in the browser against the mock backend. Screenshots are not attached; local captures cover the concept menus, preserved weight, and missing-encoder prompt. To try the flows:
Review
Resolved findings include stale concept-menu callbacks and captured-array updates, the row/field right-click race, inert keyboard stops, static-row hover fill, missing pending-draft project-switch coverage, per-move Video/Upscale writes, and Image Map availability/label recovery after encoder deletion or replacement. A later pass fixed indexing and gallery search staying down after a replacement until the map was opened, a draining replacement reading as a missing model in the map and as disabled in the gallery, map churn from repeated missing-encoder polls, a captured list in Generate concept adds, focus dropping to the page after removing the last concept, and no-op Upscale/Video writes from removed rows. The earlier Add Models reopening and rebalance-spacing fixes remain included. Accessibility setup now selects the Uncategorized board row explicitly, avoiding the disclosure displaying the same board name.
A later round fixed dialogs that unmounted on close (and two that never opened), Confirm/Rename text changing during exit, hotkey layers held through the exit, stale forms when reopening during an exit, layer-menu exit races, keyboard-only detection that missed screen-reader and touch activation, and per-row install menus that doubled tab stops and subscriptions.
The main-branch concept conflicts are resolved, retaining unsupported-model messaging/removal and remapping the incoming retired tokens. The typography guard covers literal JSX/style/recipe values, including conditionals and responsive values. It does not evaluate variables/calls; still-valid names such as
smrequire semantic review when merging older code.Remaining risks and verification limits:
Compatibility / Rollout
promptFontSizeis optional and normalized; a missing or invalid value falls back to the default size.chakra typegenruns fromprepareand beforetsc, writing intonode_modules, so it needspnpm installto have run.@chakra-ui/cliis a new devDependency. The typography policy also runs inarchitecture:checkandlint./image_map/pointsand/image_map/statusadd an optional, nullablemodel_idfingerprint. While an installed replacement drains they reportcomputingwith nomodel_name, and/searchand/search_by_imagereturn 409 explaining the switch; search requests now also reconcile the encoder. Generated OpenAPI and TypeScript contracts are updated; older clients can ignore the additive fields. No database or project migration is needed.starter_bundlesnow returns onewankey instead ofwan_t2vandwan_i2v. Both frontends render whatever the response lists, so only scripts reading those keys are affected. The FLUX.2 bundle grows from about 11 GB to about 70 GB, and FLUX.2 [dev] is non-commercial.enonly; no other locale had them):widgets.properties.title.*.common.searchSchedulers/common.noSchedulersFound→common.searchOptions/common.noOptionsFound.settings.catalog.promptFontSizeand its option labels.Checklist
What's Newcopy (if doing a release after this PR)