Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

render.gno

19.86 Kb · 566 lines
  1package kourt
  2import (
  3	"strconv"
  4	"strings"
  5	sanitize "gno.land/p/nt/markdown/sanitize/v0"
  6)
  7func Render(path string) string {
  8	return withSiteBanner(render(path), path)
  9}
 10func render(path string) string {
 11	path = strings.Trim(path, "/")
 12	if path == "" {
 13		return renderDirectory()
 14	}
 15	parts := strings.SplitN(path, "/", 4)
 16	if len(parts) == 1 {
 17		if parts[0] == helpPath {
 18			return renderHelp()
 19		}
 20		if parts[0] == adminParamsPath {
 21			return renderAdminParams()
 22		}
 23		return renderCourt(parts[0])
 24	}
 25	if parts[1] == "mod" {
 26		return renderModLog(parts[0])
 27	}
 28	if parts[1] == electionPath {
 29		if len(parts) != 2 {
 30			return "## Not found\nThe ballot is /<court>/election."
 31		}
 32		return renderElection(parts[0])
 33	}
 34	if parts[1] == "folder" {
 35		if len(parts) != 3 {
 36			return "## Not found\nA folder is /<court>/folder/<id>."
 37		}
 38		fid, ferr := strconv.ParseUint(parts[2], 10, 64)
 39		if ferr != nil {
 40			return "## Not found\nA folder id must be a number."
 41		}
 42		return renderFolderPage(parts[0], fid)
 43	}
 44	if parts[1] == "me" {
 45		if len(parts) == 3 {
 46			return renderStanding(parts[0], parts[2])
 47		}
 48		return renderStandingIndex(parts[0])
 49	}
 50	id, err := strconv.ParseUint(parts[1], 10, 64)
 51	if err != nil {
 52		return "## Not found\nA claim id must be a number."
 53	}
 54	if len(parts) >= 3 && parts[2] == "board" {
 55		if len(parts) == 4 {
 56			if parts[3] == "top" {
 57				return renderBoardTop(parts[0], id)
 58			}
 59			if parts[3] == "hidden" {
 60				return renderBoardHidden(parts[0], id)
 61			}
 62			return renderBoardRow(parts[0], id, parts[3])
 63		}
 64		return renderBoardPage(parts[0], id)
 65	}
 66	if len(parts) == 4 {
 67		return "## Not found\nThat is not a page."
 68	}
 69	if len(parts) == 3 {
 70		return renderPositions(parts[0], id, parts[2])
 71	}
 72	return renderClaim(parts[0], id)
 73}
 74func renderDirectory() string {
 75	b := strings.Builder{}
 76	b.WriteString("# " + platformName + "\n\nLet Truth be told.\n\n")
 77	b.WriteString("Stake on claims of fact. Your principal always returns 1×.\n\n")
 78	b.WriteString(helpLink + "\n\n")
 79	afterHeader := b.Len()
 80	writeTier(&b, "Featured", tierFeatured)
 81	writeListedByBurn(&b)
 82	if b.Len() == afterHeader {
 83		b.WriteString("_No courts yet._\n")
 84	}
 85	return b.String()
 86}
 87func writeListedByBurn(b *strings.Builder) {
 88	slugs := listedPage("burn", 0, renderPageSize)
 89	if len(slugs) == 0 {
 90		return
 91	}
 92	b.WriteString("## Courts\n\n")
 93	shown := 0
 94	for _, s := range slugs {
 95		c := mustCourt(s)
 96		if c.tier != tierListed {
 97			continue
 98		}
 99		shown++
100		b.WriteString("- [" + courtNameFor(c) + "](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + s + ") · " +
101			qualifiedSymbol(c) + " — " + strconv.FormatUint(c.nextID, 10) + " claims\n")
102	}
103	if total := len(ListByTier(int(tierListed))); total > shown {
104		b.WriteString("- _…and " + strconv.Itoa(total-shown) +
105			" more; open any court directly at /r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:<slug>_\n")
106	}
107	b.WriteString("\n")
108}
109const renderPageSize = 50
110func writeTier(b *strings.Builder, heading string, tier uint8) {
111	slugs := ListByTier(int(tier))
112	if len(slugs) == 0 {
113		return
114	}
115	b.WriteString("## " + heading + "\n\n")
116	shown := len(slugs)
117	if shown > renderPageSize {
118		shown = renderPageSize
119	}
120	for _, s := range slugs[:shown] {
121		c := mustCourt(s)
122		b.WriteString("- [" + courtNameFor(c) + "](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + s + ") · " +
123			qualifiedSymbol(c) + " — " + strconv.FormatUint(c.nextID, 10) + " claims\n")
124	}
125	if len(slugs) > shown {
126		b.WriteString("- _…and " + strconv.Itoa(len(slugs)-shown) +
127			" more; open any court directly at /r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:<slug>_\n")
128	}
129	b.WriteString("\n")
130}
131func renderCourt(slug string) string {
132	if !Exists(slug) {
133		return "## Not found\nNo court by that slug."
134	}
135	c := mustCourt(slug)
136	b := strings.Builder{}
137	b.WriteString("# " + courtNameFor(c) + "\n\n")
138	if c.tier == tierHidden {
139		b.WriteString("> **Delisted by the global DAO.** This court appears in no " +
140			"listing and is reachable only by direct link. Nothing else changes: " +
141			"its claims still stake, answer, settle and pay out as usual.\n\n")
142	}
143	if d := courtDescFor(c); d != "" {
144		b.WriteString(d + "\n\n")
145	}
146	writeFolderIndex(&b, c, slug)
147	b.WriteString("## Claims\n\n")
148	if c.nextID == 0 {
149		b.WriteString("_No claims yet._\n\n")
150		writeCourtCoin(&b, c)
151		return b.String()
152	}
153	lo := uint64(1)
154	if c.nextID > renderPageSize {
155		lo = c.nextID - renderPageSize + 1
156	}
157	for id := c.nextID; id >= lo; id-- {
158		cs := mustClaim(c, id)
159		if HiddenFromListing(slug, id) {
160			continue
161		}
162		b.WriteString("- [" + claimTitleFor(c, cs) + "](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug + "/" +
163			strconv.FormatUint(id, 10) + ") — " + claimStatus(cs) + "\n")
164	}
165	if lo > 1 {
166		b.WriteString("- _…and " + strconv.FormatUint(lo-1, 10) +
167			" older; open any by id at /r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug + "/<id>_\n")
168	}
169	b.WriteString("\n")
170	writeStrip(&b, slug)
171	writePending(&b, slug)
172	writeCourtCoin(&b, c)
173	b.WriteString("[Moderation log](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug + "/mod)\n")
174	return b.String()
175}
176func writeCourtCoin(b *strings.Builder, c *Court) {
177	b.WriteString("## This court's coin\n\n")
178	b.WriteString("- " + qualifiedSymbol(c) +
179		" — this court's own coin, not interchangeable with any other court's\n")
180	b.WriteString("- price: " + strconv.FormatInt(c.crv.Price(c.minted), 10) +
181		" ugnot per coin — buying burns the GNOT, there is no sell side, " +
182		"and the price only rises\n")
183	b.WriteString("- in circulation: " + strconv.FormatInt(c.coin.TotalSupply(), 10) +
184		" · paid out as rewards so far: " + strconv.FormatInt(c.emittedTotal, 10) + "\n")
185	b.WriteString("- reward reservoir: " + strconv.FormatInt(c.reservoirR(), 10) +
186		" waiting to be earned · " + strconv.FormatInt(c.seniorOwed, 10) +
187		" already owed to earlier earners\n\n")
188	b.WriteString(helpLink + "\n\n")
189}
190func renderClaim(slug string, id uint64) string {
191	if !Exists(slug) {
192		return "## Not found\nNo court by that slug."
193	}
194	c := mustCourt(slug)
195	if id == 0 || id > c.nextID {
196		return "## Not found\nNo claim by that id."
197	}
198	cs := mustClaim(c, id)
199	b := strings.Builder{}
200	b.WriteString("# " + claimTitleFor(c, cs) + "\n\n")
201	if body := claimBodyQuoted(c, cs); body != "" {
202		b.WriteString(body)
203	}
204	writeClaimMedia(&b, c, cs)
205	b.WriteString(hideBanner(c, cs))
206	writeRelations(&b, c, id)
207	writeAppeal(&b, c, cs)
208	if cs.seeded {
209		b.WriteString("_Seeded by a moderator to start this court; the author earns nothing from it._\n\n")
210	}
211	b.WriteString("## Signal\n\n")
212	if cs.frozenAt != 0 {
213		fTotal := cs.yesStakeAtFreeze + cs.noStakeAtFreeze
214		b.WriteString("- staked when answered: " + strconv.FormatInt(fTotal, 10) +
215			" " + qualifiedSymbol(c) + " (YES " + strconv.FormatInt(cs.yesStakeAtFreeze, 10) +
216			" / NO " + strconv.FormatInt(cs.noStakeAtFreeze, 10) + ")\n")
217		if fTotal > 0 {
218			b.WriteString("- lean when answered: " + pct(cs.yesStakeAtFreeze, fTotal) + " YES\n")
219		}
220		if live := cs.yesStake + cs.noStake; live > 0 {
221			b.WriteString("- still unwithdrawn: " + strconv.FormatInt(live, 10) +
222				" " + qualifiedSymbol(c) + "\n")
223		}
224	} else {
225		total := cs.yesStake + cs.noStake
226		b.WriteString("- staked now: " + strconv.FormatInt(total, 10) +
227			" " + qualifiedSymbol(c) + " (YES " + strconv.FormatInt(cs.yesStake, 10) +
228			" / NO " + strconv.FormatInt(cs.noStake, 10) + ")\n")
229		if total > 0 {
230			b.WriteString("- right now: " + pct(cs.yesStake, total) + " YES\n")
231		}
232		b.WriteString("- pays: not set until the claim is answered — it is " +
233			"this claim's total stake against a typical claim in this court, " +
234			"so staking now is what sets it\n")
235	}
236	h := heightNow()
237	if yAvg, ym := cs.yes.Average(h, periodBlocks); ym {
238		if tAvg, tm := cs.oi.Average(h, periodBlocks); tm && tAvg > 0 {
239			b.WriteString("- trailing week: " + pct(yAvg, tAvg) + " YES\n")
240		}
241	}
242	yc := convToCC(cs.yesConvHi, cs.yesConvLo)
243	nc := convToCC(cs.noConvHi, cs.noConvLo)
244	if yc+nc > 0 {
245		b.WriteString("- lifetime, weighted by how long each stake was held: " + pct(yc, yc+nc) + " YES\n")
246	}
247	b.WriteString("\n## Status\n\n" + claimStatus(cs) + "\n")
248	if cs.frozenAt != 0 {
249		b.WriteString("\n## Resolution\n\n")
250		b.WriteString("- answer: " + sideName(int(cs.answer)) + " (bond " +
251			strconv.FormatInt(cs.answerBond0, 10) + " " + qualifiedSymbol(c) + ")\n")
252		b.WriteString("- the answerer has been challenged and upheld " +
253			strconv.FormatInt(int64(AnswerRecord(slug, cs.answerer)), 10) +
254			" time(s) in this court — answers nobody contested do not count\n")
255		if PriorityGateActive(slug) {
256			b.WriteString("- answer priority is live here — a record of " +
257				strconv.FormatInt(int64(priorityNetRecord), 10) +
258				"+ earns a 24h head start on new claims\n")
259		}
260		if cs.failedRounds > 0 {
261			b.WriteString("- failed dispute rounds: " + strconv.FormatInt(cs.failedRounds, 10) +
262				" of " + strconv.FormatInt(int64(maxFailedRounds), 10) +
263				" — the next dispute bond doubles\n")
264		}
265		if cs.disputeOpen {
266			b.WriteString("- dispute round " + strconv.FormatInt(cs.round, 10) +
267				" vote open — no running total is shown here until it closes\n")
268		} else if cs.verdictAt == 0 {
269			b.WriteString("- disputing this answer costs a bond of " +
270				strconv.FormatInt(cs.disputeBond0(), 10) + " " + qualifiedSymbol(c) +
271				", doubling for each round that fails to reach a quorum\n")
272			b.WriteString("  - win and the answerer's bond is burned and you are " +
273				"compensated, up to twice your own bond; lose and yours burns " +
274				"instead. Nothing passes between the two of you — a forfeit is " +
275				"burned, a compensation is newly issued\n")
276			b.WriteString("  - if the round draws no quorum, half your bond burns " +
277				"and half returns; three such rounds close the claim undecided, " +
278				"with every stake out at 1× and the deposit and fee refunded\n")
279			b.WriteString("  - **your stake is not at risk either way** — only the " +
280				"bond is\n")
281		}
282		if cs.verdictAt != 0 && !cs.provClose {
283			b.WriteString("- verdict: " + sideName(int(cs.provisional)) + " — " + cs.route + "\n")
284		}
285		if cs.tierRef <= 0 {
286			b.WriteString("- pays " + tierText(tierParBps) +
287				" — this claim was answered before ratings were recorded, so it " +
288				"pays the standard amount\n")
289		} else {
290			b.WriteString("- pays " + tierText(tierBpsFor(cs)) +
291				" — this claim held " + strconv.FormatInt(cs.xBarFrozen, 10) +
292				" " + qualifiedSymbol(c) + " against a typical " +
293				strconv.FormatInt(cs.tierRef, 10) + " " + qualifiedSymbol(c) + " here\n")
294			b.WriteString("- the rating is the money that showed up, not a vote: " +
295				"a claim of typical size pays 1.00×, and it is capped at 0.25× and 2.00×\n")
296			if cs.spamTotalW > 0 && cs.spamW > 0 {
297				b.WriteString("- and " + tierText(spamNetBps(cs)) +
298					" of that, because " + strconv.FormatInt(cs.spamW, 10) +
299					" of " + strconv.FormatInt(cs.spamTotalW, 10) +
300					" of the weight cast flagged it as spam\n")
301			}
302		}
303		switch {
304		}
305		if cs.rewardsOpened {
306			w, a, ans, carrot := cs.drawWinners, cs.drawAuthor, cs.drawAnswerer, cs.carrotPool
307			b.WriteString("- rewards open — pools (" + qualifiedSymbol(c) +
308				"): accuracy " + strconv.FormatInt(w, 10) +
309				", author " + strconv.FormatInt(a, 10) + ", answerer " + strconv.FormatInt(ans, 10) +
310				", participation remaining " + strconv.FormatInt(carrot, 10) + "\n")
311		}
312	}
313	b.WriteString("\n_To see your own stake on this claim, put your address on " +
314		"the end of this page's path:_ `/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + sanitize.InlineText(slug) +
315		"/" + strconv.FormatUint(id, 10) + "/<your address>`\n\n")
316	writeBoardLink(&b, c, cs, id)
317	return b.String()
318}
319func renderPositions(slug string, id uint64, addrStr string) string {
320	if !Exists(slug) {
321		return "## Not found\nNo court by that slug."
322	}
323	c := mustCourt(slug)
324	if id == 0 || id > c.nextID {
325		return "## Not found\nNo claim by that id."
326	}
327	who := address(addrStr)
328	if !who.IsValid() {
329		return "## Not found\nThat is not a valid address."
330	}
331	cs := mustClaim(c, id)
332	b := strings.Builder{}
333	b.WriteString("# Positions on this claim\n\n")
334	b.WriteString(hideBanner(c, cs))
335	b.WriteString("Claim: [" + claimTitleFor(c, cs) + "](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug +
336		"/" + strconv.FormatUint(id, 10) + ") in [" + courtNameFor(c) +
337		"](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug + ")\n\n")
338	b.WriteString("Address: " + sanitize.InlineText(addrStr) + "\n\n")
339	yStake := StakeOf(slug, id, sideYES, who)
340	nStake := StakeOf(slug, id, sideNO, who)
341	yConv := ConvictionOf(slug, id, sideYES, who)
342	nConv := ConvictionOf(slug, id, sideNO, who)
343	b.WriteString("## Stake (what you have in, and always get back)\n\n")
344	b.WriteString("- YES: " + strconv.FormatInt(yStake, 10) +
345		"   NO: " + strconv.FormatInt(nStake, 10) + " (" + qualifiedSymbol(c) +
346		" base units)\n")
347	b.WriteString("- conviction so far — YES: " + strconv.FormatInt(yConv, 10) +
348		"   NO: " + strconv.FormatInt(nConv, 10) + " (the reward weight)\n")
349	bal := c.coin.BalanceOf(who)
350	locked := lockedOf(c, who)
351	voted := voteLockedOf(c, who)
352	b.WriteString("\n## This address's " + qualifiedSymbol(c) +
353		" in this court (all claims)\n\n")
354	b.WriteString("- held: " + strconv.FormatInt(bal, 10) +
355		" — every unit votes, committed or not\n")
356	b.WriteString("- committed as stake: " + strconv.FormatInt(locked, 10) + "\n")
357	b.WriteString("- committed by voting: " + strconv.FormatInt(voted, 10) +
358		" — until each question resolves\n")
359	b.WriteString("- free to stake: " + strconv.FormatInt(spendable(c, who), 10) + "\n")
360	b.WriteString("- free to bond, deposit or transfer: " +
361		strconv.FormatInt(disposable(c, who), 10) + "\n")
362	roles := ""
363	if who == cs.author {
364		roles += "author "
365	}
366	if cs.frozenAt != 0 && who == cs.answerer {
367		roles += "answerer "
368	}
369	if roles != "" {
370		b.WriteString("- roles: " + roles + "\n")
371	}
372	if rec := AnswerRecord(slug, who); rec > 0 {
373		b.WriteString("- answers challenged and upheld in this court: " + strconv.FormatInt(int64(rec), 10) + "\n")
374	}
375	b.WriteString("\n## What you can do\n\n")
376	switch {
377	case cs.verdictAt == 0 && cs.frozenAt == 0:
378		b.WriteString("- staking is open — stake or unstake freely until an answer posts\n")
379	case cs.verdictAt == 0:
380		b.WriteString("- answered, awaiting the verdict — principal is never withheld; withdraw once it is final\n")
381	default:
382		b.WriteString("- the verdict is final — withdraw your principal on either side (1×)\n")
383		if cs.rewardsOpened {
384			win := int(cs.provisional)
385			if !cs.provClose && ((win == sideYES && yStake+yConv > 0) || (win == sideNO && nStake+nConv > 0)) {
386				b.WriteString("- you backed the winning side — pull your accuracy reward\n")
387			}
388			if who == cs.author && cs.drawAuthor > 0 {
389				b.WriteString("- author reward available — pull it\n")
390			}
391			if who == cs.answerer && cs.drawAnswerer > 0 {
392				b.WriteString("- answerer reward available — pull it\n")
393			}
394		}
395	}
396	return b.String()
397}
398func tierText(bps int64) string {
399	if bps < 0 {
400		bps = 0
401	}
402	frac := (bps % tierParBps) / 100
403	out := strconv.FormatInt(bps/tierParBps, 10) + "."
404	if frac < 10 {
405		out += "0"
406	}
407	return out + strconv.FormatInt(frac, 10) + "×"
408}
409func sideName(side int) string {
410	if side == sideYES {
411		return "YES"
412	}
413	return "NO"
414}
415func ClaimStatus(courtSlug string, claimID uint64) string {
416	return claimStatus(mustClaim(mustCourt(courtSlug), claimID))
417}
418func claimStatus(cs *claimState) string {
419	switch {
420	case cs.closed:
421		return "closed — never answered; every stake exited 1×, the deposit refunded"
422	case cs.spamClosed:
423		return "discarded — more than half the weight cast called this claim spam; " +
424			"every stake withdraws 1×, the answerer's and disputer's bonds return, " +
425			"the filing fee burns"
426	case cs.provClose:
427		return "closed without a decision — three dispute rounds failed quorum; everyone withdraws 1×, deposit and fee refunded"
428	case cs.verdictAt != 0:
429		return "settled " + sideName(int(cs.provisional)) + " — every stake withdraws 1×"
430	case cs.disputeOpen:
431		disputed := int(cs.answer)
432		if cs.provisional >= 0 {
433			disputed = int(cs.provisional)
434		}
435		return "disputed " + sideName(disputed) +
436			" — a sealed vote is deciding; principal is never withheld"
437	case cs.provisional >= 0:
438		return "provisional verdict " + sideName(int(cs.provisional)) +
439			" — reopenable by a new dispute until block " + strconv.FormatInt(cs.escrowUntil, 10) +
440			"; the side it is against may withdraw 1× now"
441	case cs.frozenAt != 0:
442		return "answered " + sideName(int(cs.answer)) +
443			" — staking frozen; disputable until block " +
444			strconv.FormatInt(cs.answerHeight+settleDelay, 10) + ", then it settles undisputed"
445	default:
446		return "open — stake YES or NO; unstake freely until an answer posts"
447	}
448}
449func pct(num, den int64) string {
450	if den <= 0 || num < 0 {
451		return "—"
452	}
453	bps := mulDiv128(num, 10000, den)
454	return strconv.FormatInt(bps/100, 10) + "." + pad2(bps%100) + "%"
455}
456func pad2(v int64) string {
457	if v < 10 {
458		return "0" + strconv.FormatInt(v, 10)
459	}
460	return strconv.FormatInt(v, 10)
461}
462func writeFolderIndex(b *strings.Builder, c *Court, slug string) {
463	if c.mod == nil {
464		return
465	}
466	shown := 0
467	for _, f := range c.mod.folderRows() {
468		if f.parent != 0 || f.retired || f.purged {
469			continue
470		}
471		if shown == 0 {
472			b.WriteString("Filed under: ")
473		} else {
474			b.WriteString(" · ")
475		}
476		b.WriteString("[" + sanitize.InlineText(f.name) + "](/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" +
477			slug + "/folder/" + strconv.FormatUint(f.id, 10) + ")")
478		shown++
479	}
480	if shown > 0 {
481		b.WriteString("\n\n")
482	}
483}
484func renderFolderPage(slug string, fid uint64) string {
485	if !Exists(slug) {
486		return "## Not found\nNo court by that slug."
487	}
488	c := mustCourt(slug)
489	if c.mod == nil {
490		return "## Not found\nThis court has no folders."
491	}
492	v := c.mod.folders.Get(beClaimKey(fid))
493	if v == nil {
494		return "## Not found\nNo folder by that id."
495	}
496	f := v.(*folder)
497	base := "/r/g1ecsuj0q572jr0dhu29q9njtnmw03hyu7tyyvv6/kourt:" + slug
498	var b strings.Builder
499	if f.purged {
500		b.WriteString("# [purged:" + sanitize.InlineText(f.code) + "]\n\n")
501		b.WriteString("_This heading was purged by the global DAO. Its name and " +
502			"description were erased, not hidden._\n\n")
503		b.WriteString("[← " + courtNameFor(c) + "](" + base + ")\n")
504		return b.String()
505	}
506	b.WriteString("# " + sanitize.InlineText(f.name) + "\n\n")
507	b.WriteString("[← " + courtNameFor(c) + "](" + base + ")")
508	for up, n := f.parent, 0; up != 0 && n <= maxFolders; n++ {
509		pv := c.mod.folders.Get(beClaimKey(up))
510		if pv == nil {
511			break
512		}
513		pf := pv.(*folder)
514		b.WriteString(" · [" + sanitize.InlineText(pf.name) + "](" + base +
515			"/folder/" + strconv.FormatUint(pf.id, 10) + ")")
516		up = pf.parent
517	}
518	b.WriteString("\n\n")
519	if f.retired {
520		b.WriteString("> This heading was retired by a moderator. It is kept so " +
521			"links to it still resolve.\n\n")
522	}
523	if f.bornOf != 0 {
524		b.WriteString("> The court voted this heading into existence: [claim #" +
525			strconv.FormatUint(f.bornOf, 10) + "](" + base + "/" +
526			strconv.FormatUint(f.bornOf, 10) + ") settled YES, and carrying it was " +
527			"a second act anyone could take.\n\n")
528	}
529	if f.desc != "" {
530		b.WriteString(sanitize.Block(f.desc) + "\n")
531	}
532	kids := 0
533	for _, k := range c.mod.folderRows() {
534		if k.parent != f.id || k.retired || k.purged {
535			continue
536		}
537		if kids == 0 {
538			b.WriteString("## Under this heading\n\n")
539		}
540		kids++
541		b.WriteString("- [" + sanitize.InlineText(k.name) + "](" + base + "/folder/" +
542			strconv.FormatUint(k.id, 10) + ")\n")
543	}
544	if kids > 0 {
545		b.WriteString("\n")
546	}
547	b.WriteString("## Claims filed here\n\n")
548	filed := 0
549	for _, id := range f.items {
550		if id == 0 || id > c.nextID {
551			continue
552		}
553		cs := mustClaim(c, id)
554		filed++
555		b.WriteString("- [" + claimTitleFor(c, cs) + "](" + base + "/" +
556			strconv.FormatUint(id, 10) + ") — " + claimStatus(cs) + "\n")
557	}
558	if filed == 0 {
559		b.WriteString("_Nothing filed here yet._\n")
560	}
561	b.WriteString("\n_A folder curates and can never bury: every claim above is " +
562		"also on the court's own newest-first list, and nothing is reachable only " +
563		"from here._\n\n")
564	b.WriteString(helpLink + "\n")
565	return b.String()
566}