From 4686ec6594a7dab44d080c1bc44474d1102b6b98 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 27 Mar 2026 04:42:15 +0100 Subject: [PATCH] feat(federation): add federation status badges to attachment renderer --- .../components/chat/AttachmentRenderer.tsx | 108 ++++++++++++++---- 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/packages/web/src/components/chat/AttachmentRenderer.tsx b/packages/web/src/components/chat/AttachmentRenderer.tsx index 9fd6fb44..de132636 100644 --- a/packages/web/src/components/chat/AttachmentRenderer.tsx +++ b/packages/web/src/components/chat/AttachmentRenderer.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type { Attachment } from '@backspace/shared'; import { useUIStore } from '../../stores/uiStore'; +import { Tooltip } from '../ui/Tooltip'; interface AttachmentRendererProps { attachment: Attachment; @@ -28,11 +29,66 @@ export function AttachmentRenderer({ attachment }: AttachmentRendererProps) { const { mimetype, originalName, size } = attachment; + // Federation status badge + const federationBadge = (() => { + if (!attachment.federationStatus) return null; + + if (attachment.federationStatus === 'remote') { + // Receiving side — file hosted on sender's instance + let senderName = 'the sender'; + if (attachment.federationMeta) { + try { + const meta = JSON.parse(attachment.federationMeta); + if (meta.sourceUsername) senderName = meta.sourceUsername; + } catch { /* ignore */ } + } + return ( + +
+ + + +
+
+ ); + } + + if (attachment.federationStatus === 'remote_partial') { + // Sending side — file rejected by some peers + let tooltipText = 'Some recipients cannot cache this file locally.'; + if (attachment.federationMeta) { + try { + const meta: Array<{ username: string; limit: number }> = JSON.parse(attachment.federationMeta); + if (meta.length > 0) { + const parts = meta.map(u => { + const limitMb = Math.round(u.limit / (1024 * 1024)); + return `${u.username}'s instance limit (${limitMb} MB)`; + }); + tooltipText = `Exceeds ${parts.join(' and ')}. They can still view this file from your instance.`; + } + } catch { /* ignore */ } + } + return ( + +
+ + + + + +
+
+ ); + } + + return null; + })(); + if (mimetype.startsWith('image/')) { const { width, height } = attachment; return (
openImagePreview(attUrl)} loading="lazy" /> + {federationBadge}
); } @@ -51,7 +108,7 @@ export function AttachmentRenderer({ attachment }: AttachmentRendererProps) { const hasDimensions = width && height; return (
+ {federationBadge}
); } if (mimetype.startsWith('audio/')) { return ( -
+
@@ -90,30 +148,34 @@ export function AttachmentRenderer({ attachment }: AttachmentRendererProps) { Your browser does not support audio playback. + {federationBadge}
); } return ( - -
- - - -
-
-

{originalName}

-

{formatFileSize(size)}

-
-
+
+ +
+ + + +
+
+

{originalName}

+

{formatFileSize(size)}

+
+
+ {federationBadge} +
); }