From cd46d508106b6059d2a740aab38faf18f6029bce Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 30 Aug 2026 19:18:52 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=95=AF=EF=B8=8F=20style:=20Settle=20the?= =?UTF-8?q?=20Applied=20Steer=20Receipt=20Once=20the=20Response=20Is=20Don?= =?UTF-8?q?e=20(#15377)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The applied checkmarks kept the amber steering identity forever, so a conversation full of historical steers stayed lit long after anyone cared whether they landed. The receipt now keeps amber only while the owning response is still generating (isSubmitting, the window where it is the live thing at the end) and settles to the timestamp gray with a soft color transition. Reload, share, and search render settled. --- .../components/Chat/Messages/Content/Part.tsx | 1 + .../Chat/Messages/Content/Parts/SteerPart.tsx | 7 +++++- .../Parts/__tests__/SteerPart.test.tsx | 22 +++++++++++++++++++ .../src/components/Chat/Steering/Receipt.tsx | 21 ++++++++++++------ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/client/src/components/Chat/Messages/Content/Part.tsx b/client/src/components/Chat/Messages/Content/Part.tsx index a21b5f9920..67f7bb75b1 100644 --- a/client/src/components/Chat/Messages/Content/Part.tsx +++ b/client/src/components/Chat/Messages/Content/Part.tsx @@ -84,6 +84,7 @@ const Part = memo(function Part({ quotes={part.quotes} steerId={part.steerId} createdAt={part.createdAt} + isSubmitting={isSubmitting} /> ); } diff --git a/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx b/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx index 5c5446fd3a..ca472fed1c 100644 --- a/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/SteerPart.tsx @@ -29,6 +29,7 @@ const SteerPart = memo(function SteerPart({ quotes, steerId, createdAt, + isSubmitting = false, }: { steer: string; files?: TMessage['files']; @@ -38,6 +39,10 @@ const SteerPart = memo(function SteerPart({ /** Anchors the part for the message-nav rail (`#steer-` rib target). */ steerId?: string; createdAt?: number; + /** The owning response is still generating: the receipt keeps its amber + * steering identity while it is the live thing at the end, then settles + * to timestamp gray (always settled on reload, share, and search). */ + isSubmitting?: boolean; }) { const localize = useLocalize(); /** Read the atom rather than the auth context: AuthContextProvider mirrors the @@ -151,7 +156,7 @@ const SteerPart = memo(function SteerPart({
- +
diff --git a/client/src/components/Chat/Messages/Content/Parts/__tests__/SteerPart.test.tsx b/client/src/components/Chat/Messages/Content/Parts/__tests__/SteerPart.test.tsx index 6fd7d30462..6b4fdc1a06 100644 --- a/client/src/components/Chat/Messages/Content/Parts/__tests__/SteerPart.test.tsx +++ b/client/src/components/Chat/Messages/Content/Parts/__tests__/SteerPart.test.tsx @@ -260,3 +260,25 @@ describe('SteerPart live receipt draw-in', () => { expect(screen.getByTestId('live-ids').textContent).toBe(''); }); }); + +describe('SteerPart receipt settling', () => { + const checks = () => screen.getByLabelText('com_ui_steer_applied_info').querySelector('svg'); + + it('keeps the amber identity while the owning response is still generating', () => { + render( + set(store.user, SEEDED_USER as never)}> + + , + ); + expect(checks()).toHaveClass('dark:text-amber-500'); + expect(checks()).not.toHaveClass('text-text-secondary'); + }); + + it('settles to timestamp gray once the response is done, and on reload/share', () => { + // The default (no isSubmitting) is the settled, reload, share, and search + // rendering: still a double check, no longer lit. + renderPart(); + expect(checks()).toHaveClass('text-text-secondary'); + expect(checks()).not.toHaveClass('dark:text-amber-500'); + }); +}); diff --git a/client/src/components/Chat/Steering/Receipt.tsx b/client/src/components/Chat/Steering/Receipt.tsx index 163ded1f66..da2f1b5b1f 100644 --- a/client/src/components/Chat/Steering/Receipt.tsx +++ b/client/src/components/Chat/Steering/Receipt.tsx @@ -22,20 +22,27 @@ const AMBER_DOT = 'bg-amber-600 dark:bg-amber-500'; * - `interrupting`: asked to seal generation at the next safe boundary — the * amber label and pulsing dot say "working, not stuck". Until the arm is * server-confirmed (`confirmed` false) the label shows without its check. - * - `applied`: the running response durably absorbed the words (amber double - * check, persistent — it renders identically live, on reload, and in - * shared/search views). + * - `applied`: the running response durably absorbed the words (double check, + * persistent). It stays lit amber only while the owning response is still + * generating — the window where "did it land" is the question — then + * settles to the same muted gray as the timestamp, so a conversation full + * of historical steers doesn't glow forever. * * The hover explanations render through the shared `InfoHoverCard` with the * receipt as the trigger, so the info text doubles as the accessible name. */ const SteerReceipt = memo(function SteerReceipt({ state, + live = false, confirmed = true, animateIn = false, className, }: { state: SteerReceiptState; + /** Applied only: the owning response is still generating, so the receipt is + * the newest thing on screen and keeps the amber steering identity. Off + * (settled, reload, share, search) it dims to the timestamp gray. */ + live?: boolean; /** Server confirmation of the underlying enqueue/arm; an interrupt awaiting * its ACK shows the label and pulse without a check. */ confirmed?: boolean; @@ -62,15 +69,15 @@ const SteerReceipt = memo(function SteerReceipt({