From bf75f4657d9a43079c9d2c19a0b6af90fcd696db Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Fri, 5 Jun 2026 11:25:19 -0400 Subject: [PATCH] fix: glassmorphism sidebar background visibility + image upload cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Glassmorphism: the sidebar is a flex sibling of the room view, so backdrop-filter had nothing behind it to blur. Fix: apply the active chat background to document.body when glassmorphismSidebar is on (cleaned up when it's turned off or the component unmounts). Now the sidebar blurs through the same background pattern as the room view, making the frosted-glass effect obvious. Image upload cleanup: delete the pre-uploaded original MXC from the homeserver after the compressed version is successfully uploaded (Synapse 1.97+ DELETE /_matrix/client/v1/media/{server}/{mediaId}). Also delete on cancel when a successful upload is removed by the user. Both are best-effort — failures are swallowed so UX is unaffected. Added tryDeleteMxcContent() utility to src/app/utils/matrix.ts. Co-Authored-By: Claude Sonnet 4.6 --- .../upload-card/UploadCardRenderer.tsx | 5 +++ src/app/features/room/RoomInput.tsx | 3 ++ src/app/pages/client/SidebarNav.tsx | 34 ++++++++++++++++++- src/app/utils/matrix.ts | 17 ++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/app/components/upload-card/UploadCardRenderer.tsx b/src/app/components/upload-card/UploadCardRenderer.tsx index 4628e2b26..fe616c746 100644 --- a/src/app/components/upload-card/UploadCardRenderer.tsx +++ b/src/app/components/upload-card/UploadCardRenderer.tsx @@ -13,6 +13,7 @@ import { import { useObjectURL } from '../../hooks/useObjectURL'; import { useMediaConfig } from '../../hooks/useMediaConfig'; import { compressImage, formatFileSize, isCompressible } from '../../utils/imageCompression'; +import { tryDeleteMxcContent } from '../../utils/matrix'; type PreviewImageProps = { fileItem: TUploadItem; @@ -274,6 +275,10 @@ export function UploadCardRenderer({ }; const removeUpload = () => { + if (upload.status === UploadStatus.Success) { + // Upload already completed — delete the orphaned MXC from the server. + tryDeleteMxcContent(mx, upload.mxc); + } cancelUpload(); onRemove(file); }; diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 6c54104a1..0444365f2 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -65,6 +65,7 @@ import { getImageInfo, getMxIdLocalPart, mxcUrlToHttp, + tryDeleteMxcContent, } from '../../utils/matrix'; import { compressImage } from '../../utils/imageCompression'; import { useTypingStatusUpdater } from '../../hooks/useTypingStatusUpdater'; @@ -434,6 +435,8 @@ export const RoomInput = forwardRef( }); const compressedMxc = (uploadRes as { content_uri: string }).content_uri; if (compressedMxc) { + // Delete the pre-uploaded original so only one copy lives on the server. + tryDeleteMxcContent(mx, upload.mxc); mxc = compressedMxc; // Build a synthetic fileItem that refers to the compressed file so // getImageMsgContent picks up the correct dimensions and type. diff --git a/src/app/pages/client/SidebarNav.tsx b/src/app/pages/client/SidebarNav.tsx index 8f0ec1ead..9a5910eae 100644 --- a/src/app/pages/client/SidebarNav.tsx +++ b/src/app/pages/client/SidebarNav.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { Scroll } from 'folds'; import classNames from 'classnames'; @@ -23,10 +23,42 @@ import { import { CreateTab } from './sidebar/CreateTab'; import { useSetting } from '../../state/hooks/settings'; import { settingsAtom } from '../../state/settings'; +import { useTheme, ThemeKind } from '../../hooks/useTheme'; +import { getChatBg } from '../../features/lotus/chatBackground'; export function SidebarNav() { const scrollRef = useRef(null) as React.RefObject; const [glassmorphismSidebar] = useSetting(settingsAtom, 'glassmorphismSidebar'); + const [chatBackground] = useSetting(settingsAtom, 'chatBackground'); + const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal'); + const theme = useTheme(); + const isDark = theme.kind === ThemeKind.Dark; + + // backdrop-filter only blurs content directly behind the element in the z-axis. + // The sidebar is a flex sibling of the room view, so nothing sits behind it by default. + // Fix: mirror the active chat background onto document.body so the sidebar blurs through it. + useEffect(() => { + const { style } = document.body; + if (!glassmorphismSidebar) { + style.removeProperty('background-image'); + style.removeProperty('background-color'); + style.removeProperty('background-size'); + style.removeProperty('background-position'); + return; + } + const effectiveBg = lotusTerminal && chatBackground === 'none' ? 'tactical' : chatBackground; + const bgStyle = getChatBg(effectiveBg, isDark); + style.backgroundImage = (bgStyle.backgroundImage as string | undefined) ?? ''; + style.backgroundColor = (bgStyle.backgroundColor as string | undefined) ?? ''; + style.backgroundSize = (bgStyle.backgroundSize as string | undefined) ?? ''; + style.backgroundPosition = (bgStyle.backgroundPosition as string | undefined) ?? ''; + return () => { + style.removeProperty('background-image'); + style.removeProperty('background-color'); + style.removeProperty('background-size'); + style.removeProperty('background-position'); + }; + }, [glassmorphismSidebar, chatBackground, lotusTerminal, isDark]); return ( diff --git a/src/app/utils/matrix.ts b/src/app/utils/matrix.ts index 21ddaca8b..cc83cee3c 100644 --- a/src/app/utils/matrix.ts +++ b/src/app/utils/matrix.ts @@ -401,3 +401,20 @@ export const creatorsSupported = (version: string): boolean => { const unsupportedVersion = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']; return !unsupportedVersion.includes(version); }; + +// Best-effort deletion of a user-owned MXC URI from the homeserver. +// Synapse 1.97+ supports DELETE /_matrix/client/v1/media/{server}/{mediaId} for media owners. +// Failures are silently ignored — this is cleanup only, not critical path. +export const tryDeleteMxcContent = async (mx: MatrixClient, mxcUrl: string): Promise => { + try { + const path = mxcUrl.replace('mxc://', ''); + const token = mx.getAccessToken(); + if (!token || !path.includes('/')) return; + await fetch(`${mx.getHomeserverUrl()}/_matrix/client/v1/media/${path}`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${token}` }, + }); + } catch { + // Intentionally swallowed + } +};