fix(desktop): rename generic attachment action from 'Attach image' to 'Attach file' (#2381) (#4304)

Fixes #2381.

## What was broken

The message composer's paperclip accepts generic attachments — images,
videos, PDFs, archives, and any other supported file — but its tooltip
and accessible name still read **"Attach image"**. Sighted users might
reasonably believe the control is image-only, and screen-reader users
get an incomplete description of what the button does.

## The fix

Rename the accessible name and tooltip text on the generic composer
paperclip in `MessageComposerToolbar.tsx`:

- `aria-label` — `"Attach image"` → `"Attach file"`
- `<TooltipContent>` — `"Attach image"` → `"Attach file"`

Plus update the 12 affected Desktop e2e selectors across five spec files
to reference the new accessible name:

- `desktop/tests/e2e/file-attachment.spec.ts` (2 selectors)
- `desktop/tests/e2e/spoiler.spec.ts` (2)
- `desktop/tests/e2e/composer-image-draw.spec.ts` (2)
- `desktop/tests/e2e/image-attachment-gallery.spec.ts` (4)
- `desktop/tests/e2e/video-attachment.spec.ts` (2)

## Scope (per the issue)

The feedback screenshot dialog
(`desktop/src/features/settings/ui/SendFeedbackDialog.tsx`) is
**unchanged** — that dialog itself is image-only, so its "Attach image"
wording is accurate. This PR only touches the generic composer control.

## Test plan

- All **105** unit tests in
`desktop/src/features/messages/ui/*.test.mjs` pass locally.
- Verified no remaining `"Attach image"` string outside the
intentionally preserved feedback dialog:
  ```sh
  grep -rn '"Attach image"' desktop/
  # → only hits in SendFeedbackDialog.tsx
  ```
- The six e2e specs are only exercised in CI; the selector updates are
mechanical and verified by grep to reference the new a11y name.

## Blast radius

- **Files touched**: `MessageComposerToolbar.tsx` (two strings); five
e2e spec files (12 selector updates).
- **User-facing behaviour**: one tooltip + one screen-reader name
change; no functional or visual changes otherwise.
- **No API or state change.**

## Out of scope

- The feedback dialog's "Attach image" wording — kept per the issue's
own "Scope" guidance.
- Any i18n plumbing — Buzz Desktop doesn't currently localize these
strings.

Signed-off-by: Sarthak Singh <sarthak.singh@juspay.in>
Signed-off-by: Ravneet Arora <rarora@squareup.com>
Co-authored-by: Ravneet Arora <rarora@squareup.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
iroiro147
2026-08-05 15:16:32 -07:00
committed by GitHub
co-authored by Ravneet Arora Cursor
parent 2ea9385015
commit d42d60d64e
6 changed files with 17 additions and 17 deletions
@@ -179,7 +179,7 @@ export const MessageComposerToolbar = React.memo(
<Tooltip disableHoverableContent>
<TooltipTrigger asChild>
<Button
aria-label="Attach image"
aria-label="Attach file"
disabled={composerDisabled || isUploading}
onClick={onPaperclip}
onMouseDown={onCaptureSelection}
@@ -190,7 +190,7 @@ export const MessageComposerToolbar = React.memo(
<Paperclip />
</Button>
</TooltipTrigger>
<TooltipContent>Attach image</TooltipContent>
<TooltipContent>Attach file</TooltipContent>
</Tooltip>
<ComposerEmojiPicker
disabled={composerDisabled}
@@ -72,7 +72,7 @@ test.beforeEach(async ({ page }) => {
test("image annotation overlay and editor controls", async ({ page }) => {
await page.goto("/");
await page.getByTestId("channel-general").click();
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
const composer = page.getByTestId("message-composer");
await expect(composer.getByAltText("Attachment aaaa")).toBeVisible();
@@ -102,7 +102,7 @@ test("draw on an uploaded image, save replaces it, revert restores in place", as
await expect(page.getByTestId("chat-title")).toHaveText("general");
// Attach the original image via the mocked paperclip flow.
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
const composer = page.getByTestId("message-composer");
await expect(composer.getByAltText("Attachment aaaa")).toBeVisible();
@@ -182,7 +182,7 @@ test("spoiler marking survives drawing on the attachment", async ({ page }) => {
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
const composer = page.getByTestId("message-composer");
await expect(composer.getByAltText("Attachment aaaa")).toBeVisible();
+4 -4
View File
@@ -39,7 +39,7 @@ test.beforeEach(async ({ page }) => {
async function chooseQuarterlyReport(page: Page) {
const [chooser] = await Promise.all([
page.waitForEvent("filechooser"),
page.getByRole("button", { name: "Attach image" }).click(),
page.getByRole("button", { name: "Attach file" }).click(),
]);
await chooser.setFiles({
buffer: Buffer.from("quarterly report"),
@@ -51,7 +51,7 @@ async function chooseQuarterlyReport(page: Page) {
async function chooseLargeVideo(page: Page) {
const [chooser] = await Promise.all([
page.waitForEvent("filechooser"),
page.getByRole("button", { name: "Attach image" }).click(),
page.getByRole("button", { name: "Attach file" }).click(),
]);
await chooser.setFiles({
buffer: Buffer.alloc(16 * 1024 * 1024, 1),
@@ -63,7 +63,7 @@ async function chooseLargeVideo(page: Page) {
async function choosePhoto(page: Page) {
const [chooser] = await Promise.all([
page.waitForEvent("filechooser"),
page.getByRole("button", { name: "Attach image" }).click(),
page.getByRole("button", { name: "Attach file" }).click(),
]);
await chooser.setFiles({
buffer: Buffer.from("photo"),
@@ -542,7 +542,7 @@ test("forum posts emit a FileCard for generic attachments, not a broken image",
await page.getByRole("button", { name: "Start a new post..." }).click();
// Paperclip → mocked pick_and_upload_media returns the PDF descriptor.
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
// Submit the (attachment-only) forum post.
await page.getByTestId("send-message").click();
@@ -123,7 +123,7 @@ test("image bundle lightbox navigates as a gallery", async ({ page }) => {
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.getByTestId("message-input").fill("gallery bundle");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
await page.getByTestId("send-message").click();
await expect(page.getByText("Sending")).toHaveCount(0);
@@ -667,7 +667,7 @@ test("mosaic image context menu is portaled outside the clipped gallery", async
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.getByTestId("message-input").fill("mosaic context menu");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
await page.getByTestId("send-message").click();
await expect(page.getByText("Sending")).toHaveCount(0);
@@ -709,7 +709,7 @@ test("lightbox image context menu stays inside the dialog focus scope", async ({
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.getByTestId("message-input").fill("lightbox context menu");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
await page.getByTestId("send-message").click();
await expect(page.getByText("Sending")).toHaveCount(0);
@@ -751,7 +751,7 @@ test("right-click image shows Copy image and invokes copy command", async ({
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.getByTestId("message-input").fill("copy me");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
await page.getByTestId("send-message").click();
await expect(page.getByText("Sending")).toHaveCount(0);
+2 -2
View File
@@ -96,7 +96,7 @@ test("image attachments can be marked and sent as hidden spoilers", async ({
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
const composer = page.getByTestId("message-composer");
await expect(composer.getByAltText("Attachment cccc")).toBeVisible();
@@ -138,7 +138,7 @@ test("text spoiler stays usable while attachment upload is pending", async ({
// Kick off the (delayed) upload first — the attach button lives in the
// passive toolbar, which is replaced while formatting is expanded.
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
await page.getByRole("button", { name: "Toggle formatting" }).click();
const spoilerButton = page.getByRole("button", {
+2 -2
View File
@@ -183,7 +183,7 @@ async function openReviewWithPostedTimecode(
await expect(page.getByTestId("chat-title")).toHaveText("general");
await waitForMockLiveSubscription(page, "general");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
await expect(
page.getByTestId("message-composer").getByAltText("Video attachment bbbb"),
).toBeVisible();
@@ -238,7 +238,7 @@ test("video upload previews use poster frames and inline videos open review mode
await expect(page.getByTestId("chat-title")).toHaveText("general");
await waitForMockLiveSubscription(page, "general");
await page.getByRole("button", { name: "Attach image" }).click();
await page.getByRole("button", { name: "Attach file" }).click();
const composer = page.getByTestId("message-composer");
const composerPoster = composer.getByAltText("Video attachment bbbb");