A `nft: "give"` message is a change of custody, not a thing. It has no photo of its own, so it borrowed the photo of the item it transferred - which meant the same object appeared on the grid twice, a few cards apart, distinguished only by a dimmed image and an id overlay. That reads as a duplicate, not as a hand-off. Custody is better answered one level down. Each card already links to its item's thread, where the mint and every hand-off since are in order, with the steward resolved. So the overlay was showing a truncated feed id on the grid to save a click that is worth making. Filters on nft === "mint" rather than excluding gives: every custodisco message carries one of the two - checked across all 407 - so matching mint is exact. 301 items from 3 feeds, down from 407 entries. The feed count now counts feeds that actually contributed an item rather than the size of the follow set. The pub follows itself and publishes no items, so the old number was one too high and would have drifted further as feeds get followed for other reasons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0192zBTNZKZn5svyJ5HTnYds
custo-viewer
The web view behind www.cust.ooo — a fork of ssb-viewer that renders custo items from a Scuttlebutt pub.
Upstream's own docs are kept verbatim in UPSTREAM-README.md.
What custo adds
/items |
One merged grid of every item the pub knows about, across all kiosks. New kiosks appear by being followed — no code change. |
| known-blob gate | serveBlob only serves blobs referenced by a feed we replicate, so the node never hands out a stranger's cached blob. |
| exit on dead sbot | bin.js exits when its muxrpc handle dies, instead of holding the port open and hanging every request forever. |
blob-wanter.js |
Standing blobs.want orders for our own feeds' blobs, replacing what ssb-blobs sympathy used to do before it was turned off. |
The data model
An item is an ordinary type: "post" carrying custo's own fields:
{ "type": "post", "custodisco": "true", "nft": "mint",
"text": "\n\n…\n\n a #custodisco item ",
"mentions": [{ "name": "photo.jpg", "type": "image/jpeg", "link": "&…sha256" }] }
A transfer of custody is a reply to that message:
{ "type": "post", "custodisco": "true", "nft": "give",
"target": "@…ed25519", "root": "%…sha256", "branch": "%…sha256" }
Two things to know before writing a query against this:
custodiscois the string"true", not a boolean.- No message ever sets
content.channel. The#custodiscohashtag exists only in post text. Anything keyed on the channel index will silently return nothing — which is why/channel/custodiscorenders an empty page.
Messages are published by the kiosks (/home/trav/custodisco-kiosk/ssb-post.sh), not by
this viewer. The viewer is read-only.
Running it
bin.js connects to a local ssb-server and serves on conf.viewer.port (8807).
See UPSTREAM-README.md for the plugin-vs-standalone options.
Deployment
Deployed by rsync from a laptop, not by git pull — the server holds no push
credentials, and one deploy path is better than two. The script, the systemd units, the
nginx config, and the disaster-recovery runbook all live in the ops repo alongside this
one (documents/custo/ssb-viewer), which is the source of truth for the server itself.
Never commit secrets here. Keys and server config belong in the ops repo.
AGPL-3.0+, inherited from upstream.