/p/moul/kit/ui/v0
gno.land/p/moul/kit/ui/v0
The shared display vocabulary for r/moul realms: the small set of rendering
decisions every realm was making on its own, made once.
First package of the p/moul/kit/* layer
(moul/gno-contracts#151).
kit composes the existing packages, it does not replace them.
Why it exists
Measured across 118 realm directories in this repo:
Realm dirs importing zero p/moul/* |
79 of 118 |
Realm lines inside Render / render* |
5,909 of 23,276 (25.4%) |
| Lines in duplicated private helpers | 857 across 126 functions |
Copies of shortAddr |
11, with four different truncation rules |
Copies of escapeInline |
8, all a byte-identical seven-pair replacer |
The shortAddr number is the one that matters: the same account rendered
differently depending on which realm you opened. That is not duplication, it is
four answers to one question.
What is here, and what is deliberately not
This package does not re-export markdown primitives. Headings, bold, lists,
code blocks and links already have an owner in
p/moul/md; import that alongside. What lives here is only
what had no owner and was therefore copy-pasted.
1import (
2 "gno.land/p/moul/kit/ui/v0"
3 "gno.land/p/moul/md/v0"
4)
5
6func Render(path string) string {
7 t := ui.NewTable("#", "Player", "Score")
8 for i, p := range players {
9 t.Row(strconv.Itoa(i+1)+ui.Podium(i), ui.Addr(p.addr), ui.Cell(p.label))
10 }
11 return md.H1("Leaderboard") + t.OrEmpty("Nobody has played yet.")
12}
Addr(a) |
`g1manfre…dlf5`, the one address format |
AddrFull(a) |
the full address, in backticks |
AddrText(a) |
shortened, no backticks, for a link title |
AddrOf(s) |
Addr for an address already in string form (an avl key) |
Short(s), ShortN(s, head, tail) |
the same rule for any string: a URL, a handle, a commitment hash |
Inline(s), Cell(s) |
escape user text, delegated to p/nt/markdown/sanitize |
Excerpt(s, width) |
a preview of user prose: cut to width runes, then escape. That order, because escaping first and cutting second strands a backslash |
Action(title, fn, args...) |
a clickable call, instead of prose telling the reader to type a function name |
ActionIn(pkgPath, title, fn, args...) |
the same, against another realm |
NewTable(headers...), .Row(...), .String(), .OrEmpty(msg) |
GFM tables |
Empty(msg) |
the italic placeholder where a list would be |
Podium(rank) |
🥇🥈🥉, or "" past third |
Join(sep, parts...) |
concatenate, skipping empty sections |
The escaping contract
Table cells and Action titles are markdown, not plain text.
- Anything that came from a user goes through
Cell(inside a table) orInline(anywhere else) before it reaches this package. - Output of
Addr,AddrFull,AddrOfandPodiumis already safe and must not be escaped again.
Table renders the GFM table itself rather than delegating to
p/moul/mdtable, which unconditionally rewrites | to
| in every cell. Stacked on Cell, which already emits the GFM escape
\|, that double-escapes into a stray backslash (a\|b, found while
porting guestbook). One escaping stage is the only way to get this right, and
it has to be the stage that knows whether the text is user input.
Action also escapes its title, which
p/moul/helplink does not (it carries an // XXX: escape title where this would go).
Design rule
The safe, conventional thing must be the shortest thing to type. A realm author reaching for the obvious call has to land on the correct behaviour; that is the only mechanism that stops these helpers from being rewritten a twelfth time.
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:

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