diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index 37a20e9cb358075b5927a328690210a61c712516..7d17842f270a6f0b94ae4db918bb378dcf12e223 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -76,7 +76,7 @@ Item {
         anchors.left: bubbleOnRight? undefined : parent.left
         anchors.right: bubbleOnRight? parent.right : undefined
         property int maxWidth: parent.width-anchors.leftMargin-anchors.rightMargin
-        width: Settings.bubbles? Math.min(maxWidth,implicitWidth+4) : maxWidth
+        width: Settings.bubbles? Math.min(maxWidth,implicitWidth+metadata.width+12) : maxWidth
         leftPadding: 4
         rightPadding: (Settings.bubbles && !isStateEvent)? 4: 2
         topPadding: (Settings.bubbles && !isStateEvent)? 4: 2
@@ -103,7 +103,6 @@ Item {
                 Layout.fillWidth: true
                 Layout.bottomMargin: visible? 2 : 0
                 Layout.preferredHeight: height
-                Layout.maximumWidth: implicitWidth
                 id: reply
 
                 function fromModel(role) {
@@ -140,7 +139,6 @@ Item {
                 Layout.column: 0
                 Layout.fillWidth: true
                 Layout.preferredHeight: height
-                Layout.maximumWidth: implicitWidth
                 id: contentItem
 
                 blurhash: r.blurhash
@@ -165,6 +163,7 @@ Item {
                 encryptionError: r.encryptionError
                 relatedEventCacheBuster: r.relatedEventCacheBuster
                 isReply: false
+                metadataWidth: metadata.width
             }
 
             RowLayout {
@@ -173,6 +172,7 @@ Item {
                 Layout.row: Settings.bubbles? 2 : 0
                 Layout.rowSpan: Settings.bubbles? 1 : 2
                 Layout.bottomMargin: -2
+                Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles)? -height-Layout.bottomMargin : 0
                 Layout.alignment: Qt.AlignTop | Qt.AlignRight
                 Layout.preferredWidth: implicitWidth
                 visible: !isStateEvent
diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml
index fd81b176e21654af559923f18103531b0bd6ce51..daae8635d60fe67270314fbf60a66c2b8548396d 100644
--- a/resources/qml/delegates/FileMessage.qml
+++ b/resources/qml/delegates/FileMessage.qml
@@ -12,15 +12,17 @@ Item {
     required property string filename
     required property string filesize
 
-    height: row.height + 24
+    height: row.height + (Settings.bubbles? 16: 24)
     width: parent.width
-    implicitWidth: row.implicitWidth
+    implicitWidth: row.implicitWidth+metadataWidth
+    property int metadataWidth
+    property bool fitsMetadata: true
 
     RowLayout {
         id: row
 
         anchors.centerIn: parent
-        width: parent.width - 24
+        width: parent.width - (Settings.bubbles? 16 : 24)
         spacing: 15
 
         Rectangle {
@@ -88,6 +90,7 @@ Item {
         z: -1
         radius: 10
         anchors.fill: parent
+        visible: !Settings.bubbles // the bubble in a bubble looks odd
     }
 
 }
diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml
index a13bb4f65f1f67b7e9939be64c9abe7c5d299194..fea3eaefcacb053bbff4a31cc509f507fc145498 100644
--- a/resources/qml/delegates/ImageMessage.qml
+++ b/resources/qml/delegates/ImageMessage.qml
@@ -20,9 +20,12 @@ Item {
     property double divisor: isReply ? 5 : 3
 
     implicitWidth: Math.round(originalWidth*Math.min((timelineView.height/divisor)/(originalWidth*proportionalHeight), 1))
-    width: parent.width
+    width: Math.min(parent.width,implicitWidth)
     height: width*proportionalHeight
 
+    property int metadataWidth
+    property bool fitsMetadata: (parent.width - width) > metadataWidth+4
+
     Image {
         id: blurhash_
 
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
index 3210128a0c3226167691d23230226d9cde474562..08b2098ed2228bfbe0c49018d49ed29d799b3723 100644
--- a/resources/qml/delegates/MessageDelegate.qml
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -35,6 +35,8 @@ Item {
     required property string callType
     required property int encryptionError
     required property int relatedEventCacheBuster
+    property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false
+    property int metadataWidth
 
     height: chooser.child ? chooser.child.height : Nheko.paddingLarge
 
@@ -65,6 +67,7 @@ Item {
                 body: d.body
                 isOnlyEmoji: d.isOnlyEmoji
                 isReply: d.isReply
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -78,6 +81,7 @@ Item {
                 isOnlyEmoji: d.isOnlyEmoji
                 isReply: d.isReply
                 isStateEvent: d.isStateEvent
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -92,6 +96,7 @@ Item {
                 isOnlyEmoji: d.isOnlyEmoji
                 isReply: d.isReply
                 isStateEvent: d.isStateEvent
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -109,6 +114,7 @@ Item {
                 filename: d.filename
                 isReply: d.isReply
                 eventId: d.eventId
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -126,6 +132,7 @@ Item {
                 filename: d.filename
                 isReply: d.isReply
                 eventId: d.eventId
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -137,6 +144,7 @@ Item {
                 eventId: d.eventId
                 filename: d.filename
                 filesize: d.filesize
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -153,6 +161,7 @@ Item {
                 url: d.url
                 body: d.body
                 filesize: d.filesize
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -169,6 +178,7 @@ Item {
                 url: d.url
                 body: d.body
                 filesize: d.filesize
+                metadataWidth: d.metadataWidth
             }
 
         }
@@ -177,7 +187,7 @@ Item {
             roleValue: MtxEvent.Redacted
 
             Redacted {
-                //delegateWidth: d.width
+                metadataWidth: d.metadataWidth
             }
         }
 
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 54813d23cd159d7e862e241cc60a869bc96e617c..5188b80e0f1e56cd5c196c1f3c8cab5c17f556be 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -25,10 +25,13 @@ Item {
     property double divisor: isReply ? 4 : 2
     property int tempWidth: originalWidth < 1? 400: originalWidth
     implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500
-    width: parent.width
+    width: Math.min(parent.width, implicitWidth)
     height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height
     implicitHeight: height
 
+    property int metadataWidth
+    property bool fitsMetadata: (parent.width - fileInfoLabel.width) > metadataWidth+4
+
     MxcMedia {
         id: mxcmedia
 
diff --git a/resources/qml/delegates/Redacted.qml b/resources/qml/delegates/Redacted.qml
index 8097fbf88633548e7e729ea75b341baf9b650e01..dcdb7248026a3bf86e20479ec1b8bc3729cca237 100644
--- a/resources/qml/delegates/Redacted.qml
+++ b/resources/qml/delegates/Redacted.qml
@@ -12,9 +12,11 @@ Rectangle{
 
     height: redactedLayout.implicitHeight + Nheko.paddingSmall
     implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
-    width: parent.width
+    width: Math.min(parent.width,implicitWidth+1)
     radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
     color: Nheko.colors.alternateBase
+    property int metadataWidth
+    property bool fitsMetadata: parent.width - redactedLayout.width > metadataWidth + 4
 
     RowLayout {
         id: redactedLayout
diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml
index 741d8aa85e06c58992425812cfa6bacc714343ab..73411ab60acc2d41a3b173f8dd5393aea8cfd6de 100644
--- a/resources/qml/delegates/TextMessage.qml
+++ b/resources/qml/delegates/TextMessage.qml
@@ -13,6 +13,8 @@ MatrixText {
     required property bool isReply
     required property string formatted
     property string copyText: selectedText ? getText(selectionStart, selectionEnd) : body
+    property int metadataWidth
+    property bool fitsMetadata: positionAt(width,height-4) == positionAt(width-metadataWidth-10, height-4)
 
     // table border-collapse doesn't seem to work
     text: "