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/reactions/v0

Directory · 5 Files
README.md Open

gno.land/p/moul/reactions/v0

The engine behind an embeddable reaction block: pages, tallies, a closed emoji palette, and the markdown that shows them. No realm state, no chain reads, no authority decisions. The realm on top owns all three.

Live realm: gno.land/r/moul/reactions/v0.

The model

A Board maps a page key to a Page. A page holds exactly three things:

a count per palette key what the strip shows
the current reaction of each address so a tally counts addresses and not clicks, and a reader can change their mind or take it back
the last reaction which key, from whom, at what height

There is no text field anywhere. That is what makes the whole thing runnable with no moderation: reactions come from a closed set of six, so there is nothing to take down.

 1b := reactions.NewBoard()
 2b.React("gno.land/r/moul/home", caller, reactions.KeyFire, height) // false if rejected
 3b.Unreact("gno.land/r/moul/home", caller)                          // false if there was none
 4
 5p := b.Page("gno.land/r/moul/home") // nil when untouched, and nil is safe to use
 6p.Count(reactions.KeyFire)          // 1
 7p.Total()                           // addresses reacting, not clicks
 8p.Of(caller)                        // "fire", or "" for none
 9key, who, height := p.Last()
10
11md := reactions.Block(realmPath, page, p) // the whole widget

Every Page read is nil-safe and answers as if the page were empty, so a host realm renders the block without ever checking whether the page exists.

Three decisions worth knowing before you use it

A reaction key is an ASCII name, not a glyph. "up", not 👍. A name survives a URL query parameter, an avl key and a markdown escaper with no encoding question anywhere, and the palette can change a glyph without rewriting stored state. Emoji(key) is the presentation half.

No glyph in the palette uses U+FE0F or a zero-width joiner. The link title goes through ui.Inline, which strips exactly those, so a glyph needing a variation selector would come out the other side as its monochrome form. ❤️ is the casualty; heart is 💜, a codepoint that is colour on its own. Pinned by TestEmojiSurviveTheEscaper.

Unreact does not rewind the last-reaction line. It records an event that happened, and recomputing it would mean walking every address on the page, so Page.Last keeps naming a reaction nobody holds any more.

What is shown is a separate decision, and Summary makes it: a page whose reactors have all left renders as empty, not as a withdrawn reaction. Gating on the last key alone would print "last 👍 by g1… · 0 reactions" above an empty strip, which contradicts itself. TestSummaryGoesEmptyWhenEveryoneLeaves pins the pair.

Page keys

A page key is a package path, and ValidPage is what bounds this package's exposure: at least one /, no empty element, and only lowercase ASCII letters, digits, ., -, _, : and /, up to MaxPageLen bytes. That excludes every character a markdown escaper exists for, plus every byte above ASCII, so a stored key can never be a bidi run, a zero-width joiner or an image.

: is in the set so a host realm can key one block per article on the path it already serves: gno.land/r/you/blog:hello-world.

This package only ever puts a key in a URL. A realm that shows the key as text escapes it there; ValidPage and ui.Inline protect against different mistakes.

Not to be confused with

r/moul/x/daily/reactions is an older, unrelated experiment: a single self-contained board with its own "topics", no embedding, and no way for a reader to change or remove a reaction. Nothing is shared between the two.


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/reactions/v0 dependency graph

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.