refactor(bookmarks): never default timestamps to now; allow nulls and sort nulls last; render empty when missing

This commit is contained in:
Gigi
2025-10-22 13:04:24 +02:00
parent 538cbd2296
commit 2e955e9bed
12 changed files with 36 additions and 34 deletions

View File

@@ -112,10 +112,10 @@ export const CardView: React.FC<CardViewProps> = ({
title="Open event in search"
onClick={(e) => e.stopPropagation()}
>
{formatDate(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}
{formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)}
</a>
) : (
<span className="bookmark-date">{formatDate(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}</span>
<span className="bookmark-date">{formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)}</span>
)}
</div>

View File

@@ -73,7 +73,7 @@ export const CompactView: React.FC<CompactViewProps> = ({
<code>{bookmark.id.slice(0, 12)}...</code>
</div>
)}
<span className="bookmark-date-compact">{formatDateCompact(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}</span>
<span className="bookmark-date-compact">{formatDateCompact(bookmark.created_at ?? bookmark.listUpdatedAt)}</span>
{/* CTA removed */}
</div>

View File

@@ -144,7 +144,7 @@ export const LargeView: React.FC<LargeViewProps> = ({
className="bookmark-date-link"
onClick={(e) => e.stopPropagation()}
>
{formatDate(bookmark.created_at || bookmark.listUpdatedAt || Math.floor(Date.now() / 1000))}
{formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)}
</a>
)}

View File

@@ -419,7 +419,7 @@ const Me: React.FC<MeProps> = ({
const mockEvent = {
id: item.id,
pubkey: item.author || '',
created_at: item.readingTimestamp || Math.floor(Date.now() / 1000),
created_at: item.readingTimestamp || 0,
kind: 1,
tags: [] as string[][],
content: item.title || item.url || 'Untitled',

View File

@@ -343,7 +343,7 @@ class BookmarkController {
...b,
tags: b.tags || [],
content: b.content || this.externalEventStore?.getEvent(b.id)?.content || '', // Fallback to eventStore content
created_at: b.created_at || this.externalEventStore?.getEvent(b.id)?.created_at || b.created_at
created_at: (b.created_at ?? this.externalEventStore?.getEvent(b.id)?.created_at ?? null)
}))
const sortedBookmarks = enriched
@@ -352,9 +352,10 @@ class BookmarkController {
urlReferences: extractUrlsFromContent(b.content)
}))
.sort((a, b) => {
// Sort by listUpdatedAt (timestamp of bookmark list event = proxy for when bookmarked)
// Newest first (descending)
return (b.listUpdatedAt || 0) - (a.listUpdatedAt || 0)
// Sort by listUpdatedAt desc, nulls last
const aTs = a.listUpdatedAt ?? -Infinity
const bTs = b.listUpdatedAt ?? -Infinity
return bTs - aTs
})
// DEBUG: Show top 5 sorted bookmarks
@@ -370,7 +371,7 @@ class BookmarkController {
title: `Bookmarks (${sortedBookmarks.length})`,
url: '',
content: latestContent,
created_at: newestCreatedAt || Math.floor(Date.now() / 1000),
created_at: newestCreatedAt || 0,
tags: allTags,
bookmarkCount: sortedBookmarks.length,
eventReferences: allTags.filter((tag: string[]) => tag[0] === 'e').map((tag: string[]) => tag[1]),

View File

@@ -81,7 +81,7 @@ export const processApplesauceBookmarks = (
allItems.push({
id: note.id,
content: '',
created_at: note.created_at || 0,
created_at: note.created_at ?? null,
pubkey: note.author || activeAccount.pubkey,
kind: 1, // Short note kind
tags: [],
@@ -101,14 +101,14 @@ export const processApplesauceBookmarks = (
allItems.push({
id: coordinate,
content: '',
created_at: article.created_at || 0,
created_at: article.created_at ?? null,
pubkey: article.pubkey,
kind: article.kind, // Usually 30023 for long-form articles
tags: [],
parsedContent: undefined,
type: 'event' as const,
isPrivate,
listUpdatedAt: parentCreatedAt || 0
listUpdatedAt: parentCreatedAt ?? null
})
})
}
@@ -126,7 +126,7 @@ export const processApplesauceBookmarks = (
parsedContent: undefined,
type: 'event' as const,
isPrivate,
listUpdatedAt: parentCreatedAt || 0
listUpdatedAt: parentCreatedAt ?? null
})
})
}
@@ -159,14 +159,14 @@ export const processApplesauceBookmarks = (
return {
id: bookmark.id!,
content: bookmark.content || '',
created_at: bookmark.created_at || 0,
created_at: bookmark.created_at ?? null,
pubkey: activeAccount.pubkey,
kind: bookmark.kind || 30001,
tags: bookmark.tags || [],
parsedContent: bookmark.content ? (getParsedContent(bookmark.content) as ParsedContent) : undefined,
type: 'event' as const,
isPrivate,
listUpdatedAt: parentCreatedAt || 0
listUpdatedAt: parentCreatedAt ?? null
}
})

View File

@@ -136,7 +136,7 @@ export async function collectBookmarksFromEvents(
publicItemsAll.push({
id: evt.id,
content: evt.content || '',
created_at: evt.created_at || Math.floor(Date.now() / 1000),
created_at: evt.created_at ?? null,
pubkey: evt.pubkey,
kind: evt.kind,
tags: evt.tags || [],
@@ -148,7 +148,7 @@ export async function collectBookmarksFromEvents(
setTitle,
setDescription,
setImage,
listUpdatedAt: evt.created_at || Math.floor(Date.now() / 1000)
listUpdatedAt: evt.created_at ?? null
})
continue
}

View File

@@ -76,7 +76,7 @@ export async function fetchAllReads(
source: 'bookmark',
type: 'article',
readingProgress: 0,
readingTimestamp: bookmark.created_at
readingTimestamp: bookmark.created_at ?? undefined
}
readsMap.set(coordinate, item)
if (onItem) emitItem(item)

View File

@@ -32,7 +32,7 @@ export interface IndividualBookmark {
id: string
content: string
// Timestamp when the content was created (from the content event itself)
created_at: number
created_at: number | null
pubkey: string
kind: number
tags: string[][]
@@ -51,7 +51,7 @@ export interface IndividualBookmark {
setImage?: string
// Timestamp of the bookmark list event (best proxy for "when bookmarked")
// Note: This is imperfect - it's when the list was last updated, not necessarily when this item was added
listUpdatedAt?: number
listUpdatedAt?: number | null
}
export interface ActiveAccount {

View File

@@ -4,16 +4,16 @@ import { ParsedContent, ParsedNode, IndividualBookmark } from '../types/bookmark
import ResolvedMention from '../components/ResolvedMention'
// Note: RichContent is imported by components directly to keep this file component-only for fast refresh
export const formatDate = (timestamp: number) => {
const safe = typeof timestamp === 'number' && isFinite(timestamp) && timestamp > 0 ? timestamp : Math.floor(Date.now() / 1000)
const date = new Date(safe * 1000)
export const formatDate = (timestamp: number | null | undefined) => {
if (!timestamp || !isFinite(timestamp) || timestamp <= 0) return ''
const date = new Date(timestamp * 1000)
return formatDistanceToNow(date, { addSuffix: true })
}
// Ultra-compact date format for tight spaces (e.g., compact view)
export const formatDateCompact = (timestamp: number) => {
const safe = typeof timestamp === 'number' && isFinite(timestamp) && timestamp > 0 ? timestamp : Math.floor(Date.now() / 1000)
const date = new Date(safe * 1000)
export const formatDateCompact = (timestamp: number | null | undefined) => {
if (!timestamp || !isFinite(timestamp) || timestamp <= 0) return ''
const date = new Date(timestamp * 1000)
const now = new Date()
const seconds = differenceInSeconds(now, date)
@@ -89,7 +89,11 @@ export const renderParsedContent = (parsedContent: ParsedContent) => {
export const sortIndividualBookmarks = (items: IndividualBookmark[]) => {
return items
.slice()
.sort((a, b) => (b.listUpdatedAt || 0) - (a.listUpdatedAt || 0))
.sort((a, b) => {
const aTs = a.listUpdatedAt ?? -Infinity
const bTs = b.listUpdatedAt ?? -Infinity
return bTs - aTs
})
}
export function groupIndividualBookmarks(items: IndividualBookmark[]) {
@@ -124,10 +128,7 @@ export function hasContent(bookmark: IndividualBookmark): boolean {
export function hasCreationDate(bookmark: IndividualBookmark): boolean {
if (!bookmark.created_at) return false
// If timestamp is missing or equals current time (within 1 second), consider it invalid
const now = Math.floor(Date.now() / 1000)
const createdAt = Math.floor(bookmark.created_at)
// If created_at is within 1 second of now, it's likely missing/placeholder
return Math.abs(createdAt - now) > 1
return true
}
// Bookmark sets helpers (kind 30003)

View File

@@ -51,7 +51,7 @@ export function deriveLinksFromBookmarks(bookmarks: Bookmark[]): ReadItem[] {
summary,
image,
readingProgress: 0,
readingTimestamp: bookmark.created_at
readingTimestamp: bookmark.created_at ?? undefined
}
linksMap.set(url, item)

View File

@@ -49,7 +49,7 @@ export function deriveReadsFromBookmarks(bookmarks: Bookmark[]): ReadItem[] {
source: 'bookmark',
type: 'article',
readingProgress: 0,
readingTimestamp: bookmark.created_at,
readingTimestamp: bookmark.created_at ?? undefined,
title,
summary,
image,