electionrender.gno
5.42 Kb · 129 lines
1package kourtv3
2import (
3 "strconv"
4 "strings"
5 sanitize "gno.land/p/nt/markdown/sanitize/v0"
6)
7const electionPath = "election"
8func renderElection(slug string) string {
9 if !Exists(slug) {
10 return "## Not found\nNo court by that slug."
11 }
12 c := mustCourt(slug)
13 base := "/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/kourtv3:" + slug
14 var b strings.Builder
15 b.WriteString("# Electing the moderators of " + courtNameFor(c) + "\n\n")
16 b.WriteString("[← " + courtNameFor(c) + "](" + base + ") · [moderation log](" +
17 base + "/mod)\n\n")
18 b.WriteString("A court's moderators answer claims, curate its folders, and can " +
19 "hide a claim, hide a comment, slash somebody's standing, or freeze an " +
20 "address out of the boards. **This ballot is the only way the court's " +
21 "holders replace them.**\n\n")
22 cm := c.mod
23 if cm == nil || cm.election == nil || cm.election.resolved {
24 writeNoElection(&b, c, cm)
25 b.WriteString("\n" + helpLink + "\n")
26 return b.String()
27 }
28 writeOpenElection(&b, c, cm, base)
29 b.WriteString("\n" + helpLink + "\n")
30 return b.String()
31}
32func writeNoElection(b *strings.Builder, c *Court, cm *courtMod) {
33 b.WriteString("## No election is open\n\n")
34 if cm == nil || cm.n == 0 {
35 b.WriteString("This court has no moderator set yet — its creator holds the " +
36 "seat until an election or an appointment changes that.\n")
37 } else {
38 b.WriteString("- seats held now: **" + strconv.Itoa(cm.n) + "**, " +
39 strconv.Itoa(cm.m) + " of whom must agree for a moderation act\n")
40 }
41 now := heightNow()
42 if cm != nil && electionCooldownOpen(cm) {
43 b.WriteString("- this court is in its post-election cooldown until block **" +
44 strconv.FormatInt(cm.electionCooldownUntil, 10) + "** (now " +
45 strconv.FormatInt(now, 10) + "); no ballot can open before then\n\n")
46 return
47 }
48 b.WriteString("- a ballot **can be opened now**\n\n")
49 b.WriteString("Opening one takes two calls: `RegisterModCandidate` records a " +
50 "proposed set of moderators and returns its id, and `OpenElection` puts " +
51 "that id on a ballot. Any holder may do both.\n\n")
52}
53func writeOpenElection(b *strings.Builder, c *Court, cm *courtMod, base string) {
54 e := cm.election
55 now := heightNow()
56 b.WriteString("## A ballot is open\n\n")
57 switch {
58 case !nominationClosed(e):
59 b.WriteString("**Nominating.** Candidate sets may be added until block **" +
60 strconv.FormatInt(e.nominateEnd, 10) + "**; voting runs from then " +
61 "until block **" + strconv.FormatInt(e.voteEnd, 10) + "**.\n\n")
62 case !votingClosed(e):
63 b.WriteString("**Voting.** Nominations closed at block " +
64 strconv.FormatInt(e.nominateEnd, 10) + "; the ballot closes at block **" +
65 strconv.FormatInt(e.voteEnd, 10) + "**.\n\n")
66 default:
67 b.WriteString("**Closed and awaiting resolution.** The ballot shut at block " +
68 strconv.FormatInt(e.voteEnd, 10) + ". Anyone may call `ResolveElection` " +
69 "to apply the result below.\n\n")
70 }
71 b.WriteString("- now: block " + strconv.FormatInt(now, 10) + "\n")
72 b.WriteString("- a candidate set costs a bond of **" +
73 strconv.FormatInt(e.bond, 10) + " " + qualifiedSymbol(c) + "**\n")
74 b.WriteString("- weights are pinned at the epoch the ballot opened, so buying " +
75 "coin now does not add voting weight to it\n\n")
76 writeBallotLines(b, c, e)
77 writeTurnoutBar(b, c, e)
78}
79func writeBallotLines(b *strings.Builder, c *Court, e *election) {
80 b.WriteString("## On the ballot\n\n")
81 rows := 0
82 e.lines.Iterate("", "", func(_ string, v any) bool {
83 l, ok := v.(*ballotLine)
84 if !ok {
85 return false
86 }
87 rows++
88 b.WriteString("- **set #" + strconv.FormatUint(l.candID, 10) + "** — " +
89 strconv.FormatInt(l.weight, 10) + " " + qualifiedSymbol(c) + " approving\n")
90 if cv := c.mod.candidates.Get(beClaimKey(l.candID)); cv != nil {
91 cand := cv.(*modCandidate)
92 b.WriteString(" - would seat " + strconv.Itoa(len(cand.members)) +
93 ", " + strconv.Itoa(cand.m) + " of whom must agree\n")
94 for _, m := range cand.members {
95 b.WriteString(" - `" + sanitize.InlineText(m.String()) + "`\n")
96 }
97 }
98 return rows >= renderPageSize
99 })
100 if rows == 0 {
101 b.WriteString("_No candidate sets yet._ Until one is nominated there is " +
102 "nothing to elect, and the current moderators stay.\n\n")
103 } else {
104 b.WriteString("\n")
105 }
106 b.WriteString("- **keep the current moderators** — " +
107 strconv.FormatInt(e.retainW, 10) + " " + qualifiedSymbol(c) + " approving\n\n")
108}
109func writeTurnoutBar(b *strings.Builder, c *Court, e *election) {
110 b.WriteString("## Turnout\n\n")
111 b.WriteString("- cast so far: **" + strconv.FormatInt(e.turnout, 10) + " " +
112 qualifiedSymbol(c) + "**\n")
113 b.WriteString("- the bar: **" + strconv.FormatInt(e.floor, 10) + " " +
114 qualifiedSymbol(c) + "**, fixed when the ballot opened\n")
115 if e.turnout < e.floor {
116 b.WriteString("- still needed: **" + strconv.FormatInt(e.floor-e.turnout, 10) +
117 " " + qualifiedSymbol(c) + "**\n\n")
118 } else {
119 b.WriteString("- the bar is cleared\n\n")
120 }
121 b.WriteString("> **Falling short is not a draw.** If the ballot closes with less " +
122 "than the bar cast, nothing is installed and the current moderators keep " +
123 "their seats — the same outcome as a vote to retain them.\n>\n" +
124 "> And the bar is a share of this court's coin **including coin that " +
125 "cannot vote**: it nets out only the court's own escrow, so coin held at " +
126 "a wrapper's address counts toward the pool the bar was measured against " +
127 "while no key can cast it. The share of actually-castable weight you need " +
128 "is therefore larger than the bar looks.\n\n")
129}