diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index aef05a639c5b4b2d16f20b544ba29106c7200b3b..a00ada3e7524f92b1dfdf5d8768a14bc0a97a3e2 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -33,7 +33,7 @@ ScrollView {
         //reuseItems: true
         boundsBehavior: Flickable.StopAtBounds
         pixelAligned: true
-        spacing: 4
+        spacing: 2
         verticalLayoutDirection: ListView.BottomToTop
         onCountChanged: {
             // Mark timeline as read
@@ -464,7 +464,7 @@ ScrollView {
                 property date timestamp: wrapper.timestamp
 
                 z: 4
-                active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day
+                active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
                 //asynchronous: true
                 sourceComponent: sectionHeader
                 visible: status == Loader.Ready
diff --git a/resources/qml/ReplyPopup.qml b/resources/qml/ReplyPopup.qml
index e6c838350b9192a5d3d98f09778c8a0315189cc9..ef0d7c60a6c59c12b9e8844a986af3e7141bd938 100644
--- a/resources/qml/ReplyPopup.qml
+++ b/resources/qml/ReplyPopup.qml
@@ -47,6 +47,7 @@ Rectangle {
         userId: modelData.userId ?? ""
         userName: modelData.userName ?? ""
         encryptionError: modelData.encryptionError ?? ""
+        width: parent.width
     }
 
     ImageButton {
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index b40ce0687d022a7d5dd27ea3bba235e88d5154eb..85e6fa4bdb8c99ad19116cd177407ffdd32aabb2 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -47,7 +47,7 @@ Item {
 
     anchors.left: parent.left
     anchors.right: parent.right
-    height: row.height+reactionRow.height+(Settings.bubbles? 8 : 4)
+    height: row.height+reactionRow.height
 
     Rectangle {
         color: (Settings.messageHoverHighlight && hoverHandler.hovered) ? Nheko.colors.alternateBase : "transparent"
@@ -78,30 +78,19 @@ Item {
         anchors.rightMargin: 1
         anchors.leftMargin: Nheko.avatarSize + 12 // align bubble with section header
         anchors.left: parent.left
-        //anchors.right: parent.right
         property int maxWidth: parent.width-anchors.leftMargin-anchors.rightMargin
-        width: Math.min(maxWidth,msg.implicitWidth)
-        height: msg.height
-        topInset: -4
-        bottomInset: -4
-        leftInset: -4
-        rightInset: -4
+        width: Settings.bubbles? Math.min(maxWidth,implicitWidth+metadata.width) : maxWidth
+        padding: isStateEvent? 0 : 3
         background: Rectangle {
-            //anchors.fill: msg
             property color userColor: TimelineManager.userColor(userId, Nheko.colors.base)
             property color bgColor: Nheko.colors.base
-            color: Qt.rgba(userColor.r*0.1+bgColor.r*0.9,userColor.g*0.1+bgColor.g*0.9,userColor.b*0.1+bgColor.b*0.9,1) //TimelineManager.userColor(userId, Nheko.colors.base)
-            radius: 4
+            color: Qt.tint(bgColor, Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2))
+            radius: parent.padding*2
             visible: Settings.bubbles && !isStateEvent
         }
 
-        GridLayout {
+        contentItem: GridLayout {
             id: msg
-            anchors {
-                right: parent.right
-                left: parent.left
-                top: parent.top
-            }
             property bool narrowLayout: Settings.bubbles //&& (timelineView.width < 500) // timelineView causes fewew binding loops than r. But maybe it shouldn't depend on width anyway
             rowSpacing: 0
             columnSpacing: 2
@@ -114,6 +103,8 @@ Item {
                 Layout.column: 0
                 Layout.fillWidth: true
                 Layout.bottomMargin: visible? 2 : 0
+                Layout.preferredHeight: height
+                Layout.maximumWidth: implicitWidth
                 id: reply
 
                 function fromModel(role) {
@@ -142,7 +133,6 @@ Item {
                 callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
                 encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? ""
                 relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
-                maxWidth: row.maxWidth
             }
 
             // actual message content
@@ -150,6 +140,8 @@ Item {
                 Layout.row: 1
                 Layout.column: 0
                 Layout.fillWidth: true
+                Layout.preferredHeight: height
+                Layout.maximumWidth: implicitWidth
                 id: contentItem
 
                 blurhash: r.blurhash
@@ -174,7 +166,6 @@ Item {
                 encryptionError: r.encryptionError
                 relatedEventCacheBuster: r.relatedEventCacheBuster
                 isReply: false
-                maxWidth: row.maxWidth
             }
 
             RowLayout {
@@ -182,7 +173,7 @@ Item {
                 Layout.column: msg.narrowLayout? 0 : 1
                 Layout.row: msg.narrowLayout? 2 : 0
                 Layout.rowSpan: msg.narrowLayout? 1 : 2
-                Layout.bottomMargin: msg.narrowLayout? -4 : 0
+                Layout.bottomMargin: -4
                 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 2ac2a80adda02cd6393d5716284003c962932219..1e50fe3ad2a19bceb88aa0298c16b5d33e4e8616 100644
--- a/resources/qml/delegates/FileMessage.qml
+++ b/resources/qml/delegates/FileMessage.qml
@@ -11,11 +11,10 @@ Item {
     required property string eventId
     required property string filename
     required property string filesize
-    property int maxWidth
 
     height: row.height + 24
-    width: maxWidth
-    implicitWidth: maxWidth
+    width: parent.width
+    implicitWidth: row.implicitWidth
 
     RowLayout {
         id: row
diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml
index 3bbcd077f30c1e653d9cb5298102098f28f615ca..a13bb4f65f1f67b7e9939be64c9abe7c5d299194 100644
--- a/resources/qml/delegates/ImageMessage.qml
+++ b/resources/qml/delegates/ImageMessage.qml
@@ -17,16 +17,11 @@ Item {
     required property string filename
     required property bool isReply
     required property string eventId
-    property int maxWidth
-    property double tempWidth: Math.min(maxWidth, originalWidth < 1 ? 200 : originalWidth)
-    property double tempHeight: tempWidth * proportionalHeight
     property double divisor: isReply ? 5 : 3
-    property bool tooHigh: tempHeight > timelineView.height / divisor
 
-    height: Math.round(tooHigh ? timelineView.height / divisor : tempHeight)
-    width: Math.round(tooHigh ? (timelineView.height / divisor) / proportionalHeight : tempWidth)
-    implicitHeight: height
-    implicitWidth: width
+    implicitWidth: Math.round(originalWidth*Math.min((timelineView.height/divisor)/(originalWidth*proportionalHeight), 1))
+    width: parent.width
+    height: width*proportionalHeight
 
     Image {
         id: blurhash_
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
index 5d98784177b02845f211a677740116026ace2638..3210128a0c3226167691d23230226d9cde474562 100644
--- a/resources/qml/delegates/MessageDelegate.qml
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -35,9 +35,8 @@ Item {
     required property string callType
     required property int encryptionError
     required property int relatedEventCacheBuster
-    property int maxWidth
 
-    Layout.preferredHeight: chooser.child ? chooser.child.height : Nheko.paddingLarge
+    height: chooser.child ? chooser.child.height : Nheko.paddingLarge
 
     DelegateChooser {
         id: chooser
@@ -46,8 +45,7 @@ Item {
         roleValue: type
         //anchors.fill: parent
 
-        anchors.left: parent.left
-        anchors.right: parent.right
+        width: parent.width? parent.width: 0 // this should get rid of "cannot read property 'width' of null"
 
         DelegateChoice {
             roleValue: MtxEvent.UnknownMessage
@@ -111,7 +109,6 @@ Item {
                 filename: d.filename
                 isReply: d.isReply
                 eventId: d.eventId
-                maxWidth: d.maxWidth
             }
 
         }
@@ -129,7 +126,6 @@ Item {
                 filename: d.filename
                 isReply: d.isReply
                 eventId: d.eventId
-                maxWidth: d.maxWidth
             }
 
         }
@@ -141,7 +137,6 @@ Item {
                 eventId: d.eventId
                 filename: d.filename
                 filesize: d.filesize
-                maxWidth: d.maxWidth
             }
 
         }
@@ -158,7 +153,6 @@ Item {
                 url: d.url
                 body: d.body
                 filesize: d.filesize
-                maxWidth: d.maxWidth
             }
 
         }
@@ -175,7 +169,6 @@ Item {
                 url: d.url
                 body: d.body
                 filesize: d.filesize
-                maxWidth: d.maxWidth
             }
 
         }
@@ -184,7 +177,7 @@ Item {
             roleValue: MtxEvent.Redacted
 
             Redacted {
-                delegateWidth: d.width
+                //delegateWidth: d.width
             }
         }
 
diff --git a/resources/qml/delegates/Pill.qml b/resources/qml/delegates/Pill.qml
index bfc18b9a23eb26e77c9be7f72863541c970413df..a3964f73e23aaf8f6846dac252c5d756247cc22f 100644
--- a/resources/qml/delegates/Pill.qml
+++ b/resources/qml/delegates/Pill.qml
@@ -11,8 +11,8 @@ Label {
     property bool isStateEvent
     color: Nheko.colors.text
     horizontalAlignment: Text.AlignHCenter
-    height: contentHeight * 1.2
-    width: contentWidth * 1.2
+    //height: contentHeight * 1.2
+    //width: contentWidth * 1.2
     font.pointSize: isStateEvent? 0.75*fontMetrics.font.pointSize : 1*fontMetrics.font.pointSize
 
     background: Rectangle {
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index 805eafbfcc71c728d94d99e1ff6075b00583df7b..9324cdaf6c8babd2147eb3e811b1d2c289e34221 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -22,16 +22,11 @@ Item {
     required property string url
     required property string body
     required property string filesize
-    property int maxWidth
-    property double tempWidth: Math.min(maxWidth, originalWidth < 1 ? 400 : originalWidth)
-    property double tempHeight: tempWidth * proportionalHeight
     property double divisor: isReply ? 4 : 2
-    property bool tooHigh: tempHeight > timelineRoot.height / divisor
-
-    height: (type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80) + fileInfoLabel.height
-    width: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250
+    implicitWidth: type == MtxEvent.VideoMessage ? Math.round(originalWidth*Math.min((timelineView.height/divisor)/(originalWidth*proportionalHeight), 1)) : 500
+    width: parent.width
+    height: (type == MtxEvent.VideoMessage ? width*proportionalHeight : 80) + fileInfoLabel.height
     implicitHeight: height
-    implicitWidth: width
 
     MxcMedia {
         id: mxcmedia
diff --git a/resources/qml/delegates/Redacted.qml b/resources/qml/delegates/Redacted.qml
index b0c9abfef3607944e7a8538c2cbb7bb71db06b12..cf7239888df98b7e44f1e2c63042e23782ca07c5 100644
--- a/resources/qml/delegates/Redacted.qml
+++ b/resources/qml/delegates/Redacted.qml
@@ -10,10 +10,10 @@ import im.nheko 1.0
 
 Rectangle{
 
-    required property real delegateWidth
+    //required property real delegateWidth
     height: redactedLayout.implicitHeight + Nheko.paddingSmall
-    width: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
-    implicitWidth: width
+    implicitWidth: redactedLayout.implicitWidth + 2 * Nheko.paddingMedium
+    //implicitWidth: width
     radius: fontMetrics.lineSpacing / 2 + 2 * Nheko.paddingSmall
     color: Nheko.colors.alternateBase
 
@@ -33,8 +33,9 @@ Rectangle{
             id: redactedLabel
             Layout.margins: 0
             Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
-            Layout.fillWidth: true
-            Layout.maximumWidth: delegateWidth - 4 * Nheko.paddingSmall - trashImg.width - 2 * Nheko.paddingMedium
+            Layout.preferredWidth: implicitWidth
+            //Layout.fillWidth: true
+            //Layout.maximumWidth: delegateWidth - 4 * Nheko.paddingSmall - trashImg.width - 2 * Nheko.paddingMedium
             property var redactedPair: room.formatRedactedEvent(eventId)
             text: redactedPair["first"]
             wrapMode: Label.WordWrap
diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml
index 28f0dd21973d9d023fcb0dae77b66b678923d036..c60867de4b1ab90982495b23b9b6c027033ae464 100644
--- a/resources/qml/delegates/Reply.qml
+++ b/resources/qml/delegates/Reply.qml
@@ -37,8 +37,8 @@ Item {
     property int relatedEventCacheBuster
     property int maxWidth
 
-    Layout.preferredHeight: replyContainer.height
     height: replyContainer.height
+    implicitHeight: replyContainer.height
     implicitWidth: visible? colorLine.width+replyContainer.implicitWidth : 0
 
     CursorShape {
@@ -99,6 +99,7 @@ Item {
 
         MessageDelegate {
             Layout.leftMargin: 4
+            Layout.preferredHeight: height
             id: reply
             blurhash: r.blurhash
             body: r.body
@@ -125,7 +126,6 @@ Item {
             enabled: false
             Layout.fillWidth: true
             isReply: true
-            maxWidth: r.maxWidth-4
         }
 
     }