From b519ce29addb6dca5b864733fab67154ca38393f Mon Sep 17 00:00:00 2001
From: xxxigm <54813621+xxxigm@users.noreply.github.com>
Date: Thu, 27 Aug 2026 01:38:19 +0700
Subject: [PATCH] fix(desktop): keep attachment close and code copy icons
visible (#95611)
* fix(desktop): keep attachment close and code copy icons visible
Hover-only opacity-0 hid the composer remove control and code-block copy button, so they stayed clickable but invisible on Windows and other no-hover surfaces.
* test(desktop): pin attachment close and code copy visibility at rest
The remove chip and code-block copy control must stay in the tree without a hover class, so Windows and no-hover surfaces cannot hide them again.
---
.../src/app/chat/composer/attachments.test.tsx | 12 ++++++++++++
apps/desktop/src/app/chat/composer/attachments.tsx | 6 +++---
.../assistant-ui/markdown-text.artifacts.test.tsx | 8 ++++++++
.../src/components/chat/shiki-highlighter.tsx | 5 +++--
4 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/apps/desktop/src/app/chat/composer/attachments.test.tsx b/apps/desktop/src/app/chat/composer/attachments.test.tsx
index 7377691f0f..ab491a00a5 100644
--- a/apps/desktop/src/app/chat/composer/attachments.test.tsx
+++ b/apps/desktop/src/app/chat/composer/attachments.test.tsx
@@ -236,6 +236,18 @@ describe('AttachmentList', () => {
)
})
+ it('shows the remove control at rest and does not hide it behind hover', async () => {
+ const onRemove = vi.fn()
+
+ await renderWithI18n()
+
+ const remove = screen.getByRole('button', { name: 'Remove doc.pdf' })
+
+ expect(remove.className.split(/\s+/)).not.toContain('opacity-0')
+ fireEvent.click(remove)
+ expect(onRemove).toHaveBeenCalledWith('a')
+ })
+
it('still routes a non-image attachment to the preview rail', async () => {
$previewTabs.set([])
diff --git a/apps/desktop/src/app/chat/composer/attachments.tsx b/apps/desktop/src/app/chat/composer/attachments.tsx
index b87e69068f..39216e0372 100644
--- a/apps/desktop/src/app/chat/composer/attachments.tsx
+++ b/apps/desktop/src/app/chat/composer/attachments.tsx
@@ -149,7 +149,7 @@ function AttachmentPill({ attachment, onRemove }: { attachment: ComposerAttachme
return (
<>
-
+
)}
diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.artifacts.test.tsx b/apps/desktop/src/components/assistant-ui/markdown-text.artifacts.test.tsx
index 89fc0b61c1..af573eadfd 100644
--- a/apps/desktop/src/components/assistant-ui/markdown-text.artifacts.test.tsx
+++ b/apps/desktop/src/components/assistant-ui/markdown-text.artifacts.test.tsx
@@ -65,6 +65,14 @@ describe('MarkdownTextContent artifacts', () => {
expect(artifactsForSession('session-artifacts')).toHaveLength(0)
})
+ it('keeps the code-block copy control visible without hover', async () => {
+ render()
+
+ const copy = await screen.findByRole('button', { name: 'Copy code' })
+
+ expect(copy.className.split(/\s+/)).not.toContain('opacity-0')
+ })
+
it('does not register while the message is still streaming', async () => {
render()
diff --git a/apps/desktop/src/components/chat/shiki-highlighter.tsx b/apps/desktop/src/components/chat/shiki-highlighter.tsx
index 6823bba7cf..eaa8a2bc67 100644
--- a/apps/desktop/src/components/chat/shiki-highlighter.tsx
+++ b/apps/desktop/src/components/chat/shiki-highlighter.tsx
@@ -15,7 +15,8 @@ import { isLikelyProseCodeBlock } from '@/lib/markdown-code'
* own the wrapping `` here and neutralize the upstream
* `data-streamdown="code-block"` chrome from styles.css. The card is
* background-only — no header row, no language label — so a fence reads as a
- * tinted slab of the reply; copy is a hover-reveal control in the corner.
+ * tinted slab of the reply. Copy stays visible in the corner (not hover-only)
+ * so Windows / touch / no-hover surfaces can still find it.
*
* `react-shiki` full bundle so all `bundledLanguages` work; theme switches
* follow the document `color-scheme` via `defaultColor="light-dark()"`.
@@ -148,7 +149,7 @@ export const SyntaxHighlighter: FC = ({