Threads live in the messages table
A reply has everything a message has — an author, reactions, attachments, edits — so a separate table would duplicate all of it and then need a union on every channel read. One nullable thread_id costs a column and makes 'show this thread' an index lookup. The price is that queries must remember to filter thread_id is null for the channel view, which is exactly the bug people hit once and then never again.