experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(cards): show item age in card footer

Adds a small "today / Nd / Nmo / Ny" badge next to the visit count
on every card built via `createSearchResultCard` (search results,
tag views, list views, pagestream, groups). Falls back to nothing
when `item.createdAt` isn't a usable timestamp; absolute date is
in the title attribute on hover.

Helps surface stale items at a glance without consuming a
dedicated row.

+31
+31
app/lib/search-result-card.js
··· 18 18 createAffordanceElements 19 19 } from 'peek://app/lib/tag-action-affordances.js'; 20 20 21 + /** 22 + * Format an item's age (ms-since-creation) as a short relative string. 23 + * Examples: "today", "1d", "29d", "1mo", "11mo", "2y". 24 + * Returns null when no usable timestamp is provided. 25 + */ 26 + const formatAge = (createdAtMs) => { 27 + if (typeof createdAtMs !== 'number' || !isFinite(createdAtMs) || createdAtMs <= 0) return null; 28 + const ms = Date.now() - createdAtMs; 29 + if (ms < 0) return 'today'; 30 + const days = Math.floor(ms / 86400000); 31 + if (days < 1) return 'today'; 32 + if (days < 30) return `${days}d`; 33 + const months = Math.floor(days / 30); 34 + if (months < 12) return `${months}mo`; 35 + const years = Math.floor(days / 365); 36 + return `${years}y`; 37 + }; 38 + 21 39 // Open button SVG icon (external link) 22 40 const OPEN_BUTTON_SVG = 23 41 '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + ··· 159 177 visitSpan.textContent = `${visitCount} ${visitCount === 1 ? 'visit' : 'visits'}`; 160 178 visitSpan.style.flexShrink = '0'; 161 179 footerLeft.appendChild(visitSpan); 180 + } 181 + 182 + // Age indicator — bottom left, after visit count. Short relative format 183 + // (today / Nd / Nmo / Ny) with absolute date in the title attribute for 184 + // hover precision. Helps surface stale items without consuming row space. 185 + const ageLabel = formatAge(item.createdAt); 186 + if (ageLabel) { 187 + const ageSpan = document.createElement('span'); 188 + ageSpan.className = 'card-age'; 189 + ageSpan.textContent = ageLabel; 190 + ageSpan.title = new Date(item.createdAt).toLocaleString(); 191 + ageSpan.style.flexShrink = '0'; 192 + footerLeft.appendChild(ageSpan); 162 193 } 163 194 164 195 // For non-URL items that have a subtitle (e.g. entity type), show it on the left