From 5c9a13fc9577298dd18d1254fb1a0c23276cd080 Mon Sep 17 00:00:00 2001 From: Wanjun Gu Date: Sun, 27 Sep 2026 00:41:10 -0700 Subject: [PATCH 001/364] fix(crew): show peer file names with hidden characters visible and keep them out of save names A file name, server-path label or path comes from another member, and the broker refuses only Cc control characters in them. A U+202E override got through, so q3_{RLO}fdp.terminal read q3_lanimret.pdf on the card, in every control named for it and as the Save dialog's default name. The files area now shows every such string with hidden characters as U+FFFD (the native share dialog's rule, held equal by a test), compares namesakes by the shown name, and offers the Save dialog the name with them left out. A transfer's own name, a copied path and every ID are unchanged. (RENDERER-1) --- .../src/components/crew/composer/Composer.tsx | 12 +- .../crew/composer/ComposerChips.tsx | 13 +- .../components/crew/files/AttachmentCard.tsx | 10 +- .../src/components/crew/files/FilesTab.tsx | 9 +- .../components/crew/files/ServerPathRow.tsx | 9 +- .../src/components/crew/files/TransferRow.tsx | 11 +- .../src/components/crew/files/UploadChip.tsx | 8 +- .../src/components/crew/files/fileName.ts | 58 ++++ .../crew/files/peerFileNames.test.tsx | 254 ++++++++++++++++++ 9 files changed, 361 insertions(+), 23 deletions(-) create mode 100644 ui/desktop/src/components/crew/files/fileName.ts create mode 100644 ui/desktop/src/components/crew/files/peerFileNames.test.tsx diff --git a/ui/desktop/src/components/crew/composer/Composer.tsx b/ui/desktop/src/components/crew/composer/Composer.tsx index 84b36fa56..ab8210827 100644 --- a/ui/desktop/src/components/crew/composer/Composer.tsx +++ b/ui/desktop/src/components/crew/composer/Composer.tsx @@ -21,6 +21,7 @@ import type { DraftFile, DraftReference } from '../state/types'; import { useCrew, useCrewErrorSlot, useCrewSurfaceReset } from '../state/CrewControllerContext'; import { postedLabel, useAttachmentIndexVersion } from '../files/attachmentIndex'; import { filesCopy } from '../files/copy'; +import { visibleFileText } from '../files/fileName'; import { CrewFileDropZone, useCrewDropTarget, @@ -274,15 +275,18 @@ export function Composer({ note, inputRef, agentPaneId }: ComposerProps) { if (attachmentIndex) { for (const file of draft.attachments) { const sha256 = upload.uploads.find((item) => item.blob_id === file.id)?.sha256 ?? ''; - const match = attachmentIndex.compare(file.name, sha256, file.id); + // Names as the cards show them (RENDERER-1): the index holds shown names, so a name is + // compared, and quoted, with its hidden characters made visible. + const fileName = visibleFileText(file.name); + const match = attachmentIndex.compare(fileName, sha256, file.id); if (!match) continue; const when = postedLabel(match.earlier.postedAt); duplicates[file.id] = match.kind === 'same' - ? composerCopy.sameFileShared(file.name, match.earlier.name, name, when) + ? composerCopy.sameFileShared(fileName, match.earlier.name, name, when) : match.kind === 'different' - ? composerCopy.differentFileShared(file.name, name, when) - : composerCopy.alreadyShared(file.name, name, when); + ? composerCopy.differentFileShared(fileName, name, when) + : composerCopy.alreadyShared(fileName, name, when); } } const agentOpen = ui.pane?.mode === 'agent'; diff --git a/ui/desktop/src/components/crew/composer/ComposerChips.tsx b/ui/desktop/src/components/crew/composer/ComposerChips.tsx index d9a17cc21..4b64da620 100644 --- a/ui/desktop/src/components/crew/composer/ComposerChips.tsx +++ b/ui/desktop/src/components/crew/composer/ComposerChips.tsx @@ -2,6 +2,7 @@ import { Badge } from '../../ui/badge'; import { File, Link, XIcon } from '../../icons/app-icons'; import type { CrewTransfer } from '../crewTransfers'; import type { DraftFile, DraftReference } from '../state/types'; +import { visibleFileText } from '../files/fileName'; import { ChipAction } from '../files/GlyphButton'; import { UploadChip } from '../files/UploadChip'; import { composerCopy } from './copy'; @@ -60,9 +61,11 @@ export function ComposerChips({
  • - {file.name} + + {visibleFileText(file.name)} + onRemoveAttachment(file.id)} > @@ -75,9 +78,11 @@ export function ComposerChips({
  • - {item.label} + + {visibleFileText(item.label)} + onRemoveReference(item.id)} > diff --git a/ui/desktop/src/components/crew/files/AttachmentCard.tsx b/ui/desktop/src/components/crew/files/AttachmentCard.tsx index e054de49e..6d7d4e713 100644 --- a/ui/desktop/src/components/crew/files/AttachmentCard.tsx +++ b/ui/desktop/src/components/crew/files/AttachmentCard.tsx @@ -18,6 +18,7 @@ import { CopyForSupport, useMenuCopy } from '../timeline/TimelineCopy'; import { postedLabel, useAttachmentWhich, useRegisterAttachment } from './attachmentIndex'; import { cachedBlob, forgetBlob, rememberBlob } from './blobMetadataCache'; import { filesCopy } from './copy'; +import { saveNameFor, visibleFileText } from './fileName'; import { MoreActionsTrigger } from './GlyphButton'; import { formatBytes } from './formatBytes'; import { TransferMenuItems } from './TransferRow'; @@ -177,7 +178,8 @@ export function AttachmentCard({ channel_id: metadata.channel_id, direction: 'download', blob_id: blobId, - suggestedName: metadata.name, + // Another member chose this name: its hidden characters never reach the Save dialog. + suggestedName: saveNameFor(metadata.name), }), filesCopy.downloadFailed ); @@ -205,12 +207,14 @@ export function AttachmentCard({ } }; - const name = metadata?.name || fallbackName || filesCopy.attachment; + // Another member chose the name: shown, and named in every control, with its hidden + // characters made visible (RENDERER-1). The index compares names as they are shown. + const name = visibleFileText(metadata?.name || fallbackName) || filesCopy.attachment; useRegisterAttachment( blobId, metadata && !sending ? { - name: metadata.name, + name: visibleFileText(metadata.name), sha256: metadata.sha256, complete: metadata.complete, postedAt, diff --git a/ui/desktop/src/components/crew/files/FilesTab.tsx b/ui/desktop/src/components/crew/files/FilesTab.tsx index c55703025..51037c755 100644 --- a/ui/desktop/src/components/crew/files/FilesTab.tsx +++ b/ui/desktop/src/components/crew/files/FilesTab.tsx @@ -11,6 +11,7 @@ import { isoTime, messageTime } from '../timeline/timelineTime'; import { AttachmentCard, type CrewBlob } from './AttachmentCard'; import { postedLabel } from './attachmentIndex'; import { filesCopy } from './copy'; +import { visibleFileText } from './fileName'; import { MoreActionsTrigger } from './GlyphButton'; import { formatBytes } from './formatBytes'; import { ServerPathRow } from './ServerPathRow'; @@ -202,7 +203,7 @@ export function FilesTab() {
    - {file.name} + {visibleFileText(file.name)} {onItsWay ? ( {filesCopy.sending} ) : upload ? ( @@ -226,14 +227,14 @@ export function FilesTab() {
    - {transfer.name} + {visibleFileText(transfer.name)} {formatBytes(transfer.size)} - +