LibreChat/client
Danny Avila 8f712259ea
💬 refactor: Anchor In-Flight Steers Above the Composer (#14308)
* 💬 refactor: Anchor In-Flight Steers Above the Composer

Mid-run steers were rendered in-thread at the tail of the streaming
assistant message, at a guessed injection point, then swapped to the
persisted STEER part at its real index once the server applied them.

In-flight steers now render as message bubbles anchored above the
composer, so the thread only ever shows what the server committed:

- InFlightSteers: sending/pending steers as left-aligned bubbles with
  image previews and a cancel affordance, anchored above the composer box
- PendingSteerChips: unchanged, still owns the failed/queued control rows
- SteerPart: drops the pending/onCancel props, now only ever the
  server-applied part
- useSteerCancel: the optimistic cancel + restore-on-error, lifted out of
  the deleted PendingSteers slot

The steer state machine is untouched: the 202 ACK reconciliation,
reconnect reseeding, and queue conversion all key off status, not render
location.

* 🎨 fix: Match In-Flight Steer Presentation to the Applied Part

Codex review on 6a5f36f7ef. All three findings were real, and all three
were the same underlying mistake: the anchored bubble hand-rolled
presentation instead of reusing the leaves the applied SteerPart uses,
so a steer visibly changed on apply.

- Images: the message `Image` sets an inline height from the file's
  dimensions and centers with object-contain, so clipping it into a 56px
  wrapper showed the blank top of a large element. Use ImagePreview, the
  composer's fixed-size thumbnail path (also gives click-to-enlarge).
- Non-image files: FileContainer always renders a button, so without an
  onClick the chip was dead. Wire FilePreviewDialog, as SteerPart does.
- Markdown: honor enableUserMsgMarkdown so text does not reflow the
  moment the server injects it.

Splits files in a single pass rather than two filters.

* 🎨 style: Outline the In-Flight Steer Bubble and Move the Bolt Inline

The filled bubble read as a settled message. An outline reads as
provisional, which is what an in-flight steer is, and separates it from
the composer surface behind it.

- Border + bubble keeps the composer's rounded-3xl radius so it reads as
  anchored to the input rather than floating over it. Border stays
  NEUTRAL: the failed-steer row already owns a colored (red) border, so
  a colored outline on the happy path would read as a warning.
- The Zap moves inside the bubble, left of the text, where it prefixes
  the words as a status label instead of competing with cancel for the
  right edge. items-start pins it to the first line when text wraps.
- Cancel drops plain `opacity-0` for `[@media(hover:hover)]:opacity-0`,
  matching SteerPart's info affordance: a hover-revealed control is
  unreachable on touch until a first tap (the #14272 pattern).
2026-07-16 10:27:59 -04:00
..
public
scripts
src 💬 refactor: Anchor In-Flight Steers Above the Composer (#14308) 2026-07-16 10:27:59 -04:00
sw
test
babel.config.cjs
check_updates.sh
index.html
jest.config.cjs
jest.resolver.cjs
nginx.conf
package.json
postcss.config.cjs
tailwind.config.cjs
tsconfig.json
vite.config.ts