From 4f0025700fcdacbecc7af7649f200e7ccef22f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Mon, 1 Jun 2026 00:43:48 +0800 Subject: [PATCH] fix(desktop): scope inline image gallery hover Inline image galleries live inside chat message shells that already use Tailwind group hover for message-level affordances. Reusing the unqualified group selector on image tiles made a parent message hover reveal every fullscreen affordance in the gallery at once. Naming the image tile group keeps the overlay state local to the hovered tile while preserving the existing layout and click behavior. Constraint: Desktop chat message shells also use generic Tailwind group hover selectors. Rejected: Track hovered image index in React state | unnecessary runtime state for a selector-scoping issue Confidence: high Scope-risk: narrow Directive: Keep image-tile hover selectors scoped if this gallery remains nested inside message shells. Tested: cd desktop && bun run test InlineImageGallery.test.tsx --run Tested: bun run check:desktop Tested: git diff --check Not-tested: Manual desktop screenshot smoke --- .../chat/InlineImageGallery.test.tsx | 20 +++++++++++++++++++ .../components/chat/InlineImageGallery.tsx | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/desktop/src/components/chat/InlineImageGallery.test.tsx b/desktop/src/components/chat/InlineImageGallery.test.tsx index 7862c0d7..18101f14 100644 --- a/desktop/src/components/chat/InlineImageGallery.test.tsx +++ b/desktop/src/components/chat/InlineImageGallery.test.tsx @@ -64,6 +64,26 @@ describe('InlineImageGallery', () => { ]) }) + it('scopes image hover overlays to each image tile', () => { + render( +
+ +
, + ) + + const firstTile = screen.getByRole('button', { name: /photo\.png/i }) + expect(firstTile).toHaveClass('group/image') + expect(firstTile).not.toHaveClass('group') + + const overlay = firstTile.querySelector('.group-hover\\/image\\:opacity-100') + expect(overlay).not.toBeNull() + expect(firstTile.querySelector('.group-hover\\:opacity-100')).toBeNull() + }) + it('does not render an in-workspace absolute path twice (dedup by basename)', () => { // The absolute path is INSIDE workDir, so extractAssistantOutputTargets also // surfaces it as a relative target (frame.png). It must only render once. diff --git a/desktop/src/components/chat/InlineImageGallery.tsx b/desktop/src/components/chat/InlineImageGallery.tsx index 104d5c6f..44427029 100644 --- a/desktop/src/components/chat/InlineImageGallery.tsx +++ b/desktop/src/components/chat/InlineImageGallery.tsx @@ -114,7 +114,7 @@ export function InlineImageGallery({ text, sessionId, workDir }: Props) { key={img.src} type="button" onClick={() => setActiveIndex(i)} - className="group relative overflow-hidden rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] text-left shadow-sm transition-all hover:shadow-md hover:border-[var(--color-brand)]/40" + className="group/image relative overflow-hidden rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] text-left shadow-sm transition-all hover:shadow-md hover:border-[var(--color-brand)]/40" > -
+
fullscreen