From 8ffd0fd2cb68e986948f257e27584df52d39d68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Wed, 10 Dec 2025 22:32:36 +0100 Subject: [PATCH] feat: timestamps and user locale --- src/components/EventDetailViewer.tsx | 2 +- src/components/JsonViewer.tsx | 2 +- .../nostr/kinds/BaseEventRenderer.tsx | 38 +++--- src/components/nostr/kinds/Kind0Renderer.tsx | 4 +- .../nostr/kinds/Kind1063Renderer.tsx | 4 +- src/components/nostr/kinds/Kind1Renderer.tsx | 20 +--- src/components/nostr/kinds/Kind20Renderer.tsx | 4 +- src/components/nostr/kinds/Kind21Renderer.tsx | 4 +- src/components/nostr/kinds/Kind22Renderer.tsx | 4 +- .../nostr/kinds/Kind30023Renderer.tsx | 4 +- src/components/nostr/kinds/Kind3Renderer.tsx | 4 +- src/components/nostr/kinds/Kind6Renderer.tsx | 4 +- src/components/nostr/kinds/Kind7Renderer.tsx | 4 +- .../nostr/kinds/Kind9735Renderer.tsx | 6 +- .../nostr/kinds/Kind9802Renderer.tsx | 4 +- src/components/nostr/kinds/index.tsx | 8 +- src/core/state.ts | 8 ++ src/hooks/useLocale.ts | 113 ++++++++++++++++++ src/types/app.ts | 7 ++ tsconfig.app.tsbuildinfo | 2 +- 20 files changed, 183 insertions(+), 63 deletions(-) create mode 100644 src/hooks/useLocale.ts diff --git a/src/components/EventDetailViewer.tsx b/src/components/EventDetailViewer.tsx index 219ad81..c02c1e9 100644 --- a/src/components/EventDetailViewer.tsx +++ b/src/components/EventDetailViewer.tsx @@ -175,7 +175,7 @@ export function EventDetailViewer({ pointer }: EventDetailViewerProps) { ) : event.kind === 9802 ? ( ) : ( - + )} diff --git a/src/components/JsonViewer.tsx b/src/components/JsonViewer.tsx index ed1632c..d34670a 100644 --- a/src/components/JsonViewer.tsx +++ b/src/components/JsonViewer.tsx @@ -58,7 +58,7 @@ export function JsonViewer({
-
+          
             {jsonString}
           
diff --git a/src/components/nostr/kinds/BaseEventRenderer.tsx b/src/components/nostr/kinds/BaseEventRenderer.tsx index ffd2478..8167b1f 100644 --- a/src/components/nostr/kinds/BaseEventRenderer.tsx +++ b/src/components/nostr/kinds/BaseEventRenderer.tsx @@ -13,13 +13,13 @@ import { import { Menu, Copy, FileJson, ExternalLink } from "lucide-react"; import { useGrimoire } from "@/core/state"; import { JsonViewer } from "@/components/JsonViewer"; +import { formatTimestamp } from "@/hooks/useLocale"; /** * Universal event properties and utilities shared across all kind renderers */ export interface BaseEventProps { event: NostrEvent; - showTimestamp?: boolean; depth?: number; } @@ -126,35 +126,37 @@ export function EventMenu({ event }: { event: NostrEvent }) { * Base event container with universal header * Kind-specific renderers can wrap their content with this */ +/** + * Format relative time (e.g., "2m ago", "3h ago", "5d ago") + */ + export function BaseEventContainer({ event, children, - showTimestamp = false, }: { event: NostrEvent; children: React.ReactNode; - showTimestamp?: boolean; }) { - // Format timestamp - const timestamp = new Date(event.created_at * 1000).toLocaleString("en-US", { - month: "2-digit", - day: "2-digit", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - }); + // Format relative time for display + const { locale } = useGrimoire(); + const relativeTime = formatTimestamp(event.created_at, "relative", locale.locale); + + // Format absolute timestamp for hover (ISO-8601 style) + const absoluteTime = formatTimestamp(event.created_at, "absolute", locale.locale); return (
- - {showTimestamp ? ( - - {timestamp} +
+ + + {relativeTime} - ) : ( - - )} +
+
{children}
diff --git a/src/components/nostr/kinds/Kind0Renderer.tsx b/src/components/nostr/kinds/Kind0Renderer.tsx index ca5b401..779ffd5 100644 --- a/src/components/nostr/kinds/Kind0Renderer.tsx +++ b/src/components/nostr/kinds/Kind0Renderer.tsx @@ -8,7 +8,7 @@ import { RichText } from "../RichText"; * Renderer for Kind 0 - Profile Metadata * Displays as a compact profile card in feed view */ -export function Kind0Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind0Renderer({ event }: BaseEventProps) { const pubkey = event.pubkey; const profile = useProfile(pubkey); @@ -16,7 +16,7 @@ export function Kind0Renderer({ event, showTimestamp }: BaseEventProps) { const website = profile?.website; return ( - +
{/* Profile Info */}
diff --git a/src/components/nostr/kinds/Kind1063Renderer.tsx b/src/components/nostr/kinds/Kind1063Renderer.tsx index e553c24..40f6e2d 100644 --- a/src/components/nostr/kinds/Kind1063Renderer.tsx +++ b/src/components/nostr/kinds/Kind1063Renderer.tsx @@ -14,7 +14,7 @@ import { FileText, Download } from "lucide-react"; * Renderer for Kind 1063 - File Metadata (NIP-94) * Displays file metadata with appropriate preview for images, videos, and audio */ -export function Kind1063Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind1063Renderer({ event }: BaseEventProps) { const metadata = parseFileMetadata(event); // Determine file type from MIME @@ -29,7 +29,7 @@ export function Kind1063Renderer({ event, showTimestamp }: BaseEventProps) { event.tags.find((t) => t[0] === "summary")?.[1] || event.content; return ( - +
{/* File preview */} {metadata.url && (isImage || isVideo || isAudio) ? ( diff --git a/src/components/nostr/kinds/Kind1Renderer.tsx b/src/components/nostr/kinds/Kind1Renderer.tsx index cce4711..71e9b5b 100644 --- a/src/components/nostr/kinds/Kind1Renderer.tsx +++ b/src/components/nostr/kinds/Kind1Renderer.tsx @@ -9,24 +9,16 @@ import { useGrimoire } from "@/core/state"; /** * Renderer for Kind 1 - Short Text Note */ -export function Kind1Renderer({ - event, - showTimestamp, - depth = 0, -}: BaseEventProps) { +export function Kind1Renderer({ event, depth = 0 }: BaseEventProps) { const { addWindow } = useGrimoire(); const refs = getNip10References(event); - const hasReply = refs.reply?.e || refs.reply?.a; - - // Fetch parent event if replying - const parentEvent = useNostrEvent( - hasReply ? refs.reply?.e || refs.reply?.a : undefined, - ); + const pointer = + refs.reply?.e || refs.reply?.a || refs.root?.e || refs.root?.a; + const parentEvent = useNostrEvent(pointer); const handleReplyClick = () => { if (!parentEvent) return; - const pointer = refs.reply?.e || refs.reply?.a; if (pointer) { addWindow( "open", @@ -37,8 +29,8 @@ export function Kind1Renderer({ }; return ( - - {hasReply && parentEvent && ( + + {pointer && parentEvent && (
t[0] === "title")?.[1]; return ( - +
{/* Title if present */} {title && ( diff --git a/src/components/nostr/kinds/Kind21Renderer.tsx b/src/components/nostr/kinds/Kind21Renderer.tsx index 6ec00f5..508293e 100644 --- a/src/components/nostr/kinds/Kind21Renderer.tsx +++ b/src/components/nostr/kinds/Kind21Renderer.tsx @@ -7,7 +7,7 @@ import { parseImetaTags } from "@/lib/imeta"; * Renderer for Kind 21 - Video Event (NIP-71) * Horizontal/landscape video events with imeta tags */ -export function Kind21Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind21Renderer({ event }: BaseEventProps) { // Parse imeta tags to get video URLs and metadata const videos = parseImetaTags(event); @@ -15,7 +15,7 @@ export function Kind21Renderer({ event, showTimestamp }: BaseEventProps) { const title = event.tags.find((t) => t[0] === "title")?.[1]; return ( - +
{/* Title if present */} {title &&

{title}

} diff --git a/src/components/nostr/kinds/Kind22Renderer.tsx b/src/components/nostr/kinds/Kind22Renderer.tsx index a066fa3..f839e0c 100644 --- a/src/components/nostr/kinds/Kind22Renderer.tsx +++ b/src/components/nostr/kinds/Kind22Renderer.tsx @@ -7,7 +7,7 @@ import { parseImetaTags } from "@/lib/imeta"; * Renderer for Kind 22 - Short Video Event (NIP-71) * Short-form portrait video events (like TikTok/Reels) */ -export function Kind22Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind22Renderer({ event }: BaseEventProps) { // Parse imeta tags to get video URLs and metadata const videos = parseImetaTags(event); @@ -15,7 +15,7 @@ export function Kind22Renderer({ event, showTimestamp }: BaseEventProps) { const title = event.tags.find((t) => t[0] === "title")?.[1]; return ( - +
{/* Title if present */} {title &&

{title}

} diff --git a/src/components/nostr/kinds/Kind30023Renderer.tsx b/src/components/nostr/kinds/Kind30023Renderer.tsx index c12cd37..602d4df 100644 --- a/src/components/nostr/kinds/Kind30023Renderer.tsx +++ b/src/components/nostr/kinds/Kind30023Renderer.tsx @@ -9,12 +9,12 @@ import { * Renderer for Kind 30023 - Long-form Article * Displays article title and summary in feed */ -export function Kind30023Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind30023Renderer({ event }: BaseEventProps) { const title = useMemo(() => getArticleTitle(event), [event]); const summary = useMemo(() => getArticleSummary(event), [event]); return ( - +
{/* Title */} {title && ( diff --git a/src/components/nostr/kinds/Kind3Renderer.tsx b/src/components/nostr/kinds/Kind3Renderer.tsx index 409dd4a..0558c75 100644 --- a/src/components/nostr/kinds/Kind3Renderer.tsx +++ b/src/components/nostr/kinds/Kind3Renderer.tsx @@ -7,7 +7,7 @@ import { Users, Sparkles } from "lucide-react"; * Kind 3 Renderer - Contact/Follow List * Shows follow count and "follows you" indicator */ -export function Kind3Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind3Renderer({ event }: BaseEventProps) { const { state } = useGrimoire(); // Extract followed pubkeys from p tags @@ -20,7 +20,7 @@ export function Kind3Renderer({ event, showTimestamp }: BaseEventProps) { : false; return ( - +
diff --git a/src/components/nostr/kinds/Kind6Renderer.tsx b/src/components/nostr/kinds/Kind6Renderer.tsx index fd3784a..8d3d642 100644 --- a/src/components/nostr/kinds/Kind6Renderer.tsx +++ b/src/components/nostr/kinds/Kind6Renderer.tsx @@ -7,7 +7,7 @@ import { useGrimoire } from "@/core/state"; * Renderer for Kind 6 - Reposts * Displays repost indicator with the original event embedded */ -export function Kind6Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind6Renderer({ event }: BaseEventProps) { const { addWindow } = useGrimoire(); // Get the event being reposted (e tag) @@ -15,7 +15,7 @@ export function Kind6Renderer({ event, showTimestamp }: BaseEventProps) { const repostedEventId = eTag?.[1]; return ( - +
diff --git a/src/components/nostr/kinds/Kind7Renderer.tsx b/src/components/nostr/kinds/Kind7Renderer.tsx index 8a7e354..252a7b7 100644 --- a/src/components/nostr/kinds/Kind7Renderer.tsx +++ b/src/components/nostr/kinds/Kind7Renderer.tsx @@ -10,7 +10,7 @@ import { KindRenderer } from "./index"; * Displays emoji/reaction with the event being reacted to * Supports both e tags (event ID) and a tags (address/replaceable events) */ -export function Kind7Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind7Renderer({ event }: BaseEventProps) { // Get the reaction content (usually an emoji) const reaction = event.content || "❤️"; @@ -108,7 +108,7 @@ export function Kind7Renderer({ event, showTimestamp }: BaseEventProps) { }; return ( - +
{/* Reaction indicator */}
diff --git a/src/components/nostr/kinds/Kind9735Renderer.tsx b/src/components/nostr/kinds/Kind9735Renderer.tsx index 2e52124..9542901 100644 --- a/src/components/nostr/kinds/Kind9735Renderer.tsx +++ b/src/components/nostr/kinds/Kind9735Renderer.tsx @@ -18,7 +18,7 @@ import { RichText } from "../RichText"; * Renderer for Kind 9735 - Zap Receipts * Displays zap amount, sender, and zapped content */ -export function Kind9735Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind9735Renderer({ event }: BaseEventProps) { // Validate zap const isValid = useMemo(() => isValidZap(event), [event]); @@ -49,7 +49,7 @@ export function Kind9735Renderer({ event, showTimestamp }: BaseEventProps) { if (!isValid) { return ( - +
Invalid zap receipt
); @@ -65,7 +65,7 @@ export function Kind9735Renderer({ event, showTimestamp }: BaseEventProps) { ); return ( - +
{/* Zap indicator */}
diff --git a/src/components/nostr/kinds/Kind9802Renderer.tsx b/src/components/nostr/kinds/Kind9802Renderer.tsx index f360f9d..96c361b 100644 --- a/src/components/nostr/kinds/Kind9802Renderer.tsx +++ b/src/components/nostr/kinds/Kind9802Renderer.tsx @@ -11,13 +11,13 @@ import { * Renderer for Kind 9802 - Highlight * Displays highlighted text with optional comment and source URL */ -export function Kind9802Renderer({ event, showTimestamp }: BaseEventProps) { +export function Kind9802Renderer({ event }: BaseEventProps) { const highlightText = useMemo(() => getHighlightText(event), [event]); const sourceUrl = useMemo(() => getHighlightSourceUrl(event), [event]); const comment = useMemo(() => getHighlightComment(event), [event]); return ( - +
{/* Comment */} {comment &&

{comment}

} diff --git a/src/components/nostr/kinds/index.tsx b/src/components/nostr/kinds/index.tsx index caba42f..c0ddc0a 100644 --- a/src/components/nostr/kinds/index.tsx +++ b/src/components/nostr/kinds/index.tsx @@ -37,9 +37,9 @@ const kindRenderers: Record> = { * Default renderer for kinds without custom implementations * Shows basic event info with raw content */ -function DefaultKindRenderer({ event, showTimestamp }: BaseEventProps) { +function DefaultKindRenderer({ event }: BaseEventProps) { return ( - +
Kind {event.kind} event
@@ -56,15 +56,13 @@ function DefaultKindRenderer({ event, showTimestamp }: BaseEventProps) {
  */
 export function KindRenderer({
   event,
-  showTimestamp = false,
   depth = 0,
 }: {
   event: NostrEvent;
-  showTimestamp?: boolean;
   depth?: number;
 }) {
   const Renderer = kindRenderers[event.kind] || DefaultKindRenderer;
-  return ;
+  return ;
 }
 
 /**
diff --git a/src/core/state.ts b/src/core/state.ts
index 7d4adec..5b26db1 100644
--- a/src/core/state.ts
+++ b/src/core/state.ts
@@ -1,6 +1,7 @@
 import { useAtom } from "jotai";
 import { atomWithStorage } from "jotai/utils";
 import { GrimoireState, AppId } from "@/types/app";
+import { useLocale } from "@/hooks/useLocale";
 import * as Logic from "./logic";
 
 // Initial State Definition - Empty canvas on first load
@@ -26,9 +27,16 @@ export const grimoireStateAtom = atomWithStorage(
 // The Hook
 export const useGrimoire = () => {
   const [state, setState] = useAtom(grimoireStateAtom);
+  const browserLocale = useLocale();
+
+  // Initialize locale from browser if not set
+  if (!state.locale) {
+    setState((prev) => ({ ...prev, locale: browserLocale }));
+  }
 
   return {
     state,
+    locale: state.locale || browserLocale,
     activeWorkspace: state.workspaces[state.activeWorkspaceId],
     createWorkspace: () => {
       const count = Object.keys(state.workspaces).length + 1;
diff --git a/src/hooks/useLocale.ts b/src/hooks/useLocale.ts
new file mode 100644
index 0000000..f8dcf34
--- /dev/null
+++ b/src/hooks/useLocale.ts
@@ -0,0 +1,113 @@
+import { useMemo } from "react";
+
+export interface LocaleConfig {
+  /** Browser's detected locale (e.g., 'en-US', 'pt-BR', 'ja-JP') */
+  locale: string;
+  /** Language code (e.g., 'en', 'pt', 'ja') */
+  language: string;
+  /** Region code (e.g., 'US', 'BR', 'JP') */
+  region?: string;
+  /** Timezone (e.g., 'America/New_York') */
+  timezone: string;
+  /** 12h or 24h time preference */
+  timeFormat: "12h" | "24h";
+}
+
+/**
+ * Hook to get user's locale preferences from browser
+ * Falls back to en-US if detection fails
+ */
+export function useLocale(): LocaleConfig {
+  return useMemo(() => {
+    // Get browser locale
+    const browserLocale =
+      navigator.language || navigator.languages?.[0] || "en-US";
+
+    // Parse locale into language and region
+    const [language, region] = browserLocale.split("-");
+
+    // Detect timezone
+    const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+    // Detect 12h vs 24h preference by formatting a test date
+    const testDate = new Date(2000, 0, 1, 13, 0); // 1PM
+    const formatted = testDate.toLocaleTimeString(browserLocale, {
+      hour: "numeric",
+    });
+    const timeFormat = formatted.includes("PM") || formatted.includes("AM") ? "12h" : "24h";
+
+    return {
+      locale: browserLocale,
+      language,
+      region,
+      timezone,
+      timeFormat,
+    };
+  }, []);
+}
+
+/**
+ * Format a timestamp according to locale preferences
+ * @param timestamp - Unix timestamp in seconds
+ * @param style - 'relative' for "2h ago", 'absolute' for full date/time, 'date' for date only, 'time' for time only
+ */
+export function formatTimestamp(
+  timestamp: number,
+  style: "relative" | "absolute" | "date" | "time" = "relative",
+  locale?: string,
+): string {
+  const browserLocale = locale || navigator.language || "en-US";
+  const date = new Date(timestamp * 1000);
+
+  if (style === "relative") {
+    const now = Date.now();
+    const diff = now - timestamp * 1000;
+    const seconds = Math.floor(diff / 1000);
+    const minutes = Math.floor(seconds / 60);
+    const hours = Math.floor(minutes / 60);
+    const days = Math.floor(hours / 24);
+    const weeks = Math.floor(days / 7);
+    const months = Math.floor(days / 30);
+    const years = Math.floor(days / 365);
+
+    if (seconds < 60) return `${seconds}s ago`;
+    if (minutes < 60) return `${minutes}m ago`;
+    if (hours < 24) return `${hours}h ago`;
+    if (days < 7) return `${days}d ago`;
+    if (weeks < 4) return `${weeks}w ago`;
+    if (months < 12) return `${months}mo ago`;
+    return `${years}y ago`;
+  }
+
+  if (style === "absolute") {
+    // ISO-8601 style: 2025-12-10 23:42
+    return date
+      .toLocaleString(browserLocale, {
+        year: "numeric",
+        month: "2-digit",
+        day: "2-digit",
+        hour: "2-digit",
+        minute: "2-digit",
+        hour12: false,
+      })
+      .replace(",", "");
+  }
+
+  if (style === "date") {
+    return date.toLocaleDateString(browserLocale, {
+      year: "numeric",
+      month: "2-digit",
+      day: "2-digit",
+    });
+  }
+
+  if (style === "time") {
+    return date.toLocaleTimeString(browserLocale, {
+      hour: "2-digit",
+      minute: "2-digit",
+      hour12: false,
+    });
+  }
+
+  return date.toLocaleString(browserLocale);
+}
diff --git a/src/types/app.ts b/src/types/app.ts
index 3d7141a..8b8d24d 100644
--- a/src/types/app.ts
+++ b/src/types/app.ts
@@ -46,4 +46,11 @@ export interface GrimoireState {
     pubkey: string;
     relays?: UserRelays;
   };
+  locale?: {
+    locale: string;
+    language: string;
+    region?: string;
+    timezone: string;
+    timeFormat: "12h" | "24h";
+  };
 }
diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo
index cee3c77..0b65815 100644
--- a/tsconfig.app.tsbuildinfo
+++ b/tsconfig.app.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/main.tsx","./src/root.tsx","./src/vite-env.d.ts","./src/components/command.tsx","./src/components/commandlauncher.tsx","./src/components/decodeviewer.tsx","./src/components/encodeviewer.tsx","./src/components/eventdetailviewer.tsx","./src/components/grimoirewelcome.tsx","./src/components/home.tsx","./src/components/jsonviewer.tsx","./src/components/kindbadge.tsx","./src/components/kindrenderer.tsx","./src/components/manpage.tsx","./src/components/markdown.tsx","./src/components/niprenderer.tsx","./src/components/profileviewer.tsx","./src/components/reqviewer.tsx","./src/components/tabbar.tsx","./src/components/timestamp.tsx","./src/components/winviewer.tsx","./src/components/windowtoolbar.tsx","./src/components/nostr/embeddedevent.tsx","./src/components/nostr/feed.tsx","./src/components/nostr/mediadialog.tsx","./src/components/nostr/mediaembed.tsx","./src/components/nostr/quotedevent.tsx","./src/components/nostr/richtext.tsx","./src/components/nostr/username.tsx","./src/components/nostr/index.ts","./src/components/nostr/nip05.tsx","./src/components/nostr/npub.tsx","./src/components/nostr/relay-pool.tsx","./src/components/nostr/user-menu.tsx","./src/components/nostr/linkpreview/audiolink.tsx","./src/components/nostr/linkpreview/imagelink.tsx","./src/components/nostr/linkpreview/plainlink.tsx","./src/components/nostr/linkpreview/videolink.tsx","./src/components/nostr/linkpreview/index.ts","./src/components/nostr/richtext/emoji.tsx","./src/components/nostr/richtext/eventembed.tsx","./src/components/nostr/richtext/gallery.tsx","./src/components/nostr/richtext/hashtag.tsx","./src/components/nostr/richtext/link.tsx","./src/components/nostr/richtext/mention.tsx","./src/components/nostr/richtext/text.tsx","./src/components/nostr/richtext/index.ts","./src/components/nostr/kinds/baseeventrenderer.tsx","./src/components/nostr/kinds/kind0detailrenderer.tsx","./src/components/nostr/kinds/kind0renderer.tsx","./src/components/nostr/kinds/kind1063renderer.tsx","./src/components/nostr/kinds/kind1renderer.tsx","./src/components/nostr/kinds/kind20renderer.tsx","./src/components/nostr/kinds/kind21renderer.tsx","./src/components/nostr/kinds/kind22renderer.tsx","./src/components/nostr/kinds/kind30023detailrenderer.tsx","./src/components/nostr/kinds/kind30023renderer.tsx","./src/components/nostr/kinds/kind3renderer.tsx","./src/components/nostr/kinds/kind6renderer.tsx","./src/components/nostr/kinds/kind7renderer.tsx","./src/components/nostr/kinds/kind9735renderer.tsx","./src/components/nostr/kinds/kind9802detailrenderer.tsx","./src/components/nostr/kinds/kind9802renderer.tsx","./src/components/nostr/kinds/index.tsx","./src/components/ui/avatar.tsx","./src/components/ui/button.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/constants/kinds.ts","./src/constants/nips.ts","./src/core/logic.ts","./src/core/state.ts","./src/hooks/useaccountsync.ts","./src/hooks/usenip.ts","./src/hooks/usenip05.ts","./src/hooks/usenostrevent.ts","./src/hooks/useprofile.ts","./src/hooks/usereqtimeline.ts","./src/hooks/usetimeline.ts","./src/lib/decode-parser.ts","./src/lib/encode-parser.ts","./src/lib/imeta.ts","./src/lib/nip-kinds.ts","./src/lib/nip05.ts","./src/lib/nostr-utils.ts","./src/lib/open-parser.ts","./src/lib/profile-parser.ts","./src/lib/req-parser.ts","./src/lib/utils.ts","./src/services/accounts.ts","./src/services/db.ts","./src/services/event-store.ts","./src/services/loaders.ts","./src/services/relay-pool.ts","./src/types/app.ts","./src/types/man.ts","./src/types/nostr.ts","./src/types/profile.ts"],"version":"5.6.3"}
\ No newline at end of file
+{"root":["./src/main.tsx","./src/root.tsx","./src/vite-env.d.ts","./src/components/command.tsx","./src/components/commandlauncher.tsx","./src/components/decodeviewer.tsx","./src/components/encodeviewer.tsx","./src/components/eventdetailviewer.tsx","./src/components/grimoirewelcome.tsx","./src/components/home.tsx","./src/components/jsonviewer.tsx","./src/components/kindbadge.tsx","./src/components/kindrenderer.tsx","./src/components/manpage.tsx","./src/components/markdown.tsx","./src/components/niprenderer.tsx","./src/components/profileviewer.tsx","./src/components/reqviewer.tsx","./src/components/tabbar.tsx","./src/components/timestamp.tsx","./src/components/winviewer.tsx","./src/components/windowtoolbar.tsx","./src/components/nostr/embeddedevent.tsx","./src/components/nostr/feed.tsx","./src/components/nostr/mediadialog.tsx","./src/components/nostr/mediaembed.tsx","./src/components/nostr/quotedevent.tsx","./src/components/nostr/richtext.tsx","./src/components/nostr/username.tsx","./src/components/nostr/index.ts","./src/components/nostr/nip05.tsx","./src/components/nostr/npub.tsx","./src/components/nostr/relay-pool.tsx","./src/components/nostr/user-menu.tsx","./src/components/nostr/linkpreview/audiolink.tsx","./src/components/nostr/linkpreview/imagelink.tsx","./src/components/nostr/linkpreview/plainlink.tsx","./src/components/nostr/linkpreview/videolink.tsx","./src/components/nostr/linkpreview/index.ts","./src/components/nostr/richtext/emoji.tsx","./src/components/nostr/richtext/eventembed.tsx","./src/components/nostr/richtext/gallery.tsx","./src/components/nostr/richtext/hashtag.tsx","./src/components/nostr/richtext/link.tsx","./src/components/nostr/richtext/mention.tsx","./src/components/nostr/richtext/text.tsx","./src/components/nostr/richtext/index.ts","./src/components/nostr/kinds/baseeventrenderer.tsx","./src/components/nostr/kinds/kind0detailrenderer.tsx","./src/components/nostr/kinds/kind0renderer.tsx","./src/components/nostr/kinds/kind1063renderer.tsx","./src/components/nostr/kinds/kind1renderer.tsx","./src/components/nostr/kinds/kind20renderer.tsx","./src/components/nostr/kinds/kind21renderer.tsx","./src/components/nostr/kinds/kind22renderer.tsx","./src/components/nostr/kinds/kind30023detailrenderer.tsx","./src/components/nostr/kinds/kind30023renderer.tsx","./src/components/nostr/kinds/kind3renderer.tsx","./src/components/nostr/kinds/kind6renderer.tsx","./src/components/nostr/kinds/kind7renderer.tsx","./src/components/nostr/kinds/kind9735renderer.tsx","./src/components/nostr/kinds/kind9802detailrenderer.tsx","./src/components/nostr/kinds/kind9802renderer.tsx","./src/components/nostr/kinds/index.tsx","./src/components/ui/avatar.tsx","./src/components/ui/button.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/hover-card.tsx","./src/components/ui/input.tsx","./src/components/ui/scroll-area.tsx","./src/constants/kinds.ts","./src/constants/nips.ts","./src/core/logic.ts","./src/core/state.ts","./src/hooks/useaccountsync.ts","./src/hooks/uselocale.ts","./src/hooks/usenip.ts","./src/hooks/usenip05.ts","./src/hooks/usenostrevent.ts","./src/hooks/useprofile.ts","./src/hooks/usereqtimeline.ts","./src/hooks/usetimeline.ts","./src/lib/decode-parser.ts","./src/lib/encode-parser.ts","./src/lib/imeta.ts","./src/lib/nip-kinds.ts","./src/lib/nip05.ts","./src/lib/nostr-utils.ts","./src/lib/open-parser.ts","./src/lib/profile-parser.ts","./src/lib/req-parser.ts","./src/lib/utils.ts","./src/services/accounts.ts","./src/services/db.ts","./src/services/event-store.ts","./src/services/loaders.ts","./src/services/relay-pool.ts","./src/types/app.ts","./src/types/man.ts","./src/types/nostr.ts","./src/types/profile.ts"],"version":"5.6.3"}
\ No newline at end of file