From 4bfa8215afd77a07dd0ed444dcb018371d70025a Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 28 Oct 2026 16:01:52 +0100 Subject: [PATCH 1/7] fix: realm: Make functions private that return a pointer to a state object. Avoid cross-realm modification Signed-off-by: Jeff Thompson --- realm/post.gno | 8 ++++---- realm/public.gno | 45 +++++++++++++++++++++---------------------- realm/social_test.gno | 12 ++++++------ realm/userposts.gno | 6 +++--- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/realm/post.gno b/realm/post.gno index eeb1b2e..49c839d 100644 --- a/realm/post.gno +++ b/realm/post.gno @@ -71,7 +71,7 @@ func (post *Post) GetPostID() PostID { return post.id } -func (post *Post) AddReply(creator address, body string) *Post { +func (post *Post) addReply(creator address, body string) *Post { userPosts := post.userPosts pid := userPosts.incGetPostID() pidkey := postIDKey(pid) @@ -86,7 +86,7 @@ func (post *Post) AddReply(creator address, body string) *Post { return reply } -func (post *Post) AddRepostTo(creator address, comment string, dst *UserPosts) *Post { +func (post *Post) addRepostTo(creator address, comment string, dst *UserPosts) *Post { if !post.IsThread() { panic("cannot repost non-thread post") } @@ -116,7 +116,7 @@ func (post *Post) GetReply(pid PostID) *Post { // If userAddr is already added, do nothing. // If the userAddr is the post's creator, do nothing. (Don't react to one's own posts.) // Return a boolean indicating whether the userAddr was added (false if it was already added). -func (post *Post) AddReaction(userAddr address, reaction Reaction) bool { +func (post *Post) addReaction(userAddr address, reaction Reaction) bool { validateReaction(reaction) if userAddr == post.creator { @@ -136,7 +136,7 @@ func (post *Post) AddReaction(userAddr address, reaction Reaction) bool { // Remove the userAddr from the posts.reactions for reaction. // If userAddr is already removed, do nothing. // Return a boolean indicating whether the userAddr was found and removed. -func (post *Post) RemoveReaction(userAddr address, reaction Reaction) bool { +func (post *Post) removeReaction(userAddr address, reaction Reaction) bool { validateReaction(reaction) if !post.reactions.Has(reactionKey(reaction)) { diff --git a/realm/public.gno b/realm/public.gno index c4a950f..ad1c7eb 100644 --- a/realm/public.gno +++ b/realm/public.gno @@ -20,7 +20,7 @@ type UserAndPostID struct { func PostMessage(cur realm, body string) PostID { caller := cur.Previous().Address() userPosts := getOrCreateUserPosts(caller, usernameOf(caller)) - thread := userPosts.AddThread(body) + thread := userPosts.addThread(body) return thread.id } @@ -41,14 +41,14 @@ func PostReply(cur realm, userPostsAddr address, threadid, postid PostID, body s panic("threadid in user posts does not exist") } if postid == threadid { - reply := thread.AddReply(caller, body) + reply := thread.addReply(caller, body) return reply.id } else { post := thread.GetReply(postid) if post == nil { panic("postid does not exist") } - reply := post.AddReply(caller, body) + reply := post.addReply(caller, body) return reply.id } } @@ -73,7 +73,7 @@ func RepostThread(cur realm, userPostsAddr address, threadid PostID, comment str if thread == nil { panic("threadid in user posts does not exist") } - repost := thread.AddRepostTo(caller, comment, dstUserPosts) + repost := thread.addRepostTo(caller, comment, dstUserPosts) return repost.id } @@ -119,7 +119,7 @@ func GetJsonTopPostsByID(addrAndIDs []UserAndPostID) string { // If threadID is X and replyID is 0, then return the posts (without replies) in that thread. (Like render args "user/2".) // If threadID is X and replyID is Y, then return the posts in the thread starting with replyID. (Like render args "user/2/5".) // The response includes reposts by this user (only if threadID is 0), but not messages of other -// users that are being followed. (See GetHomePosts.) The response is a JSON string. +// users that are being followed. (See GetJsonHomePosts.) The response is a JSON string. func GetThreadPosts(userPostsAddr address, threadID int, replyID int, startIndex int, endIndex int) string { userPosts := getUserPosts(userPostsAddr) if userPosts == nil { @@ -160,22 +160,21 @@ func RefreshHomePosts(_ realm, userPostsAddr address) int { return userPosts.homePosts.Size() } -// Get the number of posts which GetHomePosts or GetJsonHomePosts will return. +// Get the number of posts which GetJsonHomePosts will return. // The result is something like "(12 int)". // This returns the current count of the home posts (without need to pay gas). To include the // latest followed posts, call RefreshHomePosts. func GetHomePostsCount(userPostsAddr address) int { - return GetHomePosts(userPostsAddr).Size() + return getHomePosts(userPostsAddr).Size() } // Get home posts for a user, which are the user's top-level posts plus all posts of all // users being followed. // The response is a map of postID -> *Post. The bptree.BPTree sorts by the post ID which is // unique for every post and increases in time. -// If you just want the total count, use GetHomePostsCount. -// This returns the current state of the home posts (without need to pay gas). To include the -// latest followed posts, call RefreshHomePosts. -func GetHomePosts(userPostsAddr address) *bptree.BPTree { +// This must not be exported. Another realm holding this pointer could mutate the tree (and +// the posts in it) with this realm's authority. +func getHomePosts(userPostsAddr address) *bptree.BPTree { userPosts := getUserPosts(userPostsAddr) if userPosts == nil { panic("posts for userPostsAddr do not exist") @@ -183,7 +182,7 @@ func GetHomePosts(userPostsAddr address) *bptree.BPTree { return &userPosts.homePosts } -// Get home posts for a user (using GetHomePosts), which are the user's top-level posts plus all +// Get home posts for a user, which are the user's top-level posts plus all // posts of all users being followed. // Limit the response to posts from startIndex up to (not including) endIndex within the home posts. // If you just want the total count, use GetHomePostsCount. @@ -191,7 +190,7 @@ func GetHomePosts(userPostsAddr address) *bptree.BPTree { // This returns the current state of the home posts (without need to pay gas). To include the // latest posts, call RefreshHomePosts. func GetJsonHomePosts(userPostsAddr address, startIndex int, endIndex int) string { - allPosts := GetHomePosts(userPostsAddr) + allPosts := getHomePosts(userPostsAddr) postsJson := "" for i := startIndex; i < endIndex && i < allPosts.Size(); i++ { _, postI := allPosts.GetByIndex(i) @@ -209,7 +208,7 @@ func GetJsonHomePosts(userPostsAddr address, startIndex int, endIndex int) strin return ufmt.Sprintf("{\"n_posts\": %d, \"posts\": [\n %s]}", allPosts.Size(), postsJson) } -// Update the caller to follow the user with followedAddr. See UserPosts.Follow. +// Update the caller to follow the user with followedAddr. See UserPosts.follow. func Follow(cur realm, followedAddr address) PostID { caller := cur.Previous().Address() if followedAddr == caller { @@ -218,10 +217,10 @@ func Follow(cur realm, followedAddr address) PostID { // A user can follow someone before doing any posts, so create the UserPosts if needed. userPosts := getOrCreateUserPosts(caller, usernameOf(caller)) - return userPosts.Follow(followedAddr) + return userPosts.follow(followedAddr) } -// Update the caller to unfollow the user with followedAddr. See UserPosts.Unfollow. +// Update the caller to unfollow the user with followedAddr. See UserPosts.unfollow. func Unfollow(cur realm, followedAddr address) { caller := cur.Previous().Address() userPosts := getUserPosts(caller) @@ -230,7 +229,7 @@ func Unfollow(cur realm, followedAddr address) { return } - userPosts.Unfollow(followedAddr) + userPosts.unfollow(followedAddr) } // Add the reaction by the caller to the post of userPostsAddr, where threadid is the ID @@ -238,7 +237,7 @@ func Unfollow(cur realm, followedAddr address) { // to a top-level post for the threadid, otherwise add the reaction to the postid "sub reply". // (This function's arguments are similar to PostReply.) // The caller must already be registered with /r/gnoland/users/v1 Register. -// Return a boolean indicating whether the userAddr was added. See Post.AddReaction. +// Return a boolean indicating whether the userAddr was added. See Post.addReaction. func AddReaction(cur realm, userPostsAddr address, threadid, postid PostID, reaction Reaction) bool { caller := cur.Previous().Address() userPosts := getUserPosts(userPostsAddr) @@ -250,13 +249,13 @@ func AddReaction(cur realm, userPostsAddr address, threadid, postid PostID, reac panic("threadid in user posts does not exist") } if postid == threadid { - return thread.AddReaction(caller, reaction) + return thread.addReaction(caller, reaction) } else { post := thread.GetReply(postid) if post == nil { panic("postid does not exist") } - return post.AddReaction(caller, reaction) + return post.addReaction(caller, reaction) } } @@ -265,7 +264,7 @@ func AddReaction(cur realm, userPostsAddr address, threadid, postid PostID, reac // from a top-level post for the threadid, otherwise remove the reaction from the postid "sub reply". // (This function's arguments are similar to PostReply.) // The caller must already be registered with /r/gnoland/users/v1 Register. -// Return a boolean indicating whether the userAddr was removed. See Post.RemoveReaction. +// Return a boolean indicating whether the userAddr was removed. See Post.removeReaction. func RemoveReaction(cur realm, userPostsAddr address, threadid, postid PostID, reaction Reaction) bool { caller := cur.Previous().Address() userPosts := getUserPosts(userPostsAddr) @@ -277,13 +276,13 @@ func RemoveReaction(cur realm, userPostsAddr address, threadid, postid PostID, r panic("threadid in user posts does not exist") } if postid == threadid { - return thread.RemoveReaction(caller, reaction) + return thread.removeReaction(caller, reaction) } else { post := thread.GetReply(postid) if post == nil { panic("postid does not exist") } - return post.RemoveReaction(caller, reaction) + return post.removeReaction(caller, reaction) } } diff --git a/realm/social_test.gno b/realm/social_test.gno index 4b6fec9..10e788c 100644 --- a/realm/social_test.gno +++ b/realm/social_test.gno @@ -14,14 +14,14 @@ func TestPostLookups(t *testing.T) { if userPosts.GetThread(1) != nil { t.Fatal("unexpected thread in an empty user feed") } - thread := userPosts.AddThread("Hello") + thread := userPosts.addThread("Hello") if userPosts.GetThread(thread.id) != thread { t.Fatal("stored thread was not found") } if thread.GetReply(thread.id+1) != nil { t.Fatal("unexpected reply in an empty thread") } - reply := thread.AddReply(addr, "Reply") + reply := thread.addReply(addr, "Reply") if thread.GetReply(reply.id) != reply { t.Fatal("stored reply was not found") } @@ -29,20 +29,20 @@ func TestPostLookups(t *testing.T) { func TestReactionLookups(t *testing.T) { userPosts := newUserPosts("/r/berty/social:alice", address("alice")) - thread := userPosts.AddThread("Hello") + thread := userPosts.addThread("Hello") if thread.GetReactionCount(Gnod) != 0 { t.Fatal("unexpected reactions on a new post") } - if !thread.AddReaction(address("bob"), Gnod) { + if !thread.addReaction(address("bob"), Gnod) { t.Fatal("first reaction was not added") } - if thread.AddReaction(address("bob"), Gnod) { + if thread.addReaction(address("bob"), Gnod) { t.Fatal("duplicate reaction was added") } if thread.GetReactionCount(Gnod) != 1 { t.Fatal("stored reaction was not counted") } - if !thread.RemoveReaction(address("bob"), Gnod) || thread.GetReactionCount(Gnod) != 0 { + if !thread.removeReaction(address("bob"), Gnod) || thread.GetReactionCount(Gnod) != 0 { t.Fatal("reaction was not removed") } } diff --git a/realm/userposts.gno b/realm/userposts.gno index 204c973..e65e439 100644 --- a/realm/userposts.gno +++ b/realm/userposts.gno @@ -58,7 +58,7 @@ func (userPosts *UserPosts) GetThread(pid PostID) *Post { } // Add a new top-level thread to the userPosts. Return the new Post. -func (userPosts *UserPosts) AddThread(body string) *Post { +func (userPosts *UserPosts) addThread(body string) *Post { pid := userPosts.incGetPostID() pidkey := postIDKey(pid) thread := newPost(userPosts, pid, userPosts.userAddr, body, pid, 0, "") @@ -74,7 +74,7 @@ func (userPosts *UserPosts) AddThread(body string) *Post { // If there is no UserPosts for followedAddr, then do nothing and return 0. (We don't expect // this because this is usually called by clicking on the display page of followedAddr.) // Return the value of startedPostsCtr in the added FollowingInfo. -func (userPosts *UserPosts) Follow(followedAddr address) PostID { +func (userPosts *UserPosts) follow(followedAddr address) PostID { if userPosts.following.Has(followedAddr.String()) { // Already following. return PostID(0) @@ -96,7 +96,7 @@ func (userPosts *UserPosts) Follow(followedAddr address) PostID { // Remove followedAddr from following. // If there is a UserPosts for followedAddr, then remove this user from its followers. // If there is no UserPosts for followedAddr, then do nothing. (We don't expect this usually.) -func (userPosts *UserPosts) Unfollow(followedAddr address) { +func (userPosts *UserPosts) unfollow(followedAddr address) { userPosts.following.Remove(followedAddr.String()) followedUserPosts := getUserPosts(followedAddr) From d8f8eb9b543fc3a51bdb4464eec3728c68556d35 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 28 Oct 2026 16:06:14 +0100 Subject: [PATCH 2/7] fix: public.gno: Add checks for cur.IsCurrent() per gno-security-guide.md Signed-off-by: Jeff Thompson --- realm/public.gno | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/realm/public.gno b/realm/public.gno index ad1c7eb..61c2994 100644 --- a/realm/public.gno +++ b/realm/public.gno @@ -18,6 +18,9 @@ type UserAndPostID struct { // Return the "thread ID" of the new post. // (This is similar to boards.CreateThread, but no message title) func PostMessage(cur realm, body string) PostID { + if !cur.IsCurrent() { + panic("spoofed realm") + } caller := cur.Previous().Address() userPosts := getOrCreateUserPosts(caller, usernameOf(caller)) thread := userPosts.addThread(body) @@ -31,6 +34,9 @@ func PostMessage(cur realm, body string) PostID { // Return the new post ID. // (This is similar to boards.CreateReply.) func PostReply(cur realm, userPostsAddr address, threadid, postid PostID, body string) PostID { + if !cur.IsCurrent() { + panic("spoofed realm") + } caller := cur.Previous().Address() userPosts := getUserPosts(userPostsAddr) if userPosts == nil { @@ -58,6 +64,9 @@ func PostReply(cur realm, userPostsAddr address, threadid, postid PostID, body s // Return the new post ID. // (This is similar to boards.CreateRepost.) func RepostThread(cur realm, userPostsAddr address, threadid PostID, comment string) PostID { + if !cur.IsCurrent() { + panic("spoofed realm") + } caller := cur.Previous().Address() if userPostsAddr == caller { panic("Cannot repost a user's own message") @@ -210,6 +219,9 @@ func GetJsonHomePosts(userPostsAddr address, startIndex int, endIndex int) strin // Update the caller to follow the user with followedAddr. See UserPosts.follow. func Follow(cur realm, followedAddr address) PostID { + if !cur.IsCurrent() { + panic("spoofed realm") + } caller := cur.Previous().Address() if followedAddr == caller { panic("you can't follow yourself") @@ -222,6 +234,9 @@ func Follow(cur realm, followedAddr address) PostID { // Update the caller to unfollow the user with followedAddr. See UserPosts.unfollow. func Unfollow(cur realm, followedAddr address) { + if !cur.IsCurrent() { + panic("spoofed realm") + } caller := cur.Previous().Address() userPosts := getUserPosts(caller) if userPosts == nil { @@ -239,6 +254,9 @@ func Unfollow(cur realm, followedAddr address) { // The caller must already be registered with /r/gnoland/users/v1 Register. // Return a boolean indicating whether the userAddr was added. See Post.addReaction. func AddReaction(cur realm, userPostsAddr address, threadid, postid PostID, reaction Reaction) bool { + if !cur.IsCurrent() { + panic("spoofed realm") + } caller := cur.Previous().Address() userPosts := getUserPosts(userPostsAddr) if userPosts == nil { @@ -266,6 +284,9 @@ func AddReaction(cur realm, userPostsAddr address, threadid, postid PostID, reac // The caller must already be registered with /r/gnoland/users/v1 Register. // Return a boolean indicating whether the userAddr was removed. See Post.removeReaction. func RemoveReaction(cur realm, userPostsAddr address, threadid, postid PostID, reaction Reaction) bool { + if !cur.IsCurrent() { + panic("spoofed realm") + } caller := cur.Previous().Address() userPosts := getUserPosts(userPostsAddr) if userPosts == nil { From 4eee79668227a5e3eed7a03b553cd91c54602eae Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 28 Oct 2026 16:09:14 +0100 Subject: [PATCH 3/7] chore: MarshalJSON: Add comment of possible invalid JSON from the post body Signed-off-by: Jeff Thompson --- realm/post.gno | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/realm/post.gno b/realm/post.gno index 49c839d..021e471 100644 --- a/realm/post.gno +++ b/realm/post.gno @@ -294,6 +294,10 @@ func (post *Post) MarshalJSON() ([]byte, error) { if post.repostUser != "" { json.WriteString(ufmt.Sprintf(`, "repost_user": %s`, strconv.Quote(post.repostUser.String()))) } + // TODO: strconv.Quote is not a JSON encoder. For control characters it emits Go escapes + // like \x01, \a and \v which are invalid JSON, so JSON.parse rejects the whole response. + // Since anyone can reply to any thread, one such reply breaks that thread in the mobile + // app. This goes away when we switch to vm/qeval_json. json.WriteString(ufmt.Sprintf(`, "body": %s}`, strconv.Quote(post.body))) return json.Bytes(), nil From 6f625ea47d77cb2e93ad99b40b5aca52b500f09b Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 28 Oct 2026 16:22:58 +0100 Subject: [PATCH 4/7] fix: Render: Use markdown/sanitize for text and paths Signed-off-by: Jeff Thompson --- realm/post.gno | 7 ++++--- realm/render.gno | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/realm/post.gno b/realm/post.gno index 021e471..5d8c93a 100644 --- a/realm/post.gno +++ b/realm/post.gno @@ -7,6 +7,7 @@ import ( "gno.land/p/moul/txlink/v0" "gno.land/p/nt/bptree/v0" + "gno.land/p/nt/markdown/sanitize/v0" "gno.land/p/nt/ufmt/v0" ) @@ -211,10 +212,10 @@ func (post *Post) RenderSummary() string { if thread == nil { return "reposted post does not exist" } - return "Repost: " + post.GetSummary() + "\n\n" + thread.RenderSummary() + return "Repost: " + sanitize.InlineText(post.GetSummary()) + "\n\n" + thread.RenderSummary() } str := "" - str += post.GetSummary() + "\n" + str += sanitize.InlineText(post.GetSummary()) + "\n" str += "\\- " + displayAddressMD(post.creator) + "," str += " [" + post.createdAt.Format("2006-01-02 3:04pm MST") + "](" + post.GetURL() + ")" str += " (" + strconv.Itoa(post.GetReactionCount(Gnod)) + " gnods)" @@ -228,7 +229,7 @@ func (post *Post) RenderPost(indent string, levels int) string { return "nil post" } str := "" - str += indentBody(indent, post.body) + "\n" // TODO: indent body lines. + str += indentBody(indent, sanitize.Block(post.body)) + "\n" // TODO: indent body lines. str += indent + "\\- " + displayAddressMD(post.creator) + ", " str += "[" + post.createdAt.Format("2006-01-02 3:04pm (MST)") + "](" + post.GetURL() + ")" str += " - (" + strconv.Itoa(post.GetReactionCount(Gnod)) + " gnods) - " diff --git a/realm/render.gno b/realm/render.gno index b9636ef..75cd1d8 100644 --- a/realm/render.gno +++ b/realm/render.gno @@ -4,6 +4,7 @@ import ( "strconv" "strings" + "gno.land/p/nt/markdown/sanitize/v0" "gno.land/r/sys/users" ) @@ -37,7 +38,7 @@ func Render(path string) string { userAddr := resolveAddr(path) userPosts := getUserPosts(userAddr) if userPosts == nil { - return "No posts by: " + path + return "No posts by: " + sanitize.InlineText(path) } return userPosts.RenderUserPosts(false) @@ -45,7 +46,7 @@ func Render(path string) string { userAddr := resolveAddr(parts[0]) userPosts := getUserPosts(userAddr) if userPosts == nil { - return "No posts by: " + parts[0] + return "No posts by: " + sanitize.InlineText(parts[0]) } if parts[1] == "home" { @@ -61,11 +62,11 @@ func Render(path string) string { // /r/berty/social:USER_NAME_OR_ADDR/THREAD_ID pid, err := strconv.Atoi(parts[1]) if err != nil { - return "invalid thread id: " + parts[1] + return "invalid thread id: " + sanitize.InlineText(parts[1]) } thread := userPosts.GetThread(PostID(pid)) if thread == nil { - return "thread does not exist with id: " + parts[1] + return "thread does not exist with id: " + sanitize.InlineText(parts[1]) } return thread.RenderPost("", 5) } @@ -74,26 +75,26 @@ func Render(path string) string { userAddr := resolveAddr(parts[0]) userPosts := getUserPosts(userAddr) if userPosts == nil { - return "No posts by: " + parts[0] + return "No posts by: " + sanitize.InlineText(parts[0]) } pid, err := strconv.Atoi(parts[1]) if err != nil { - return "invalid thread id: " + parts[1] + return "invalid thread id: " + sanitize.InlineText(parts[1]) } thread := userPosts.GetThread(PostID(pid)) if thread == nil { - return "thread does not exist with id: " + parts[1] + return "thread does not exist with id: " + sanitize.InlineText(parts[1]) } rid, err := strconv.Atoi(parts[2]) if err != nil { - return "invalid reply id: " + parts[2] + return "invalid reply id: " + sanitize.InlineText(parts[2]) } reply := thread.GetReply(PostID(rid)) if reply == nil { - return "reply does not exist with id: " + parts[2] + return "reply does not exist with id: " + sanitize.InlineText(parts[2]) } return reply.RenderInner() } else { - return "unrecognized path: " + path + return "unrecognized path: " + sanitize.InlineText(path) } } From 74cdadb385a1c324c79c545e5af87de81b974568 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 28 Oct 2026 16:30:41 +0100 Subject: [PATCH 5/7] chore: RenderPost: Add TODO for need to paginate Signed-off-by: Jeff Thompson --- realm/post.gno | 3 +++ 1 file changed, 3 insertions(+) diff --git a/realm/post.gno b/realm/post.gno index 5d8c93a..360e800 100644 --- a/realm/post.gno +++ b/realm/post.gno @@ -239,6 +239,9 @@ func (post *Post) RenderPost(indent string, levels int) string { str += " \\[[repost](" + post.GetRepostFormURL() + ")]" } str += "\n" + // TODO: This renders every reply, recursively for `levels` levels, with no pagination, and + // post bodies have no length limit. Should paginate them, and cap the body length. + // See renderThread in r/gnoland/boards2/v0/render_thread.gno . if levels > 0 { if post.replies.Size() > 0 { post.replies.ReverseIterate("", "", func(key string, value interface{}) bool { From d3b11f58c96e376755f683024ca6440f187476da Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 28 Oct 2026 16:47:14 +0100 Subject: [PATCH 6/7] fix: Post.GetURL: Handle repost URL correctly Signed-off-by: Jeff Thompson --- realm/post.gno | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/realm/post.gno b/realm/post.gno index 360e800..9077512 100644 --- a/realm/post.gno +++ b/realm/post.gno @@ -171,7 +171,8 @@ func (post *Post) GetSummary() string { } func (post *Post) GetURL() string { - if post.IsThread() { + // Not IsThread(), which is false for a repost even though it is top-level. + if post.threadID == post.id { return post.userPosts.GetURLFromThreadAndReplyID( post.id, 0) } else { From f700311729e1f5c327bd9afb3d60b59dbb08ee9a Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 28 Oct 2026 16:49:23 +0100 Subject: [PATCH 7/7] chore: .tool-versions: Use golang 1.25.9 Signed-off-by: Jeff Thompson --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 49aa265..8dc011f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -golang 1.24.4 +golang 1.25.9