Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

/p/moul/x/social/threads/v0

Directory · 4 Files
README.md Open

gno.land/p/moul/x/social/threads/v0

The engine behind an embeddable discussion block, keyed on the page it is shown under rather than on the realm that stores it: NewBoard, Post, Reply, Pin, List, Recent, Block.

1b := threads.NewBoard()
2id, _ := b.Post("gno.land/r/moul/home", author, "worth discussing", height)
3isNew, _ := b.Reply(id, replier, "it is", height)   // isNew is the mint signal
4b.Pin(id, height+1000)                              // placement, bought elsewhere
5threads.Block(realmPath, page, b.List(page, height, 5), b.PageLen(page), height)

A forum is a destination and has to earn its traffic before anybody writes the first post. A block does not. It is dropped into pages that already have readers, and the discussion attaches to the object it is about: a realm page, a proposal, an address. One realm holds every thread, every host realm ships the same two lines, and no host realm stores anything. That is the shape the web settled on for comments in 2010, and the same one p/moul/reactions uses for the tally. The two are deliberate neighbours and share a page key format: reactions are a closed palette and need no moderation, text needs some, so a realm can take the cheap one alone.

Reply reports whether the replier was new to that thread, and that boolean is the whole reason the package keeps a replier set. A realm paying an author for attention wants distinct people, not distinct messages, and a thread's own author replying to themselves is never new. The mint rule then has exactly one signal and exactly one call site.

List is a partition, not a sort: pinned first, then the rest, both newest first. No comparator, no tie to break, and two identical calls always produce the same page, which is what a Render needs. A pin is an absolute height, so it expires on its own and nothing has to sweep it. Pin refuses to move a pin backwards, so a cheap pin cannot cut an expensive one short; what a pin costs is the realm's decision, not this package's.

A body is free text and therefore attacker-controlled markdown. ValidBody bounds it and refuses control characters, which is a different protection from escaping and not a substitute for it: everything rendered here goes through ui.Inline or ui.Cell, and so must any realm that renders a body itself. Note Block truncates with ui.ShortN and then hands the result to md.Link, which escapes: ui.Excerpt there would escape twice and render the backslashes.

A page key is a package path, validated by ValidPage to the same rule p/moul/reactions uses, so a realm embedding both blocks passes one key to both.

Live realm: r/moul/x/social/threads · render it at /r/moul/x/social/threads/v0.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

Dependency graph:

gno.land/p/moul/x/social/threads/v0 dependency graph

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.