chore: redesign threads as a linear phpBB-style forum view #300

Merged
hodlbod merged 1 commits from userAdityaa/flotilla:243-redesign-threads into dev 2026-06-17 16:08:59 +00:00
Collaborator

Summary

Implements the approved thread redesign from #243. The thread detail view replaces stacked cards with a flat, phpBB-style layout: each post shows profile info, OP badges, per-post replies, reactions/actions, and #post-N permalinks, with pagination for longer threads. The header keeps the title, reply count, and room visible while scrolling. The thread list groups topics by room as boards, with uncategorized threads under General.

Screenshots of the changes

Screenshot 2026-06-10 at 2.44.29 PM.pngScreenshot 2026-06-10 at 2.43.57 PM.png
localhost_1847_spaces_meta.spaces.coracle.social_threads(iPhone 14 Pro Max) (1).png
localhost_1847_spaces_meta.spaces.coracle.social_threads(iPhone 14 Pro Max) (2).png

### Summary Implements the approved thread redesign from #243. The thread detail view replaces stacked cards with a flat, phpBB-style layout: each post shows profile info, OP badges, per-post replies, reactions/actions, and #post-N permalinks, with pagination for longer threads. The header keeps the title, reply count, and room visible while scrolling. The thread list groups topics by room as boards, with uncategorized threads under General. ### Screenshots of the changes <img width="1440" alt="Screenshot 2026-06-10 at 2.44.29 PM.png" src="attachments/800a405e-0cac-4c58-9b33-7b68fc8aca3c"><img width="1440" alt="Screenshot 2026-06-10 at 2.43.57 PM.png" src="attachments/2142c19a-1fd0-4983-89c9-45772a386353"> ![localhost_1847_spaces_meta.spaces.coracle.social_threads(iPhone 14 Pro Max) (1).png](/attachments/7c47a9ed-23c9-4c39-9c55-70907ee9ea5f) ![localhost_1847_spaces_meta.spaces.coracle.social_threads(iPhone 14 Pro Max) (2).png](/attachments/fa3f5c40-e1a4-4f29-b312-6aee2f44368b)
userAdityaa force-pushed 243-redesign-threads from d74d07ab39 to fe9fef60b5 2026-06-10 09:19:31 +00:00 Compare
Author
Collaborator

@hodlbod, could you please take a look at the design for now? I'll clean up and refactor the code afterward 🙂

@hodlbod, could you please take a look at the design for now? I'll clean up and refactor the code afterward 🙂
Owner

I think the mockups look great, let me know when the code is ready to review.

I think the mockups look great, let me know when the code is ready to review.
userAdityaa force-pushed 243-redesign-threads from fe9fef60b5 to 71071b54c6 2026-06-10 21:42:52 +00:00 Compare
Author
Collaborator

@hodlbod, this should be ready for an initial review now.

@hodlbod, this should be ready for an initial review now.
hodlbod reviewed 2026-06-10 22:29:47 +00:00
@@ -23,0 +25,4 @@
leading,
title,
action,
hideRelay = false,
Owner

What's the reasoning behind this? I think having the relay visible is probably still important context

What's the reasoning behind this? I think having the relay visible is probably still important context
Author
Collaborator

hideRelay was only to avoid a cramped/tall mobile header after adding the two-line thread title (title + reply count/room).

but yes, we have to keep the relay context, I shall revert it back.

`hideRelay` was only to avoid a cramped/tall mobile header after adding the two-line thread title (title + reply count/room). but yes, we have to keep the relay context, I shall revert it back.
Author
Collaborator

Looks like this:
Screenshot 2026-06-12 at 9.04.42 PM.png

Looks like this: <img width="322" alt="Screenshot 2026-06-12 at 9.04.42 PM.png" src="attachments/c622ec6f-2d88-4f25-8d03-ca4b8a7be202">
Owner

Yeah, that is kind of cramped. What about meta.spaces.coracle.social * #Development * 4 replies? But that wouldn't work well on mobile. Hmmm. We can punt it for later, but let's align the space icon to the top.

Yeah, that is kind of cramped. What about `meta.spaces.coracle.social * #Development * 4 replies`? But that wouldn't work well on mobile. Hmmm. We can punt it for later, but let's align the space icon to the top.
Author
Collaborator

sure, here it is:
Screenshot 2026-06-14 at 10.12.34 PM.png

sure, here it is: <img width="145" alt="Screenshot 2026-06-14 at 10.12.34 PM.png" src="attachments/314285e8-e872-437b-9b14-6d3501d270ba">
@@ -0,0 +16,4 @@
lastActive: Map<string, number>
}
const {url, h, threads, replyCounts, lastActive}: Props = $props()
Owner

Instead of threading replyCounts and lastActive through, create a new ThreadBoardItem which calculates them locally. You can derive stuff from the repository using one of the deriveEvents utilities with an E tag. This shouldn't cause performance problems compared to iterating over all comments.

Instead of threading replyCounts and lastActive through, create a new ThreadBoardItem which calculates them locally. You can derive stuff from the repository using one of the `deriveEvents` utilities with an `E` tag. This shouldn't cause performance problems compared to iterating over all comments.
hodlbod marked this conversation as resolved
@@ -0,0 +28,4 @@
const {url, threadId, event, number, threadPubkey, onReply}: Props = $props()
const relays = removeUndefined([url])
Owner

url should never be undefined right?

url should never be undefined right?
hodlbod marked this conversation as resolved
@@ -0,0 +38,4 @@
const copyPermalink = () => {
const path = makeSpacePath(url, "threads", threadId)
const link = `${window.location.origin}${path}#post-${number}`
Owner

origin=localhost on android, use PLATFORM_URL

origin=localhost on android, use PLATFORM_URL
Owner

Also, let's do base/nevent. We can then rely on goToEvent in routes.ts. This will be easier for other clients to parse as well if they want to.

Also, let's do `base/nevent`. We can then rely on goToEvent in routes.ts. This will be easier for other clients to parse as well if they want to.
hodlbod marked this conversation as resolved
@@ -45,0 +75,4 @@
if (aOrder !== bOrder) return aOrder - bOrder
return a.localeCompare(b)
})
Owner

sortBy?

`sortBy`?
userAdityaa force-pushed 243-redesign-threads from 71071b54c6 to ae209615d9 2026-06-12 15:35:52 +00:00 Compare
userAdityaa force-pushed 243-redesign-threads from ae209615d9 to d3abc817d4 2026-06-14 16:43:37 +00:00 Compare
hodlbod reviewed 2026-06-15 18:26:08 +00:00
@@ -37,3 +37,3 @@
{@render leading?.()}
</div>
{@render title?.()}
<div class="min-w-0 leading-none">
Owner

leading-none clips letters than hang down, like g

leading-none clips letters than hang down, like `g`
hodlbod marked this conversation as resolved
@@ -0,0 +73,4 @@
class="flex flex-wrap items-center justify-between gap-2 border-b border-base-content/10 bg-base-200/40 px-3 py-2 text-xs sm:px-4 sm:text-sm">
<span class="opacity-75">{formatTimestamp(event.created_at)}</span>
<Button class="btn btn-ghost btn-xs h-auto min-h-0 gap-1 px-1 py-0" onclick={copyPermalink}>
Post #{number}
Owner

Numbered posts won't be reliable because events can always get backdated or republished. We should just change this to [link icon] permalink or something.

Numbered posts won't be reliable because events can always get backdated or republished. We should just change this to `[link icon] permalink` or something.
hodlbod marked this conversation as resolved
@@ -45,0 +50,4 @@
const roomThreads = byRoom.get(h) || []
roomThreads.push(event)
byRoom.set(h, roomThreads)
Owner

You can do pushToMapKey(byRoom, h, event)

You can do `pushToMapKey(byRoom, h, event)`
Owner

Or even const byRoom = groupBy(e => getTagValue("h", e.tags))

Or even `const byRoom = groupBy(e => getTagValue("h", e.tags))`
hodlbod marked this conversation as resolved
@@ -45,0 +53,4 @@
byRoom.set(h, roomThreads)
}
const roomOrder = new Map(($roomsByUrl.get(url) || []).map((room, index) => [room.h, index]))
Owner

This seems like it would vary. Why not sort lexically by room name?

This seems like it would vary. Why not sort lexically by room name?
@@ -34,0 +70,4 @@
const search = params.toString()
goto(`${$page.url.pathname}${search ? `?${search}` : ""}`, {
replaceState: true,
Owner

I feel like we should not replace state, but I'm not sure.

I feel like we should not replace state, but I'm not sure.
hodlbod marked this conversation as resolved
@@ -34,2 +79,4 @@
const openReply = (post: TrustedEvent) => {
replyTo = post
showReply = true
}
Owner

Similar to how chat replies work, let's prepend the compose input with a quote of the event the user clicked reply on.

Similar to how chat replies work, let's prepend the compose input with a quote of the event the user clicked reply on.
hodlbod marked this conversation as resolved
@@ -93,0 +160,4 @@
<ThreadPagination page={currentPage} {pageCount} onPage={setPage} />
{/if}
{#if showReply && replyTo}
<EventReply {url} event={replyTo} onClose={closeReply} onSubmit={closeReply} />
Owner

This actually changes how threads work and doesn't follow the spec. Replies must always refer to the root kind 11, not the comments. Replies to comments should be done the same way as chat replies — use q and embed the parent in the quote reply. This matches how old forums used to work too.

This actually changes how threads work and doesn't follow the [spec](https://github.com/nostr-protocol/nips/blob/master/7D.md). Replies must always refer to the root `kind 11`, not the comments. Replies to comments should be done the same way as chat replies — use `q` and embed the parent in the quote reply. This matches how old forums used to work too.
hodlbod marked this conversation as resolved
userAdityaa force-pushed 243-redesign-threads from d3abc817d4 to cf238c6bff 2026-06-16 05:19:05 +00:00 Compare
userAdityaa force-pushed 243-redesign-threads from cf238c6bff to f085b6a510 2026-06-16 05:22:46 +00:00 Compare
hodlbod reviewed 2026-06-16 23:04:52 +00:00
@@ -47,0 +133,4 @@
setPage(targetPage)
}
setTimeout(() => goToEvent(posts[index]!), 100)
Owner

I think this could be scrollToEvent instead of goToEvent since we're handling the routing here.

I think this could be `scrollToEvent` instead of `goToEvent` since we're handling the routing here.
userAdityaa force-pushed 243-redesign-threads from f085b6a510 to 823b2c65f3 2026-06-17 09:42:08 +00:00 Compare
hodlbod added 1 commit 2026-06-17 16:08:51 +00:00
hodlbod force-pushed 243-redesign-threads from 823b2c65f3 to d595bdeae8 2026-06-17 16:08:51 +00:00 Compare
hodlbod merged commit deb2b31466 into dev 2026-06-17 16:08:59 +00:00
hodlbod deleted branch 243-redesign-threads 2026-06-17 16:08:59 +00:00
Sign in to join this conversation.