/items shows items only, not custody transfers

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
This commit is contained in:
2026-08-22 20:30:46 -04:00
co-authored by Claude Opus 5
parent cc0ad8c1a8
commit fc9539a618
3 changed files with 37 additions and 76 deletions
+9 -33
View File
@@ -392,18 +392,6 @@ var styles = `
text-align: center;
}
.item-photo-missing::after { content: "photo not fetched yet"; }
.item-card-give .item-photo { opacity: 0.55; }
.item-badge {
position: absolute;
left: 0; right: 0; bottom: 0;
padding: 6px 8px;
background-color: rgba(33, 37, 41, 0.82);
color: #f1f3f5;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-meta { padding: 10px 12px; display: flex; flex-direction: column; gap: 3px; }
.item-caption {
line-height: 1.35em;
@@ -721,8 +709,9 @@ function renderShowAll(showAll, url) {
// The custo item grid (/items)
//
// Cards, not articles: this is a collection of things, and a single-column feed
// of 300 photos reads like a mailing list rather than an archive. A transfer
// borrows the photo of the item it transfers, so the grid stays regular.
// of 300 photos reads like a mailing list rather than an archive. Items only -
// who holds an item is answered by its thread, one click in, not by an overlay
// on the grid.
// ---------------------------------------------------------------------------
function renderItemGrid(opts, meta) {
@@ -734,7 +723,7 @@ function renderItemGrid(opts, meta) {
}
function itemsHeader(opts, meta) {
var counted = meta.total === 1 ? '1 item' : meta.total + ' items and transfers'
var counted = meta.total === 1 ? '1 item' : meta.total + ' items'
var feeds = meta.feeds === 1 ? '1 feed' : meta.feeds + ' feeds'
return h('header.items-header',
h('h1', 'custo items'),
@@ -758,11 +747,6 @@ function itemsFooter(opts, meta) {
function renderItemCard(opts, msg) {
var c = msg.value.content
var isGive = c.nft === 'give'
// A transfer links to the item it transfers, not to itself: the thread page
// shows the mint and every hand-off since, which is the interesting view.
var target = isGive && c.root ? c.root : msg.key
var media
if (msg.itemPhoto) {
@@ -779,15 +763,12 @@ function renderItemCard(opts, msg) {
media = h('div.item-photo.item-photo-missing')
}
var badge = isGive
? h('span.item-badge', '\u2192 ' + stewardName(msg, c))
: ''
var caption = msg.itemCaption || itemFallbackCaption(c)
var caption = msg.itemCaption || (isGive ? 'an item' : itemFallbackCaption(c))
return h('a.item-card' + (isGive ? '.item-card-give' : ''),
{ href: opts.base + encodeURIComponent(target) },
h('div.item-media', media, badge),
// Links to the item's own thread: the mint, and every hand-off since.
return h('a.item-card',
{ href: opts.base + encodeURIComponent(msg.key) },
h('div.item-media', media),
h('div.item-meta',
h('span.item-caption', caption),
h('span.item-by', msg.author && msg.author.name),
@@ -795,11 +776,6 @@ function renderItemCard(opts, msg) {
).outerHTML
}
function stewardName(msg, c) {
if (msg.stewardAbout && msg.stewardAbout.name) return msg.stewardAbout.name
return String(c.target || 'someone').substr(0, 10) + '\u2026'
}
function itemFallbackCaption(c) {
return String(c.text || '').replace(/\s+/g, ' ').trim().substr(0, 140) || 'untitled'
}