/// /** * Journal viewer pattern. * Displays a rolling narrative stream of journal entries. * * Access via: wish({ query: "#journal" }) */ import { Cell, computed, handler, NAME, pattern, UI, wish } from "commontools"; // Raw journal entry as stored - subject is a cell link, not a Cell type JournalEntry = { timestamp?: number; eventType?: string; subject?: { cell: { "/": string }; path: string[] }; snapshot?: { name?: string; schemaTag?: string; valueExcerpt?: string; }; narrative?: string; tags?: string[]; space?: string; }; function formatTimestamp(timestamp: number | undefined): string { if (!timestamp) return ""; const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return "just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString(); } function eventTypeLabel(eventType: string | undefined): string { if (!eventType) return "Unknown"; const labels: Record = { "charm:favorited": "Favorited", "charm:unfavorited": "Unfavorited", "charm:created": "Created", "charm:modified": "Modified", "space:entered": "Entered space", }; return labels[eventType] || eventType; } function eventTypeEmoji(eventType: string | undefined): string { if (!eventType) return "\u2022"; const emojis: Record = { "charm:favorited": "\u2b50", "charm:unfavorited": "\u2606", "charm:created": "\u2795", "charm:modified": "\u270f\ufe0f", "space:entered": "\u27a1\ufe0f", }; return emojis[eventType] || "\u2022"; } const clearJournal = handler< Record, { journal: Cell } >((_, { journal }) => { journal.set([]); }); export default pattern>((_) => { const journalResult = wish>({ query: "#journal", }); // Debug: log raw result const debugRaw = computed(() => { const raw = journalResult.result; console.log("[journal.tsx] raw journalResult.result:", raw); console.log("[journal.tsx] type:", typeof raw); if (Array.isArray(raw)) { console.log("[journal.tsx] first entry:", raw[0]); } return JSON.stringify(raw, null, 2); }); // Most recent entries first const entries = computed(() => { const journal = journalResult.result || []; return [...journal].reverse(); }); const entryCount = computed(() => entries.length); return { [NAME]: "Journal", [UI]: (

Activity Journal

{entryCount > 0 && ( Clear Journal )}
{/* Debug section */}
Debug: Raw Data
            {debugRaw}
          
{entryCount === 0 && (

No journal entries yet.

Favorite some charms to start building your journal.

)}
{entries.map((entry) => (
{eventTypeEmoji(entry.eventType)} {eventTypeLabel(entry.eventType)} {formatTimestamp(entry.timestamp)}
{entry.narrative && (

{entry.narrative}

)} {entry.snapshot?.name && (
{entry.snapshot.name}:{" "} {entry.subject && }
)} {entry.tags && entry.tags.length > 0 && (
{entry.tags.map((tag) => ( {tag} ))}
)}
))}
), }; });