From 1cd1e8c598dc4941d5627dd35e91fb2b72b64e33 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Fri, 6 Nov 2026 07:07:04 -0600 Subject: [PATCH] fix(ui): place task message copy control left of the timestamp (#19451) ## Thinking Path > - Paperclip is the open source app people use to manage agents at work. > - Task threads show a time below each message. > - Own messages align with the right edge. > - The copy control added in #19442 sits to the right of the time. > - This shifts the time inward even when the control is hidden. > - This pull request moves the control to the left of the time. > - The time stays at the outside edge. ## Linked Issues or Issue Description Refs #19442. **What happened?** The copy control occupies space to the right of the time on own messages. **Expected behavior** The copy control sits to the left of the time. The time stays at the right edge. **Steps to reproduce** Open a task with your own sent message. Move the pointer over its footer. ## What Changed - Move TaskChatBubbleActions before the time in the own-message footer. - Keep the layout change inside TaskChatBubble. No shared contract change is needed. ## Verification - All 36 TaskChatBubble tests pass. - Design-token gates pass. - UI typecheck and production build pass. - Browser layout checks pass on desktop and touch: copy is left of the time, the time ends at the bubble edge, and the time does not move on hover. - Clipboard contents and copied feedback pass. - Screenshots cover idle, hover, copied feedback, and touch using the production component in the existing Storybook fixture. - Current-head CI passed: build, typecheck, selected general tests, all eight e2e shards, policy, security, and aggregate verification. - Greptile and Paperclip reviews score the current commit 5/5. No open findings or review threads remain. - The first Greptile review requested an order assertion. The existing copy test now checks that the control precedes the time and the time is the last footer element. All 36 component tests pass. The review thread is resolved. - Initial test and Storybook commands used the wrong working directory or argument form. Corrected commands run successfully. - Local typecheck and Storybook initially could not resolve mdast-util-from-markdown. A frozen dependency install restored the missing package. Typecheck, build, and browser checks then passed. An early browser retry reached the server before it was ready; the ready-server run passed. ## Risks - Low risk. This change only moves the existing control before the time. ## Model Used - OpenAI GPT-6 Codex. Exact deployed model ID and context window are not exposed in this runtime. The agent used reasoning, tools, and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with Fixes / Closes / Refs OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal ticket id or instance details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable (existing copy tests cover unchanged behavior; browser checks cover position) - [x] I have updated relevant documentation to reflect my changes (the behavior is described here) - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --- ui/src/components/task-chat/TaskChatBubble.test.tsx | 5 ++++- ui/src/components/task-chat/TaskChatBubble.tsx | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/src/components/task-chat/TaskChatBubble.test.tsx b/ui/src/components/task-chat/TaskChatBubble.test.tsx index efa46befe8a..3f77f861d11 100644 --- a/ui/src/components/task-chat/TaskChatBubble.test.tsx +++ b/ui/src/components/task-chat/TaskChatBubble.test.tsx @@ -155,7 +155,10 @@ describe("TaskChatBubble attachment chips", () => { , )); const toolbar = container.querySelector('[data-testid="task-chat-bubble-actions"]')!; - expect(toolbar.parentElement?.parentElement?.textContent).toContain("15m ago"); + const copyWrapper = toolbar.parentElement!; + const timestamp = copyWrapper.nextElementSibling; + expect(timestamp?.textContent).toBe("15m ago"); + expect(timestamp).toBe(copyWrapper.parentElement?.lastElementChild); const button = toolbar.querySelector('button[aria-label="Copy message"]') as HTMLButtonElement; await act(async () => button.click()); expect(copyTextToClipboard).toHaveBeenLastCalledWith(text); diff --git a/ui/src/components/task-chat/TaskChatBubble.tsx b/ui/src/components/task-chat/TaskChatBubble.tsx index da67905b546..da38b567dff 100644 --- a/ui/src/components/task-chat/TaskChatBubble.tsx +++ b/ui/src/components/task-chat/TaskChatBubble.tsx @@ -434,14 +434,14 @@ function TaskChatBubbleContent({ ) : isOwnMessage ? (