diff --git a/packages/app/src/agent-stream/disclosure.browser.test.tsx b/packages/app/src/agent-stream/disclosure.browser.test.tsx index 74c61c4988..b46f10b2b2 100644 --- a/packages/app/src/agent-stream/disclosure.browser.test.tsx +++ b/packages/app/src/agent-stream/disclosure.browser.test.tsx @@ -4,6 +4,8 @@ import { within } from "@testing-library/dom"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { i18n as testI18n } from "@/i18n/i18next"; +import { ExpandableBadge } from "@/components/message"; +import { page, userEvent } from "vitest/browser"; import { PlanCard } from "@/components/plan-card"; import { MessagePreview } from "@/components/user-message"; import { DisclosureRow } from "./disclosure"; @@ -145,3 +147,49 @@ describe("timeline disclosures hold their row", () => { expect(isExpanded()).toBe(true); }); }); + +function renderFileDetails() { + return
File contents
; +} + +describe("tool badge file action", () => { + it("keeps file opening separate from the accessible disclosure button", async () => { + let opened = 0; + function Badge() { + const [expanded, setExpanded] = React.useState(false); + const toggle = React.useCallback(() => setExpanded(!expanded), [expanded]); + const openFile = React.useCallback(() => { + opened += 1; + }, []); + return ( + + ); + } + act(() => root.render()); + await page.getByRole("button", { name: "Read src/index.ts", exact: true }).hover(); + const file = view().getByRole("button", { name: /^Open file$/ }); + expect(file.parentElement?.closest("button")).toBeNull(); + await page.getByRole("button", { name: "Open file", exact: true }).click(); + expect(opened).toBe(1); + const disclosure = view().getByRole("button", { name: /^Read src\/index\.ts$/ }); + expect(view().queryByText("File contents")).toBeNull(); + await page.getByRole("button", { name: "Read src/index.ts", exact: true }).click(); + + expect(view().getByText("File contents")).toBeTruthy(); + expect(opened).toBe(1); + act(() => file.focus()); + await userEvent.keyboard("{Enter}"); + expect(opened).toBe(2); + expect(view().getByText("File contents")).toBeTruthy(); + act(() => disclosure.focus()); + await userEvent.keyboard("{Enter}"); + expect(view().queryByText("File contents")).toBeNull(); + }); +}); diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index 2bc96f6b97..8f3f16cf51 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -929,12 +929,17 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({ pressablePressed: { opacity: 0.9, }, + disclosureButton: { + flexShrink: 1, + flexDirection: "row", + alignItems: "center", + }, headerRow: { flexDirection: "row", alignItems: "center", }, labelRow: { - flex: 1, + flexShrink: 1, flexDirection: "row", alignItems: "center", overflow: "hidden", @@ -989,10 +994,6 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({ borderRadius: theme.borderRadius.md, flexShrink: 0, }, - openFileButtonPlaceholderIcon: { - width: 14, - height: 14, - }, detailWrapper: { borderBottomLeftRadius: theme.borderRadius.lg, borderBottomRightRadius: theme.borderRadius.lg, @@ -1598,7 +1599,6 @@ interface ExpandableBadgeWebShimmerOverlayProps { secondaryLabel?: string; shimmerLabelTextStyle: StyleProp; shimmerSecondaryTextStyle: StyleProp; - showOpenFileButton: boolean; } function ExpandableBadgeWebShimmerOverlay({ @@ -1606,7 +1606,6 @@ function ExpandableBadgeWebShimmerOverlay({ secondaryLabel, shimmerLabelTextStyle, shimmerSecondaryTextStyle, - showOpenFileButton, }: ExpandableBadgeWebShimmerOverlayProps) { return ( @@ -1618,14 +1617,7 @@ function ExpandableBadgeWebShimmerOverlay({ {secondaryLabel} ) : null} - {showOpenFileButton ? ( - - - - ) : null} - {!secondaryLabel && !showOpenFileButton ? ( - - ) : null} + {!secondaryLabel ? : null} ); } @@ -1649,11 +1641,6 @@ interface ExpandableBadgeLabelRowProps { onLabelRowLayout: (event: LayoutChangeEvent) => void; onLabelLayout: (event: LayoutChangeEvent) => void; onSecondaryLayout: (event: LayoutChangeEvent) => void; - showOpenFileButton: boolean; - isOpenFileHovered: boolean; - onOpenFilePress: (event: GestureResponderEvent) => void; - onOpenFileHoverIn: () => void; - onOpenFileHoverOut: () => void; } function ExpandableBadgeLabelRow({ @@ -1675,13 +1662,7 @@ function ExpandableBadgeLabelRow({ onLabelRowLayout, onLabelLayout, onSecondaryLayout, - showOpenFileButton, - isOpenFileHovered, - onOpenFilePress, - onOpenFileHoverIn, - onOpenFileHoverOut, }: ExpandableBadgeLabelRowProps) { - const { t } = useTranslation(); return ( - {showOpenFileButton ? ( - - - - ) : null} {isWebShimmer ? ( ) : null} {isNativeShimmer ? ( @@ -1928,6 +1891,7 @@ export const ExpandableBadge = memo(function ExpandableBadge({ borderlessWhenExpanded = false, testID, }: ExpandableBadgeProps) { + const { t } = useTranslation(); const resolvedDisableOuterSpacing = useDisableOuterSpacing(disableOuterSpacing); const [isHovered, setIsHovered] = useState(false); const [isOpenFileHovered, setIsOpenFileHovered] = useState(false); @@ -2190,13 +2154,13 @@ export const ExpandableBadge = memo(function ExpandableBadge({ onPointerEnter={isWeb ? handleHoverIn : undefined} onPointerLeave={isWeb ? handleHoverOut : undefined} > - - + + {iconSlotNode} - - + + {onOpenFile && isHovered ? ( + + + + ) : null} + {detailContent ? (