From f32d8fda71e665ff463dafe6db3826d0420cc79b Mon Sep 17 00:00:00 2001 From: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@sprout-oss.stage.blox.sqprod.co> Date: Fri, 3 Jul 2026 00:39:08 -0700 Subject: [PATCH 1/5] feat(composer): split text and media spoiler controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the text spoiler toggle into the expanded formatting toolbar and make it text-only — it no longer mirrors onto pending media attachments. Media spoilers are now toggled per image/video via a new control in the attachment lightbox's top-right cluster, next to the close button and backed by the existing per-URL spoilered set. The lightbox toggle is placed left of where the upcoming image edit/draw control (PR #1488) will sit, with a note to hide it while edit mode is active once that feature lands. Co-authored-by: Taylor Ho Signed-off-by: Taylor Ho --- .../messages/ui/ComposerAttachments.tsx | 61 +++++++++++++++- .../messages/ui/FormattingToolbar.tsx | 36 ++++++---- .../features/messages/ui/MessageComposer.tsx | 49 ++++--------- .../messages/ui/MessageComposerToolbar.tsx | 70 +------------------ desktop/src/shared/ui/SimpleImageLightbox.tsx | 41 ++++++----- desktop/tests/e2e/spoiler.spec.ts | 28 ++++++-- 6 files changed, 142 insertions(+), 143 deletions(-) diff --git a/desktop/src/features/messages/ui/ComposerAttachments.tsx b/desktop/src/features/messages/ui/ComposerAttachments.tsx index e40ede5730..261960cb76 100644 --- a/desktop/src/features/messages/ui/ComposerAttachments.tsx +++ b/desktop/src/features/messages/ui/ComposerAttachments.tsx @@ -11,6 +11,7 @@ import { import { cn } from "@/shared/lib/cn"; import { SimpleImageLightbox } from "@/shared/ui/SimpleImageLightbox"; import { Progress } from "@/shared/ui/progress"; +import { Toggle } from "@/shared/ui/toggle"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; /** Dashed-border overlay shown when a file is dragged over the composer form. */ @@ -36,9 +37,13 @@ type ComposerAttachmentsProps = { uploadingCount?: number; uploadingPreviews?: UploadingAttachmentPreview[]; onRemove: (url: string) => void; + onToggleSpoiler?: (url: string) => void; spoileredUrls?: ReadonlySet; }; +const LIGHTBOX_BUTTON_CLASS = + "rounded-full bg-black/50 p-2 text-white/80 transition-colors hover:bg-black/70 hover:text-white focus:outline-hidden focus:ring-2 focus:ring-white/30"; + const COMPOSER_MEDIA_HEIGHT_PX = 55; const COMPOSER_MEDIA_WIDTH_PX = 55; @@ -61,6 +66,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({ uploadingPreviews = [], onCancelUpload, onRemove, + onToggleSpoiler, spoileredUrls, }: ComposerAttachmentsProps) { if (attachments.length === 0 && !isUploading) return null; @@ -151,6 +157,7 @@ export const ComposerAttachments = React.memo(function ComposerAttachments({ isSpoilered={isSpoilered} isVideo={isVideo} mediaStyle={mediaStyle} + onToggleSpoiler={onToggleSpoiler} thumbUrl={thumbUrl} url={attachment.url} videoPosterUrl={videoPosterUrl ?? null} @@ -239,6 +246,7 @@ function AttachmentMediaLightbox({ isSpoilered, isVideo, mediaStyle, + onToggleSpoiler, thumbUrl, url, videoPosterUrl, @@ -248,6 +256,7 @@ function AttachmentMediaLightbox({ isSpoilered: boolean; isVideo: boolean; mediaStyle: React.CSSProperties; + onToggleSpoiler?: (url: string) => void; thumbUrl: string; url: string; videoPosterUrl: string | null; @@ -299,15 +308,63 @@ function AttachmentMediaLightbox({ onOpenChange={setLightboxOpen} open={lightboxOpen} src={previewSrc} + actions={ + // Hide this alongside image-edit mode once the annotation flow lands. + onToggleSpoiler ? ( + + + onToggleSpoiler(url)} + pressed={isSpoilered} + > + + + + + {isSpoilered ? "Remove spoiler" : "Mark as spoiler"} + + + ) : null + } > {isVideo ? ( // biome-ignore lint/a11y/useMediaCaption: user-uploaded video, no captions available