diff --git a/assets/pasted-20260220-222757-27e58242.png b/assets/pasted-20260220-222757-27e58242.png
new file mode 100644
index 0000000..e84f9bb
Binary files /dev/null and b/assets/pasted-20260220-222757-27e58242.png differ
diff --git a/assets/pasted-20260220-224107-f358f1b7.png b/assets/pasted-20260220-224107-f358f1b7.png
new file mode 100644
index 0000000..cf8f196
Binary files /dev/null and b/assets/pasted-20260220-224107-f358f1b7.png differ
diff --git a/assets/pasted-20260220-230123-feffa54b.png b/assets/pasted-20260220-230123-feffa54b.png
new file mode 100644
index 0000000..80c7b00
Binary files /dev/null and b/assets/pasted-20260220-230123-feffa54b.png differ
diff --git a/check_divs.php b/check_divs.php
new file mode 100644
index 0000000..3ef77f2
--- /dev/null
+++ b/check_divs.php
@@ -0,0 +1,19 @@
+ $line) {
+ $line_open = substr_count($line, '
= 760 && ($i + 1) <= 780) {
+ echo "Line " . ($i + 1) . ": Depth $old_depth -> $depth | " . trim($line) . "\n";
+ }
+}
+echo "Final depth: $depth\n";
diff --git a/check_tags.py b/check_tags.py
new file mode 100644
index 0000000..d95bf77
--- /dev/null
+++ b/check_tags.py
@@ -0,0 +1,27 @@
+
+import sys
+
+def check_html_balance(filename):
+ with open(filename, 'r') as f:
+ content = f.read()
+
+ # Simple regex to find tags, ignoring PHP for now
+ import re
+ tags = re.findall(r'<(/?div|/?span|/?form|/?section|/?article|/?aside|/?header|/?footer|/?nav|/?main)', content)
+
+ stack = []
+ for i, tag in enumerate(tags):
+ if tag.startswith('/'):
+ if not stack:
+ print(f"Error: Closing tag {tag} with no opening tag at index {i}")
+ else:
+ stack.pop()
+ else:
+ stack.append(tag)
+
+ if stack:
+ print(f"Error: Unbalanced tags remaining: {stack}")
+ else:
+ print("Tags are balanced (excluding potential PHP issues)")
+
+check_html_balance('index.php')
diff --git a/db/migrations/20260220_create_poll_votes.sql b/db/migrations/20260220_create_poll_votes.sql
new file mode 100644
index 0000000..af29308
--- /dev/null
+++ b/db/migrations/20260220_create_poll_votes.sql
@@ -0,0 +1,12 @@
+-- Migration to add poll votes table
+CREATE TABLE IF NOT EXISTS `poll_votes` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `message_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `option_index` int(11) NOT NULL,
+ `created_at` timestamp NULL DEFAULT current_timestamp(),
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_poll_option` (`message_id`,`user_id`,`option_index`),
+ CONSTRAINT `poll_votes_ibfk_1` FOREIGN KEY (`message_id`) REFERENCES `messages` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `poll_votes_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/fix_index.py b/fix_index.py
new file mode 100644
index 0000000..1456fd1
--- /dev/null
+++ b/fix_index.py
@@ -0,0 +1,265 @@
+import sys
+
+with open('index.php', 'r') as f:
+ lines = f.readlines()
+
+start_marker = "" in line and i > 1200: # around there
+ else_line = i
+ break
+
+if not else_line:
+ print("Error: Could not find the final else line")
+ sys.exit(1)
+
+# Now find the first foreach after this else
+start_line = 0
+for i in range(else_line, len(lines)):
+ if start_marker in lines[i]:
+ start_line = i
+ break
+
+if not start_line:
+ print("Error: Could not find start marker after else")
+ sys.exit(1)
+
+# Now find the end of this block. It should end with
+# But I might have multiple ones now.
+# We want to find the one that is followed by (closing the main if)
+# and then
(closing chat-container or messages-list)
+
+end_line = 0
+for i in range(start_line, len(lines)):
+ if "" in lines[i]:
+ # Check if next line (or soon after) is
+ is_real_end = False
+ for j in range(i+1, min(i+10, len(lines))):
+ if "" in lines[j]:
+ is_real_end = True
+ end_line = j
+ break
+ if is_real_end:
+ break
+
+if not end_line:
+ print("Error: Could not find end marker")
+ sys.exit(1)
+
+print(f"Replacing lines {start_line+1} to {end_line+1}")
+
+new_block = """
+
+ prepare("SELECT emoji, COUNT(*) as count, GROUP_CONCAT(user_id) as users FROM message_reactions WHERE message_id = ? GROUP BY emoji");
+ $stmt_react->execute([$m['id']]);
+ $reactions = $stmt_react->fetchAll();
+ foreach ($reactions as $r):
+ $reacted = in_array($current_user_id, explode(',', $r['users']));
+ ?>
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+# Adjust end_line to be the last endif before the start of next section
+# The original file has at line 1486 in my previous read.
+# Let's verify.
+
+with open('index.php', 'w') as f:
+ f.writelines(lines[:start_line])
+ f.write(new_block)
+ f.write("\n")
+ f.writelines(lines[end_line+1:])
+
diff --git a/fix_index_v2.py b/fix_index_v2.py
new file mode 100644
index 0000000..4cabbc2
--- /dev/null
+++ b/fix_index_v2.py
@@ -0,0 +1,222 @@
+import sys
+
+with open('index.php', 'r') as f:
+ lines = f.readlines()
+
+# Section starts after line 1294 (index 1294)
+# Section ends before chat-input-container (Line 1749 -> index 1748)
+start_index = 1294
+end_index = 0
+for i, line in enumerate(lines):
+ if '
' in line:
+ end_index = i
+ break
+
+if not end_index:
+ print("Error: Could not find end marker")
+ sys.exit(1)
+
+print(f"Replacing lines {start_index+1} to {end_index}")
+
+new_content = """
+
+ prepare("SELECT emoji, COUNT(*) as count, GROUP_CONCAT(user_id) as users FROM message_reactions WHERE message_id = ? GROUP BY emoji");
+ $stmt_react->execute([$m['id']]);
+ $reactions = $stmt_react->fetchAll();
+ foreach ($reactions as $r):
+ $reacted = in_array($current_user_id, explode(',', $r['users']));
+ ?>
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+with open('index.php', 'w') as f:
+ f.writelines(lines[:start_index])
+ f.write(new_content)
+ f.write("\n")
+ f.writelines(lines[end_index:])
+
diff --git a/fix_index_v3.py b/fix_index_v3.py
new file mode 100644
index 0000000..5b06a39
--- /dev/null
+++ b/fix_index_v3.py
@@ -0,0 +1,815 @@
+import sys
+import re
+
+with open('index.php', 'r') as f:
+ content = f.read()
+
+# First, let's restore the thread loop (the first one)
+thread_loop_pattern = r'<\?php foreach\(\$messages as \$m\):.*?\<\?php endforeach; \?\>'
+# We need a non-greedy regex that handles nested PHP tags
+# Actually, it's easier to find the markers.
+
+# Let's find the first foreach($messages as $m)
+# and the first endforeach after it.
+
+# I'll just use a simpler approach:
+# Find everything between
+# and
+# and replace it with a clean version of both loops.
+
+start_marker = '
'
+end_marker = ''
+
+start_pos = content.find(start_marker)
+end_pos = content.find(end_marker)
+
+if start_pos == -1 or end_pos == -1:
+ print("Error: Could not find markers")
+ sys.exit(1)
+
+# Now we need to keep the structure between those markers but clean it.
+# The structure is:
+# if($active_thread):
+# ...
+# foreach($messages as $m): (thread loop)
+# ...
+# endforeach
+# ...
+# elseif($channel_type === 'event'):
+# ...
+# else: (normal chat)
+# ...
+# foreach($messages as $m): (normal loop)
+# ...
+# endforeach
+# endif
+
+clean_inner = """
+
+