diff --git a/src/app.rs b/src/app.rs
index 8d694ef..ca989d5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -20,7 +20,7 @@ impl History {
 
 	fn down(&mut self, x: u16) {
 		if self.count < self.height { return };
-		let delta = self.count - self.height;
+		let delta = self.count - (self.height/2);
 		if self.offset >= delta { return }
 		self.offset += std::cmp::min(x, delta - self.offset);
 	}
@@ -103,12 +103,17 @@ pub async fn run<T: ratatui::backend::Backend>(term: &mut Terminal<T>, args: cra
 		let tl_input_len = ((tl_input.len() as u16 + 3) / history.width) + 1;
 		let tl_header = args.server.clone();
 		// TODO only render bottom lines
-		let tl_text = history
+		let mut tl_text_vec = history
 			.lines
 			.iter()
 			.map(|x| format!("{x}"))
-			.collect::<Vec<String>>()
-			.join("\n");
+			.collect::<Vec<String>>();
+
+		// two empty lines because my math is wrong and im lazy rn
+		tl_text_vec.push("".to_string());
+		tl_text_vec.push("".to_string());
+
+		let tl_text = tl_text_vec.join("\n");
 
 		term.draw(|frame| {
 			let layout = Layout::default()
diff --git a/src/chat.rs b/src/chat.rs
index 8a11ab4..647d57c 100644
--- a/src/chat.rs
+++ b/src/chat.rs
@@ -27,16 +27,20 @@ pub enum ChatLine {
 		body: String,
 	},
 	Error(String),
+	Rename(String, String),
 	Join(String),
 	Leave(String),
 	Connect(String),
+	Raw(String),
 }
 
 impl Display for ChatEvent {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 		let time = self.0.format("%H:%M:%S");
 		match &self.1 {
+			ChatLine::Raw(s) => write!(f, "{time} {s}"),
 			ChatLine::Error(err) => write!(f, "{time} /!\\ {err}"),
+			ChatLine::Rename(before, after) => write!(f, "{time} user <{before}> renamed to <{after}>"),
 			ChatLine::Join(user) => write!(f, "{time} [{user} joined]"),
 			ChatLine::Leave(user) => write!(f, "{time} [{user} left]"),
 			ChatLine::Connect(user) => write!(f, "{time} [{user} connected]"),
@@ -110,16 +114,23 @@ impl Chat {
 					Message::Ping(_) | tokio_tungstenite::tungstenite::Message::Pong(_) => {}, // ignore
 					Message::Text(payload) => for line in payload.lines() {
 						match serde_json::from_str::<Event>(line) {
-							Err(e) => chan.send(ChatEvent(chrono::Utc::now(), ChatLine::Error(format!("failed parsing message: {e}"))))?,
+							Err(e) => {
+								chan.send(ChatEvent(chrono::Utc::now(), ChatLine::Error(format!("failed parsing message: {e}"))))?;
+								chan.send(ChatEvent(chrono::Utc::now(), ChatLine::Raw(line.to_string())))?;
+							},
 							Ok(event) => match event.inner {
-								EventInner::Chat { body, visible: _ } =>
-									chan.send(ChatEvent(event.timestamp, ChatLine::Message { user: event.user.display_name, body }))?,
-								EventInner::ConnectedUserInfo =>
-									chan.send(ChatEvent(event.timestamp, ChatLine::Connect(event.user.display_name)))?,
-								EventInner::UserJoined =>
-									chan.send(ChatEvent(event.timestamp, ChatLine::Join(event.user.display_name)))?,
-								EventInner::UserParted =>
-									chan.send(ChatEvent(event.timestamp, ChatLine::Leave(event.user.display_name)))?,
+								EventInner::Chat { body, user, visible } =>
+									chan.send(ChatEvent(event.timestamp, if visible { ChatLine::Message { user: user.display_name, body } } else { ChatLine::Raw("| REDACTED |".into()) }))?,
+								EventInner::ConnectedUserInfo { user } =>
+									chan.send(ChatEvent(event.timestamp, ChatLine::Connect(user.display_name)))?,
+								EventInner::UserJoined { user } =>
+									chan.send(ChatEvent(event.timestamp, ChatLine::Join(user.display_name)))?,
+								EventInner::UserParted { user } =>
+									chan.send(ChatEvent(event.timestamp, ChatLine::Leave(user.display_name)))?,
+								EventInner::NameChange { old_name, user } =>
+									chan.send(ChatEvent(event.timestamp, ChatLine::Rename(old_name, user.display_name)))?,
+								EventInner::ChatAction { body } =>
+									chan.send(ChatEvent(event.timestamp, ChatLine::Raw(body)))?,
 							}
 						}
 					},
diff --git a/src/proto.rs b/src/proto.rs
index a64efb6..97230ae 100644
--- a/src/proto.rs
+++ b/src/proto.rs
@@ -33,26 +33,41 @@ pub mod ws {
 	pub struct Event {
 		pub id: String,
 		pub timestamp: chrono::DateTime<chrono::Utc>,
-		pub user: User,
 		#[serde(flatten)]
 		pub inner: EventInner,
 	}
 	
 	// TODO can i avoid repeating id,timestamp,user in each msg type??
 	#[derive(Debug, serde::Deserialize)]
-	#[serde(tag = "type")]
+	#[serde(tag = "type", rename_all = "camelCase")]
 	pub enum EventInner {
 		#[serde(rename = "CHAT")]
 		Chat {
 			body: String,
 			visible: bool,
+			user: User,
 		},
 		#[serde(rename = "CONNECTED_USER_INFO")]
-		ConnectedUserInfo,
+		ConnectedUserInfo {
+			user: User,
+		},
 		#[serde(rename = "USER_JOINED")]
-		UserJoined,
+		UserJoined {
+			user: User,
+		},
 		#[serde(rename = "USER_PARTED")]
-		UserParted,
+		UserParted {
+			user: User,
+		},
+		#[serde(rename = "NAME_CHANGE")]
+		NameChange {
+			old_name: String,
+			user: User,
+		},
+		#[serde(rename = "CHAT_ACTION")]
+		ChatAction {
+			body: String,
+		}
 	}
 	
 	#[derive(Debug, serde::Deserialize)]