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

v0 source pure

Package ui is the shared display vocabulary for r/moul realms: the small set of rendering decisions that every realm ...

Readme View source

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) or Inline (anywhere else) before it reaches this package.
  • Output of Addr, AddrFull, AddrOf and Podium is 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:

gno.land/p/moul/kit/ui/v0 dependency graph

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

Overview

Package ui is the shared display vocabulary for r/moul realms: the small set of rendering decisions that every realm was making on its own, made once.

It deliberately does NOT re-export markdown primitives. Headings, bold, lists, code blocks and links already have an owner in p/moul/md(/p/moul/md/v0); import that alongside this package. What lives here is only what had no owner and was therefore copy-pasted:

  • Addr: one address shortening for the whole namespace. Eleven realms had their own shortAddr with four different truncation rules, so the same account rendered differently depending on which realm you opened.
  • Inline / Cell / Excerpt: markdown escaping, delegated to p/nt/markdown/sanitize(/p/nt/markdown/sanitize/v0). Eight realms hand-rolled a seven-pair strings.NewReplacer that misses most of what the real escaper handles. Excerpt adds the length cap those previews also need, in the order that is safe: cut, then escape.
  • Action: a clickable call link instead of prose telling the reader to go type a function name.
  • Table, Empty, Podium: the last recurring scraps.

Escaping contract

Table cells and Action titles are markdown, not plain text. Anything that came from a user must go through Cell (inside a table) or Inline (anywhere else) before it is handed to this package. Output of Addr, AddrFull and Podium is already safe and must NOT be escaped again.

Constants 2

const Head, Tail

1const (
2	Head = 8
3	Tail = 4
4)
source

Head and Tail are how many characters Addr and Short keep on each side.

A gno.land address is 40 characters ("g1" plus 38 of bech32 data), so 8+1+4 renders it as g1manfre…dlf5: enough of the head to recognise a familiar account, enough of the tail to tell two similar ones apart.

const Ellipsis

1const Ellipsis = "…"
source

Ellipsis is the character placed between the kept head and tail of a shortened string.

Functions 15

func Action

1func Action(title, fn string, args ...string) string
source

Action renders a clickable call to a function of the current realm.

Example
1ui.Action("Play cell 4", "Move", "gameID", "7", "cell", "4")

Arguments are key/value pairs, as in p/moul/txlink. The title is escaped, which is the one thing helplink.Func does not do.

func ActionIn

1func ActionIn(pkgPath, title, fn string, args ...string) string
source

ActionIn is Action against another realm, given as the full package path that realm declares on the module line of its gnomod.toml.

This doc deliberately contains no example path, not even a placeholder one. gnopm scans doc comments the way it scans imports, so any package path spelled out in a comment becomes a dependency of the package holding it: one that is neither live nor in the workspace BLOCKS publishing this package and everything that imports it, and naming a live one is no better, since it drags an unrelated realm into every publish plan. This comment previously named a path that deversioning had already removed, which blocked the whole ui dependency tree on every chain.

func Addr

1func Addr(a address) string
source

Addr renders an address shortened and in backticks: `g1manfre…dlf5`.

This is the default way to show an address. It is monospace (an address is opaque data, not prose), and the backticks make it inert markdown, so it is safe in a table cell or anywhere else without further escaping.

func AddrFull

1func AddrFull(a address) string
source

AddrFull renders an address in full, in backticks. Use it where the reader needs to copy the value; use Addr everywhere else.

func AddrOf

1func AddrOf(s string) string
source

AddrOf is Addr for an address already in its string form, which is how realms hold one when it is an avl key or a stored field.

func AddrText

1func AddrText(a address) string
source

AddrText renders an address shortened, with no backticks. Use it inside a link title or another construct where backticks would not render.

func Cell

1func Cell(s string) string
source

Cell escapes user-supplied text for a markdown table cell: Inline plus tab and pipe handling, so the value cannot open a new column.

func Empty

1func Empty(msg string) string
source

Empty renders the placeholder shown where a list would be: an italic line, newline-terminated, so it drops into a Render body as-is.

Example
1ui.Empty("No games yet.")  // "_No games yet._\n"

The message is realm chrome, not user input, and is not escaped.

func Excerpt

1func Excerpt(s string, width int) string
source

Excerpt is Inline for a string too long to show whole: it keeps the first width runes, appends Ellipsis, and escapes what it kept.

Use it for a preview of user-written prose where only the beginning carries meaning: the first line of a post in an index, a note on a board, a comment in a list. For an identifier whose tail has to stay recognisable, an address, a hash, a URL, use Short instead, which keeps both ends.

The order is the whole point, and it is what a call site gets wrong. Escaping inserts backslashes, so cutting an ALREADY-escaped string can strand a trailing lone backslash that escapes whatever chrome follows it. Excerpt cuts first, on a rune boundary so a multi-byte character is never split, then escapes. Ellipsis stays outside the escaper: it is this package's chrome, not the user's text.

func Inline

1func Inline(s string) string
source

Inline escapes user-supplied text for an inline markdown context: a sentence, a list item, a link title.

It delegates to sanitize.InlineText, which strips bidi and zero-width characters, folds newlines to spaces so the text cannot escape its line, and applies the full CommonMark inline escape. Never concatenate user-supplied text into rendered output without this.

func Join

1func Join(sep string, parts ...string) string
source

Join concatenates parts, skipping empty ones, with sep between them. It is the small piece of glue every Render needs to assemble optional sections without emitting stray separators.

func Podium

1func Podium(rank int) string
source

Podium returns the medal for a zero-based rank, or "" past third place.

func Short

1func Short(s string) string
source

Short shortens any string with the house rule: unchanged when shortening would not save a character, otherwise head + ellipsis + tail.

It is the same rule Addr uses, exposed for the non-address strings realms also truncate: URLs, handles, commitment hashes.

func ShortN

1func ShortN(s string, head, tail int) string
source

ShortN is Short with an explicit head and tail. A negative head or tail is treated as zero. When head+tail cannot save a character against the input, the input is returned unchanged.

func NewTable

1func NewTable(headers ...string) *Table
source

NewTable starts a table with the given header cells.

Types 1

type Table

struct
1type Table struct {
2	headers []string
3	rows    [][]string
4}
source

Table accumulates markdown table rows and renders them.

Cells are markdown, not plain text: pass Addr and friends straight through, and wrap anything user-supplied in Cell first.

Table renders the GFM table itself rather than delegating to p/moul/mdtable(/p/moul/mdtable/v0), 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"). One escaping stage is the only way to get this right, and it has to be the one that knows whether the text is user input.

Example
1t := ui.NewTable("#", "X", "O", "Status")
2t.Row("7", ui.Addr(x), ui.Addr(o), "Turn: X")
3return t.String()

Methods on Table

func Len

method on Table
1func (t *Table) Len() int
source

Len reports how many rows have been added.

func OrEmpty

method on Table
1func (t *Table) OrEmpty(msg string) string
source

OrEmpty renders the table, or the placeholder when it has no rows.

func Row

method on Table
1func (t *Table) Row(cells ...string) *Table
source

Row appends a row and returns the table, so calls can be chained.

A row with fewer cells than there are headers is padded with empty cells; a longer row is kept as-is, which renders as a ragged table rather than silently dropping data.

func String

method on Table
1func (t *Table) String() string
source

String renders the table. A table with no rows renders as "", so a caller can fall back to Empty with a single check on Table.Len.

Imports 4

Source Files 4