1// Package todos is a shared, on-chain to-do board realm.2//3// Anyone can add an item; the caller's address is recorded as the author.4// Items can be toggled (open/done) or removed. Render exposes the board as5// Markdown checkboxes with open/done counts, newest item first.6package todos78import (9 "strconv"10 "strings"1112 "gno.land/p/nt/avl/v0"13)1415// item is one entry on the board. `address` is the uverse bech32 type — it is16// persistable (unlike a `realm` value), so it is safe to store across txs.17type item struct {18 id int19 text string20 done bool21 author address22}2324var (25 // items is keyed by zero-padded id string so avl iteration is ordered by id.26 items avl.Tree27 nextID int28)2930// Add appends a new item authored by the caller and returns its id.31//32// Crossing function: takes `cur realm` first (gno 0.9 interrealm convention),33// authenticates with cur.IsCurrent() before trusting cur.Previous().34func Add(cur realm, text string) int {35 if !cur.IsCurrent() {36 panic("spoofed realm")37 }38 text = strings.TrimSpace(text)39 if text == "" {40 panic("todos: text must not be empty")41 }4243 author := cur.Previous().Address()4445 nextID++46 id := nextID47 items.Set(idKey(id), &item{48 id: id,49 text: text,50 done: false,51 author: author,52 })53 return id54}5556// Toggle flips the done state of the item with the given id.57func Toggle(cur realm, id int) {58 if !cur.IsCurrent() {59 panic("spoofed realm")60 }61 v := items.Get(idKey(id))62 if v == nil {63 panic("todos: item not found: #" + strconv.Itoa(id))64 }65 it := v.(*item)66 it.done = !it.done67}6869// Remove deletes the item with the given id.70func Remove(cur realm, id int) {71 if !cur.IsCurrent() {72 panic("spoofed realm")73 }74 if _, removed := items.Remove(idKey(id)); !removed {75 panic("todos: item not found: #" + strconv.Itoa(id))76 }77}7879// idKey renders an id as a fixed-width, zero-padded key so avl.Tree's80// byte-lexicographic ordering matches numeric id ordering.81func idKey(id int) string {82 s := strconv.Itoa(id)83 const width = 1284 if len(s) >= width {85 return s86 }87 return strings.Repeat("0", width-len(s)) + s88}8990// shortAddr renders an address as "g1abc…" (first 6 chars + ellipsis).91func shortAddr(a address) string {92 s := a.String()93 if len(s) <= 6 {94 return s95 }96 return s[:6] + "…"97}9899// counts returns the number of open and done items.100func counts() (open, done int) {101 items.Iterate("", "", func(_ string, v interface{}) bool {102 if v.(*item).done {103 done++104 } else {105 open++106 }107 return false108 })109 return open, done110}111112// Render returns the board as Markdown. Newest item (highest id) first.113//114// Render is NOT a crossing function (no `cur realm` param) — it is read-only115// and invoked by gnoweb, not via MsgCall.116func Render(path string) string {117 open, done := counts()118119 var b strings.Builder120 b.WriteString("# Shared To-Do Board\n\n")121 b.WriteString(strconv.Itoa(open) + " open · ")122 b.WriteString(strconv.Itoa(done) + " done · ")123 b.WriteString(strconv.Itoa(open+done) + " total\n\n")124125 if open+done == 0 {126 b.WriteString("_No items yet. Add one with `Add(text)`._\n")127 return b.String()128 }129130 // Collect in ascending id order, then emit newest first.131 var lines []string132 items.Iterate("", "", func(_ string, v interface{}) bool {133 it := v.(*item)134 mark := " "135 if it.done {136 mark = "x"137 }138 line := "- [" + mark + "] #" + strconv.Itoa(it.id) + " " +139 it.text + " (" + shortAddr(it.author) + ")"140 lines = append(lines, line)141 return false142 })143144 for i := len(lines) - 1; i >= 0; i-- {145 b.WriteString(lines[i])146 b.WriteString("\n")147 }148149 return b.String()150}151Add(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, text string) int
Remove(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int)
Render(path string) string
Toggle(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id int)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.