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 realm

Package kitindexdemo is a notes board whose only job is to show gno.land/p/moul/kit/index doing the thing a realm wit...

Readme View source

kitindexdemo

A notes board whose only job is to show p/moul/kit/index doing the thing a realm with a store always ends up needing: answering "every note tagged gno" as cheaply as it answers "note 7".

No logic of its own. The records are a kit/store, the two lookups are kit/index, the page is kit/ui.

What is worth reading

Every write touches the store and both indexes in ONE function. That is the entire correctness argument for keeping three containers apart instead of reaching for a multi-index store: an abort anywhere in Post leaves none of the three applied, and Retract unwinds all three together.

The tag is validated at write time, the body is escaped at render time, and the difference is the point. A tag is also an index key and a path segment, so a tag carrying a pipe or a slash breaks the table and the URL as well as the sentence; [a-z0-9-], checked once, is narrower and cheaper than escaping it in three places. A body is prose, so it goes through ui.Cell at the one call site that renders it.

The root page is pinned with uassert.Equal, not an example. It carries two consecutive blank lines, and an // Output: block can never pin those: gno collapses them, exactly as Go does. The pages without that shape get an ExampleRender.

Pages

path shows
`` every tag with its count, paged
tag/<tag> the notes under one tag
author/<address> the notes by one address

Functions

Post(cur, tag, body) int64 files a note, returns its id
Retract(cur, id) removes one, author only

MaxBody is 280 bytes and MaxTagLen is 24, because every byte stored locks a storage deposit this realm's address pays and an unbounded write is an unbounded bill a stranger chooses the size of.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

On mainnet: deployment status transactions unique callers deployed revision

Dependency graph:

gno.land/r/moul/x/kitindexdemo/v0 dependency graph

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.

Overview

Package kitindexdemo is a notes board whose only job is to show gno.land/p/moul/kit/index doing the thing a realm with a store always ends up needing: answering "every note tagged gno" as cheaply as it answers "note 7".

There is no logic of its own here. The records are a kit/store, the two lookups are kit/index, the page is kit/ui, and what is worth reading is the shape: every write touches the store and both indexes in ONE function, which is the entire correctness argument for keeping them in separate containers.

Demo of the p/moul/kit/index library.

Constants 4

const MaxNotes, MaxPerAuthor

1const (
2	MaxNotes     = 1000
3	MaxPerAuthor = 20
4)
source

MaxNotes and MaxPerAuthor bound the WHOLE board, which MaxBody does not.

A per-call length limit bounds one write and nothing else: a caller posting one-byte notes in a loop grows the store and both indexes without limit. "An unbounded container a third party can grow is an unbounded storage deposit someone is paying for" is a rule this realm exists to demonstrate, so it had better obey it.

Two caps rather than one, and what each buys is narrower than it looks. MaxNotes bounds the state, which is the property this realm promises. MaxPerAuthor stops one ADDRESS, not one actor: addresses are free, so fifty fresh ones at twenty notes each still fill the board and lock everyone else out until somebody retracts. Keeping a board open to strangers needs something a griefer cannot mint for free, a deposit or an expiry, which this demo does not carry. Retracting frees a slot, so neither cap is a one-way door.

MaxPerAuthor is answered by byAuthor.Count, which is the index doing the job it was added for.

const MaxBody

1const MaxBody = 280
source

MaxBody is the longest note this realm accepts, in bytes.

A bound rather than none: every byte stored here locks a storage deposit, paid by whoever signs the write and refunded to whoever signs the delete (EFFECTIVE_GNO.md section 9.2), and an unbounded write is an unbounded state the realm carries forever.

const MaxTagLen

1const MaxTagLen = 24
source

MaxTagLen bounds the index key for the same reason, and small because a tag is a label rather than a sentence.

const PageSize

1const PageSize = 20
source

PageSize is how many tags the root page shows.

Functions 3

func Post

crossing Action
1func Post(cur realm, tag, body string) int64
source

Post files a note under a tag and returns its id.

The store write and both index writes happen here, in this order, in one frame. An abort anywhere in it leaves none of them applied, which is what makes three containers safe to keep apart.

func Render

1func Render(path string) string
source

Render is the whole public surface. Three pages: the tag list, one tag, one author.

Every row goes through ui.NewTable rather than concatenated pipes, and every caller string through ui.Cell, because a public path is immutable and a Render that ships wrong ships forever.

func Retract

crossing Action
1func Retract(cur realm, id int64)
source

Retract removes a note. Only its author may, and the store and both indexes are unwound together.

Imports 8

Source Files 6