Skip to content

feat(web): polish workbench UX and add two-sided status widgets - #207

Open
joshistoast wants to merge 61 commits into
mainfrom
feat/tweaks-fixes-compendium-02
Open

feat(web): polish workbench UX and add two-sided status widgets#207
joshistoast wants to merge 61 commits into
mainfrom
feat/tweaks-fixes-compendium-02

Conversation

@joshistoast

@joshistoast joshistoast commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

This is a workbench polish compendium: a broad pass over layout, navigation, control feedback, and visual consistency.

Highlights

Workbench + status bar

  • Add two-sided bottom-widget placement, sorting, drop zones, persistence, and layout-preset support.
  • Put notifications on the right by default and open notification content in a popover.
  • Keep the Video widget scoped to the Video preset.
  • Remove the redundant version widget; version and system details now live in Settings.

Faster daily actions

  • Turn generation presets into a managed panel, with the active preset visible on the closed trigger.
  • Combine gallery sorting and filtering, hide boards from other projects by default, and visually separate Starred.
  • Give the Resume card the full project actions menu and use one shared menu host across project cards.
  • Link Model Manager shortcuts to the relevant model view.
  • Let Shift-click on the top-bar cancel action cancel the full queue.

Clearer, more consistent UI

  • Share popover chrome, input shells, segmented controls, dialog styling, and destructive menu treatment.
  • Improve hover, active, drag, tooltip, tab, search, and row states across the app.
  • Fix scrollbar-axis behavior and the phantom-scrollbar recovery path.
  • Respect reduced motion in animated controls and use a subtler skeleton shine.
  • Square up preview frames, preset rows, and aspect-ratio controls where their geometry should match.

Themes + settings

  • Darken the default slate palette, replace Forest with Osaka Jade, and cool down Ultra Dark.
  • Preserve old Forest preferences by resolving them to Osaka Jade.
  • Add an About settings section with server version, community links, and copyable system information.
  • Enable prompt syntax highlighting by default and remove the inert progress-details setting.

Suggested review path

  1. Drag bottom widgets within the status bar and between its left/right clusters.
  2. Open Generate presets and confirm the selected preset stays visible on the trigger.
  3. Check gallery board visibility, Starred separation, and the combined sort/filter menu.
  4. Open project actions from cards and the Resume card.
  5. Compare themes, then inspect Settings → About.
  6. Spot-check hover, tooltip, dialog, drop, scrollbar, and reduced-motion behavior.

Merge Plan

Frontend-only. Persisted layouts and the renamed Forest theme are normalized forward. No special merge handling is needed.

Pro-app convention: pointer stays reserved for links. Overrides Chakra's
button/switch cursor tokens and sweeps the per-component pointer props,
keeping functional cursors (grab, resize, crosshair, text, not-allowed).
Zag slides the indicator via an inline var(--transition-duration, 150ms)
fallback the duration-token collapse never reaches; pointing the var at the
motion-aware fast token collapses it to 1ms with reduce motion on.
Makes Chakra's shine variant the default, resting on the same bg.emphasized
surface with a small fg lift as the band. Reduce motion falls back to the
flat fill: a gradient frozen mid-sweep reads as a smudge.
One platform primitive bakes in the arrow and the popover slot-recipe chrome
(tooltip-family surface, stroke, shadow), so consumers stay consistent by
construction. Dropdown-shaped popovers opt out via showArrow.
The rounded corners clipped generated pixels and read as decoration on what
is a verbatim view of the output; the border and shadow carry the framing.
The setting was rendered permanently disabled and nothing read it. Removing
it from the project-settings contract also drops the now-unused comingSoon
plumbing from the settings controls; normalization silently sheds the key
from older persisted projects and .invk imports.
Promotes the experimental toggle to a default-on preference and drops the
experimental labeling. Stored preferences keep their explicit value, so an
account that turned it off stays off.
A new bottomPanel: 'popover' manifest mode makes the compact chip a real
Popover.Trigger opening a large dismissable panel over the status bar,
VSCode-style, instead of claiming the shared bottom panel. The popover
renders the widget bare, so the view mounts its own header with the
mark-read and clear actions.
Two adjacent unlabelled icon buttons read as noise you had to hover to tell
apart; one options menu now carries the visibility toggles and both sort
radio groups, staying open so several can be set in one visit. The trigger
highlight compares against the defaults instead of hardcoding polarity.
The board panel opens scoped to the project at hand; boards owned by other
projects stay one toggle away in the options menu, and the open project's
own board is always shown regardless.
The starred-gap row grows to hold a hairline separator while the section is
expanded; collapsed, the header already separates the sections and the gap
stays at the plain grid gutter.
Two halves: the topbar switcher renames through the project library, whose
open-project path flushes immediately so the server renames the board in the
same transaction; and the gallery shows the open project's board under the
live project name, since the fetched board list can lag the rename.
While shift is held over the hovered button the icon, tooltip and aria-label
swap to the cancel-all action, so the click never does more than the button
shows. The keyboard is watched only while hovered, and the gesture state
resets if the button unmounts mid-hover.
openManager now takes the missing model's type and seeds the Add Models
starter catalog with it through the same one-shot dynamic-import seam the
search seed uses, so prompt expansion lands on text LLMs, image-to-prompt on
vision models, and the empty model card on main models.
The Select primitive renders valueText verbatim in the trigger, so the
static None string masked the active selection; the field now passes the
selected prompt's name and keeps None for the empty catalog.
The expand-prompt and image-to-prompt tooltips no longer switch to a
missing-model warning; the popovers already explain what is missing and
offer the way out.
The rows sit on the popover's bg.muted surface, where the row recipe's
bg.muted/60 hover is invisible; they hover one surface step up instead.
Hover and active now share bg.emphasized in the side rails and status bar;
the open widget stays distinguished by the brand hue in its content.
The gallery item search and its semantic-query chip hand-built their frames
(opaque bg, emphasized border, md radius) and drifted from the themed
inputs; InputShell carries the input recipe's chrome keyed on focus-within,
with start/end slots, so composite fields match <Input size="xs"> by
construction.
Inline drop zones preview the drop treatment with an accent border on
pointer hover (standing down while disabled), and the drag-over accent now
wins over consumer hover fills. Board rows stop flashing their hover over
the drop tint: the shell's hover stands down while a compatible drag is in
flight, and the hovered destination gets a solid accent ring and fill.
The app version already shows in the app menu, the launchpad help menu, and
the splash screen; a status-bar chip restating it earned no place. Stale
placed instances in persisted layouts degrade silently: the region view
model drops instances with no registered widget.
Non-button interactive rows (picker options, menu items) computed cursor
auto after the pointer sweep, showing the I-beam over their labels; a
base-layer rule pins interactive roles to the arrow. Typeable comboboxes
are deliberately excluded — their inputs keep the I-beam.
Chakra's subtle-fill hover collides with muted and control surfaces; an
fg-derived 10% fill reads on every surface and keeps the palette tint.
Plain buttons are link-shaped actions and now underline on hover — stock
Chakra gives them no hover state at all.
The subtle variant's bg.muted hover disappears against the topbar preset
strip; one surface step up reads everywhere.
One platform component carries the house segments (xs group, 2xs centered
labels, the axe-driven checked-item fill) and replaces hand-rolled
SegmentGroup markup in the canvas tool rows, graph preview, gallery view
tabs, and dynamic prompts.
36 icons across four captioned category blocks read as a form; one grid
scans faster and the keyboard order already walked the flat list.
The preset library graduates from a bare menu to the wildcards treatment:
a popover panel with save, search past five entries, apply-on-click rows,
rename (wiring the port that never had a UI), and delete behind a confirm.
Empty and no-match states say what the list would hold.
Pixel-equal hover colors and transition tuples churn with every theme
tweak without guarding behavior. Structural, focus, and contrast
assertions stay; the orphaned row-interaction style helpers go with them.
The UI batches added two platform primitives to the initial source graph
and ~3KB of editor-owned chrome; captured through the pipeline's own
update flow and verified green.
The wildcards tabs, graph preview modes, and gallery view tabs now wear the
layer-pane look — borderless track, neutral emphasized fill, xs/600 pills —
instead of the accent-bar track, which stays the canvas default.
A new palette hoverTint slot backs the ghost/outline/plain hovers and the
SegmentTabs fills: the default gray resolves to fg pulled 40% toward the
accent so translucent fills sit in the menus' cool-tinted family, while
red/brand/accent buttons keep their own tint. The contrast guard now
composites the translucent shown fill before measuring.
Buttons and the segment-tab strips read as different families: buttons
sat one size notch larger with squarer corners than the tab pills.

- Add a shared radii.control token (5px) and put buttons, tab pills,
  segmented controls, and input chrome on it.
- Shrink button sizes one notch (xs 28px, sm 32px, md 36px) so xs lands
  on the tab-pill height; sm/md keep the xs text style.
- Re-pair segmentGroup item heights with the new button scale.
- Pair the iterations field to the 28px capsule and retune the routing
  control's diagonal icon insets for the shorter box.
…motion

- Shift input/numberInput/select/combobox xs/sm/md heights one notch down
  so same-named sizes share a row height with buttons; fix the stale 32px
  pairings (InputShell, gallery search inner input, SAM prompt input,
  canvas number-field affix offsets).
- Speed button hover fills up to the faster duration and give segment-tab
  pills a motion-aware background/color transition.
- Round menu/select/combobox items at l2.
- Bump the topbar controls one size so they keep their 32px box, and put
  the layout-preset tabs on the shared control corner with the
  hover-tint fills (nav sidebars keep the stock selected accent).
…r treatment

The red delete tint lived in five copies of the same hover const and a
handful of items that tinted the label but forgot the hover fill. The
dropdown item recipe now styles `[data-danger]` (fg.error text, bg.error
hover/highlight) for menu, select, and combobox items alike, and every
delete/uninstall/clear item — direct or through the shared item
components' danger tone — opts in with the one attribute.
The card filled itself with the raw cluster color and flipped its text
dark/light to survive it. It now sits on the app's dropdown surface
(muted bg, emphasized border, md corner) with the cluster's palette
color confined to a swatch dot beside the cluster line.
Rows sized themselves from padding while their trailing actions were
24px squares; both now sit on the shared 28px control height and corner.
- Focus the gallery search input from anywhere on its chrome and balance
  the trailing help button's inset against the leading glyph.
- Open the project switcher on right-click too.
- Speed tooltip open/close animation up one step.
- Mark the applied generate preset and prompt template with a check and
  label weight instead of filling the row with accent.
- Trim sm button horizontal padding.
- Give the prompt-trigger popover the same empty-state anatomy as the
  image-to-prompt popover, and let its trigger show the expanded tint
  instead of disabling itself while open.
…tarred rule centering

- Pin the empty generate-presets trigger to an explicit 24px square.
- Hairline separators between preset rows.
- The active layout preset's icon takes the brand color.
- Center the starred-gallery rule between thumbnail edges; the row above
  bakes its trailing grid gap into its height, which sat the rule 2px low.
…ing stripped

Dialogs ran on Chakra's stock marketing-modal chrome — 24px gutters, lg
titles, top placement — with each file patching its own corner of it:
two competing surface colors, hand-copied borders, three cancel-button
variants, close buttons sized at every call site, and local body-padding
compensations.

The dialog recipe now owns the look: one bg.subtle surface, xs/700
titles, an fg.subtle xs description slot, 16px gutters, footer gap 2,
centered placement, and a tucked-in close trigger; the platform
CloseButton defaults to small and muted. Dialog files now carry
structure only — redundant placement/gap/size/color props are gone,
dismiss buttons all sit on ghost, header subtitles ride the
aria-wired Dialog.Description, and the crop dialog gets the close
button it was missing.
…ons menu on the resume card

- Project cards, list rows, intent tiles, and the resume card keep the
  arrow cursor over their overlay links, per the pro-app convention the
  theme's cursor tokens set for controls.
- The resume card's cover sits inset from the card with its own radius,
  vertically centered against a taller text column.
- The resume card is a library project like any other, so it now carries
  the same actions menu as the grid cards: right-click anchors it at the
  cursor and a 3-dot trigger rides next to the Resume button.
- The view-all-projects button steps up to xs.
…g per-card menus

Right-clicking one project while another's context menu was open flashed
the new menu and immediately closed it: zag's dismissable stack treats
any layer mounted above another as nested, and a controlled menu closes
a beat after the pointerdown, so the second card's menu mounted above
the first's still-registered layer and was dismissed along with it.

One ProjectActionsMenuHost now serves every card. Opens route through
its context and remount the single hosted menu (keyed per open), so the
old layer's cleanup always precedes the new mount and the menu simply
moves between cards. The rename/delete dialogs ride beside the menu —
an item click closes the menu, which would have unmounted dialogs
living inside it. Cards keep plain dots buttons wired through a trigger
hook that restores toggle-to-close (the pointerdown already dismissed
the menu; the click must not reopen it) and hands focus back to the
originating control when the menu closes, since no zag trigger is
registered to restore it to.

A browser regression test pins the cross-card handoff, aria-expanded
transfer, same-card re-right-click, and the dots toggle.
… weight, palette placement

- Scrollable heals zag's phantom thumb: the initial measure races a
  popover's zero-size mount (observers can end up watching pre-remount
  nodes) and the "has overflow" default strands a small unscrollable
  thumb on non-overflowing content. Live-node checks detect the
  contradiction and route one synthetic scroll through zag's own
  re-measure path; a popover regression test pins it. Two listeners the
  heal exposed are hardened on their own merits: scrolling inside a
  popover's own list no longer dismisses the rect-anchored surface, and
  the workflow-library grid only paginates when it actually overflows.
- The dynamic-prompts preview disables its row when there is exactly one
  expansion — pulling it in would paste back what the prompt box already
  says — while keeping the preview ink at full strength.
- Segment-tab pills use the button font weight.
- The gallery sort trigger gets a "Sort by" tooltip via the shared-ids
  pairing the routing control established.
- The command palette pins placement="top" again; the dialog recipe's
  new centered default had silently pulled it to the middle.
…phantom heal

Chakra's stock scroll-area recipe hides a scrollbar only when NEITHER
axis overflows, so a vertical-only bar stayed pinned — thumb clamped to
zag's minimum size — whenever content merely spilled sideways (nowrap
dynamic-prompt rows, wide JSON). The theme's scrollArea override scopes
the guard per orientation: a bar shows only when its own axis overflows.

The stale-measure heal moves from Scrollable into a shared
useScrollAreaPhantomHeal hook so the raw dual-bar scroll areas
(JsonPreview, the widget failure boundary) are covered too — JsonPreview
mounts inside freshly-opened dialogs, exactly the racy context.

Tests poll for zag's measure instead of guessing delays, and now pin
both directions: a bar hides when only the other axis spills, and stays
shown when its own axis genuinely overflows.
… Ultra Dark

- The default ramp trends ~2.5% L darker at the deep end with an even
  slate chroma curve instead of the old spike at 400; off-ramp seeds and
  contrast anchors re-derived. Contrast improves at every measured pair.
- Forest becomes Osaka Jade: the neutrals move to blue-green stone
  (hue ~180) with a jade brand and aqua accent. Preferences stored under
  the old id migrate through resolveWorkbenchThemeId's legacy map, which
  re-resolves on every ingest so a server echo of 'forest' cannot fight
  the client.
- Ultra Dark keeps its pure-black floor but drops the green-lime cast
  from its light steps for a faint cool slate, leaving brand lime and
  the sky accent as the only color.
- The token-contract baseline is regenerated through the same resolver
  the gate reads — a deliberate retheme, not drift.
The legacy About modal's content as a settings tab: the server version,
GitHub/Discord links (on a proper GitHub brand mark, which the help menu
adopts too), and the copyable system-information blob — installed
dependency versions plus the redacted runtime config for admins —
backed by an account-scoped store over /app/version and /app/app_deps.
The command palette lists the new section, and the Image Map section it
had been missing.
The status bar packed every widget to the left. Each bottom chip's
context menu now offers moving it to the strip's trailing cluster and
back. Placement and drag-reorder stay on the region's single order
model — alignment is a render split persisted as an optional id list on
the region state, carried by preset snapshots, counted by layout drift,
and tolerant of ids that are no longer placed (a re-enabled widget
keeps its side). Old snapshots without the field still validate.
…froze

dnd-kit's useSortable pins an inline style.transition for its transform
animation, which replaces the recipe's CSS transition outright — preset
tabs, bottom chips, and the preset manager rows all had frozen
hover/selected fills. Each sortable now joins the motion-aware fill
fade back into the inline value beside dnd's transform transition.
The context-menu toggle was the only way across; the strip's trailing
spacer is now a real drop target (join the end cluster at the end), and
landing beside a widget adopts that widget's cluster — same-region and
cross-region alike, with stale end memberships normalized on entry.
Collision detection prefers a direct pointer hit on the spacer over the
region droppable that contains it, which otherwise shadowed the gesture
into the nearest-chip fallback; a collision-level test pins the spacer
geometry alongside the resolution tests.
Compose and Edit no longer place the Video widget; it stays addable
everywhere and the Video preset keeps it. The pre-Video backfill
migration is retired with it — its "untouched default" fingerprint now
matched the current default again, so it re-added Video to every saved
preset on load and made them drift instantly. Old snapshots that
genuinely contain Video still get their instance created; tests that
exercise Video place it explicitly first.
… drop chrome

The strip-wide drop curtain hid the right-side target behind itself —
finding it meant peering through a full-width overlay. During a widget
drag the status bar now shows one rail-style drop zone per cluster,
sized to its cluster, with the trailing zone carrying a to-line-end
glyph and a minimum target even while empty.

The chrome is deliberately not a new look: both zones render the same
WidgetRegionDropOverlay the rails use (now accepting inset overrides so
it cannot fork), gated on the shell's dropState so widgets the region
rejects see the disallowed treatment instead of an inviting slot, and
highlighting only over a zone's own background — over chips it previews
a reorder, exactly like the rails. The shared DropZone's drag-over fill
drops from the opaque accent.muted to a translucent accent tint so the
content underneath stays legible everywhere it is used.

Notifications ride the trailing cluster in every built-in preset.

The end-to-end browser test drives the real pipeline with real browser
input; its gesture deliberately runs outside act(), which starves
dnd-kit's effect-driven callbacks into exactly the silent failure the
test exists to catch.
…pendium-02

# Conflicts:
#	invokeai/frontend/webv2/performance/architecture-baseline.json
#	invokeai/frontend/webv2/performance/browser-baseline.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant