LibreChat/packages/client
Danny Avila 9ac801ee45
feat: Delivery Receipt Checkmarks for Steered Messages (#15376)
*  feat: Delivery Receipt Checkmarks for Steered Messages

Users regularly read steering and interrupt as broken because nothing
visibly reacts: the in-flight bubble's only applied signal is its own
disappearance, an armed interrupt is a silent icon swap, and the inline
part's explanation hides behind a hover-only "?".

Every phase now shows an explicit, immediate receipt driven by events
the protocol already emits (no server changes):

- Shared SteerReceipt (Chat/Steering/Receipt.tsx): Sending… →
  Delivered ✓ (202 ACK) → Interrupting (amber ✓ + pulsing dot on
  preempt arm) → Applied ✓✓ on the inline part, with hover cards
  explaining each state (the info text doubles as the accessible name).
- The applied ✓✓ replaces SteerPart's hover-gated "?" in the same
  32px meta row and stays visible on reload, share, and search views.
- A one-shot draw-in plays only at the live chip→inline hand-off:
  resolveSteerChip stamps liveAppliedSteerIds in the same batch as the
  chip removal and SteerPart consumes the id on mount.
- An unacknowledged interrupt shows the amber label and pulse without a
  check: a check only ever asserts what the server confirmed. Failed and
  delivery-uncertain steers keep their recovery rows, checkless.
- Queued rows gain one caption for the group ("Sends when this
  response finishes"), shown only while the run is active.

* 🤝 fix: Address Codex Round 1 on Steer Receipts

- Extend InfoHoverCard with an optional custom trigger (children) and
  compose the receipt through it instead of duplicating the hover-card
  state, portal, and focus handling in the feature.
- Replace the feature-specific style.css keyframe with the existing
  tailwindcss-animate utilities (animate-in fade-in-0 zoom-in-50
  duration-theme-normal, motion-reduce:animate-none), matching the
  house pattern in Thinking.tsx.
- Scope the live-applied subscription per steer id via a membership
  selectorFamily so stamping/consuming one receipt no longer re-renders
  every mounted historical SteerPart.
- Gate the live stamp on a genuinely first application: a replayed
  on_steer_applied whose ids are already in the durable applied set no
  longer re-arms an animation its part already consumed.
- Track a row-local arming flag so escalating a delivered steer flips
  its receipt to the unconfirmed Interrupting state on the click, with
  the check appearing only on the confirmed durable arm (and reverting
  to Delivered on a lost race).

* 🤝 fix: Address Codex Round 2 on Steer Receipts

- Stamp the live-applied id only when the inline part is actually
  committed: resolveSteerChip now returns the first-application ids and
  applySteerToMessages threads them through its bounded retries,
  stamping in the same batch as the setMessages commit. An abandoned
  placement or a replayed (referentially stable) event can no longer
  strand an id that would animate a historical part on a later visit.
- Move the queued caption outside the role=list container so the list
  owns only listitem rows, with a structure test.

* 🤝 fix: Stamp Only the Rendered Steer Id as Live-Applied

The applied event settles both the server id and its client alias, but
the inline part renders (and therefore consumes) only part.steerId, so
stamping both left the alias resident in the live set as dead weight.
Stamp exactly the rendered id.

* 🤝 fix: Address Codex Round 4 on Steer Receipts

- Capture the live draw-in per steer IDENTITY, not per mount: a content
  slot overwritten with a different steer (applySteerPart permits index
  replacement) re-derives its flag and every identity consumes its id,
  so nothing lingers to animate a historical part later.
- Treat a relabelled chip (preempt past sending) as confirmation even
  while this row's arm request is pending: the SSE steer_updated can
  deliver the durable arm before the HTTP response, and the check must
  not wait out the round-trip timeout.
- Announce receipt transitions (Sending to Delivered, Interrupting)
  through the row's existing polite live region; mount state is not
  replayed.
2026-08-30 18:33:39 -04:00
..
src feat: Delivery Receipt Checkmarks for Steered Messages (#15376) 2026-08-30 18:33:39 -04:00
babel.config.js 🪟 feat: DataTable update + Various UI enhancements (#9698) 2025-12-11 16:39:06 -05:00
jest.config.js ⬆️ chore: Migrate off deprecated @ariakit/react-core to @ariakit/react-components (#13940) 2026-06-24 23:13:57 -04:00
jest.resolver.cjs ⬆️ chore: Migrate off deprecated @ariakit/react-core to @ariakit/react-components (#13940) 2026-06-24 23:13:57 -04:00
jest.setup.ts 🪟 feat: DataTable update + Various UI enhancements (#9698) 2025-12-11 16:39:06 -05:00
package.json ⏱️ feat: Shared Time Picker for Schedule Times (#15122) 2026-08-23 22:47:33 +00:00
tailwind.config.js 🎨 feat: Add Versioned Theme Foundation (#14709) 2026-08-10 13:41:03 -04:00
tailwind.preset.cjs 🎨 feat: Add Versioned Theme Foundation (#14709) 2026-08-10 13:41:03 -04:00
tsconfig.json refactor: Migrate @librechat/client build to tsdown (#13596) 2026-06-08 11:20:03 -04:00
tsconfig.test.json 🪟 feat: DataTable update + Various UI enhancements (#9698) 2025-12-11 16:39:06 -05:00
tsdown.config.mjs 🖼️ refactor: Consolidate Provider Icons Into a Single Registry (#15148) 2026-08-24 13:00:38 -04:00