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.
This commit is contained in:
xxxigm
2026-08-27 01:38:19 +07:00
committed by GitHub
parent d0fdbfd655
commit b519ce29ad
4 changed files with 26 additions and 5 deletions

View File

@@ -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(<AttachmentList attachments={[makeAttachment('a', 'doc.pdf')]} onRemove={onRemove} />)
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([])

View File

@@ -149,7 +149,7 @@ function AttachmentPill({ attachment, onRemove }: { attachment: ComposerAttachme
return (
<>
<Tip label={attachment.path || attachment.detail || attachment.label}>
<div className="group/attachment relative min-w-0 shrink-0">
<div className="relative min-w-0 shrink-0">
<button
aria-busy={isUploading || undefined}
aria-label={canPreview ? c.previewLabel(attachment.label) : attachment.label}
@@ -204,11 +204,11 @@ function AttachmentPill({ attachment, onRemove }: { attachment: ComposerAttachme
{onRemove && (
<button
aria-label={c.removeAttachment(attachment.label)}
className="absolute -right-1 -top-1 grid size-3.5 place-items-center rounded-full border border-border/70 bg-background text-muted-foreground opacity-0 shadow-xs transition hover:bg-accent hover:text-foreground group-hover/attachment:opacity-100 focus-visible:opacity-100"
className="absolute -right-1 -top-1 z-10 grid size-4 place-items-center rounded-full border border-border/70 bg-background text-muted-foreground shadow-xs transition hover:bg-accent hover:text-foreground"
onClick={() => onRemove(attachment.id)}
type="button"
>
<Codicon name="close" size="0.625rem" />
<Codicon name="close" size="0.7rem" />
</button>
)}
</div>

View File

@@ -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(<MarkdownTextContent isRunning={false} text={fenced('js', SMALL_SNIPPET)} />)
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(<MarkdownTextContent isRunning text={fenced('html', HTML_DOC)} />)

View File

@@ -15,7 +15,8 @@ import { isLikelyProseCodeBlock } from '@/lib/markdown-code'
* own the wrapping `<CodeCard>` 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<HermesSyntaxHighlighterProps> = ({
<CodeCard data-streaming={defer ? 'true' : undefined}>
<CopyButton
appearance="inline"
className="absolute right-1.5 top-1.5 z-10 h-5 gap-0 rounded-md px-1 opacity-0 transition-opacity group-hover/code:opacity-100 focus-visible:opacity-100"
className="absolute right-1.5 top-1.5 z-10 h-5 gap-0 rounded-md bg-(--ui-bg-editor)/90 px-1"
iconClassName="size-2.5"
label={t.assistant.tool.copyCode}
showLabel={false}