From 72fd8749d3cea129db0259d3136c1a8062e06c95 Mon Sep 17 00:00:00 2001 From: ved015 Date: Fri, 8 May 2026 21:35:30 +0530 Subject: [PATCH 01/19] add user personalization section --- apps/web/components/dashboard-view.tsx | 380 ++++++++++++++++++++++++- 1 file changed, 365 insertions(+), 15 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index a29faf886..2622ceac9 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -7,12 +7,14 @@ import { $fetch } from "@lib/api" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import { useQuery } from "@tanstack/react-query" import { useRouter } from "next/navigation" +import Image from "next/image" import { ArrowRight, ExternalLink, FileText, Lightbulb, Link2, + Plug, RotateCcw, SearchIcon, Terminal, @@ -293,6 +295,262 @@ const PLUGIN_STATIC = [ }, ] as const +// Plugin catalog for tool usage display - maps plugin IDs to display names and icons +const PLUGIN_DISPLAY_CATALOG: Record< + string, + { name: string; icon: string; type: "Plugin" } +> = { + claude_code: { + name: "Claude Code", + icon: "/images/plugins/claude-code.svg", + type: "Plugin", + }, + opencode: { + name: "OpenCode", + icon: "/images/plugins/opencode.svg", + type: "Plugin", + }, + openclaw: { + name: "OpenClaw", + icon: "/images/plugins/openclaw.svg", + type: "Plugin", + }, + hermes: { + name: "Hermes", + icon: "/images/plugins/hermes.svg", + type: "Plugin", + }, +} + +// Types for tool usage +interface ToolUsageItem { + id: string + name: string + type: "Plugin" | "MCP" + icon: string | null + lastUsedAt: Date | null + hasBeenUsed: boolean +} + +// Parse API keys to extract tool usage data +function parseToolUsage( + apiKeys: Array<{ + id: string + name: string + createdAt: string + lastRequest: string | null + metadata: string + }>, +): ToolUsageItem[] { + const toolMap = new Map() + + for (const key of apiKeys) { + let meta: Record = {} + try { + meta = key.metadata ? JSON.parse(key.metadata) : {} + } catch { + continue + } + + const smType = meta.sm_type as string | undefined + const smClient = meta.sm_client as string | undefined + const smSource = meta.sm_source as string | undefined + const smKind = meta.sm_kind as string | undefined + + // Plugin keys + if (smType === "plugin_auth" && smClient) { + const catalog = PLUGIN_DISPLAY_CATALOG[smClient] + const existingItem = toolMap.get(`plugin_${smClient}`) + const lastUsed = key.lastRequest + ? new Date(key.lastRequest) + : key.createdAt + ? new Date(key.createdAt) + : null + const existingLastUsed = existingItem?.lastUsedAt + + // Keep the most recent usage + if ( + !existingItem || + (lastUsed && + (!existingLastUsed || + lastUsed.getTime() > existingLastUsed.getTime())) + ) { + toolMap.set(`plugin_${smClient}`, { + id: `plugin_${smClient}`, + name: catalog?.name ?? smClient, + type: "Plugin", + icon: catalog?.icon ?? null, + lastUsedAt: lastUsed, + hasBeenUsed: !!key.lastRequest, + }) + } + } + + // MCP keys + if (smSource === "mcp" || smKind === "mcp_oauth_exchange") { + const existingItem = toolMap.get("mcp") + const lastUsed = key.lastRequest + ? new Date(key.lastRequest) + : key.createdAt + ? new Date(key.createdAt) + : null + const existingLastUsed = existingItem?.lastUsedAt + + // Keep the most recent usage + if ( + !existingItem || + (lastUsed && + (!existingLastUsed || + lastUsed.getTime() > existingLastUsed.getTime())) + ) { + // Try to get MCP client name from metadata + const mcpClientName = meta.sm_internal_mcp_client_name as + | string + | undefined + toolMap.set("mcp", { + id: "mcp", + name: mcpClientName || "Supermemory MCP", + type: "MCP", + icon: null, + lastUsedAt: lastUsed, + hasBeenUsed: !!key.lastRequest, + }) + } + } + } + + // Sort by lastUsedAt (most recent first), then by hasBeenUsed + return Array.from(toolMap.values()).sort((a, b) => { + // Items that have been used come first + if (a.hasBeenUsed !== b.hasBeenUsed) { + return a.hasBeenUsed ? -1 : 1 + } + // Then sort by recency + if (!a.lastUsedAt && !b.lastUsedAt) return 0 + if (!a.lastUsedAt) return 1 + if (!b.lastUsedAt) return -1 + return b.lastUsedAt.getTime() - a.lastUsedAt.getTime() + }) +} + +// Format relative time for tool usage +function formatToolUsageTime(date: Date | null, hasBeenUsed: boolean): string { + if (!hasBeenUsed) return "Never used" + if (!date) return "Connected" + const diffMs = Date.now() - date.getTime() + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)) + const diffDays = Math.floor(diffHours / 24) + if (diffHours < 1) return "Just now" + if (diffHours < 24) return `${diffHours}h ago` + if (diffDays === 1) return "Yesterday" + if (diffDays < 7) return `${diffDays}d ago` + return date.toLocaleDateString() +} + +function RecentToolUsageCard({ + items, + onOpenPlugins, + onOpenIntegrations, +}: { + items: ToolUsageItem[] + onOpenPlugins: () => void + onOpenIntegrations: (integration?: IntegrationParamValue) => void +}) { + // Show at most 3 items + const displayItems = items.slice(0, 3) + + // Empty state - no tool activity + if (displayItems.length === 0) { + return ( +
+ +

+ No recent tool activity +

+ +
+ ) + } + + return ( +
+
    + {displayItems.map((item) => ( +
  • + +
  • + ))} +
+
+ ) +} + function RecommendedPluginsCard({ profession, setProfession, @@ -678,6 +936,35 @@ export function DashboardView({ enabled: !!user, }) + // Fetch API keys for tool usage tracking + const { data: apiKeysData } = useQuery({ + queryKey: ["api-keys-tool-usage"], + queryFn: async () => { + const API_URL = + process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" + const res = await fetch(`${API_URL}/v3/auth/keys`, { + credentials: "include", + }) + if (!res.ok) return { keys: [] } + return (await res.json()) as { + keys: Array<{ + id: string + name: string + createdAt: string + lastRequest: string | null + metadata: string + }> + } + }, + staleTime: 5 * 60 * 1000, + enabled: !!user, + }) + + const toolUsageItems = useMemo( + () => parseToolUsage(apiKeysData?.keys ?? []), + [apiKeysData], + ) + const { copy: personalizedCopy, profession, @@ -839,7 +1126,7 @@ export function DashboardView({

- {/* Recently saved + Suggested for you */} + {/* Recently saved + Suggested for you + Pick up where you left off */} {recents.length > 0 ? ( <> - {/* Shared header row — both labels aligned */} + {/* Shared header row — all labels aligned */}

@@ -859,6 +1146,11 @@ export function DashboardView({ Suggested for you

+
+

+ Pick up where you left off +

+
{/* Content row */} @@ -900,24 +1192,82 @@ export function DashboardView({ onOpenIntegrations={onOpenIntegrations} /> + + {/* Pick up where you left off - desktop only */} +
+ +
+ + {/* Mobile/tablet: Show pick up where you left off below */} + {toolUsageItems.length > 0 && ( +
+

+ Pick up where you left off +

+ +
+ )} ) : ( - /* No recents yet — show suggestions full-width */ + /* No recents yet — show suggestions and tool usage */ <> -

- Suggested for you -

-
- +
+
+

+ Suggested for you +

+
+ {toolUsageItems.length > 0 && ( +
+

+ Pick up where you left off +

+
+ )} +
+
+
+ +
+ {toolUsageItems.length > 0 && ( +
+ +
+ )}
+ {/* Mobile: Show tool usage below */} + {toolUsageItems.length > 0 && ( +
+

+ Pick up where you left off +

+ +
+ )} )} From 45979647c1023ee82f1072343c96a0ab12b12b7b Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sat, 9 May 2026 21:13:49 +0530 Subject: [PATCH 02/19] fix minor bugs --- apps/web/components/dashboard-view.tsx | 207 +++++++++++++++++-------- 1 file changed, 140 insertions(+), 67 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 2622ceac9..04a6446e4 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -332,17 +332,28 @@ interface ToolUsageItem { hasBeenUsed: boolean } +type ToolUsageApiKey = { + id: string + name: string + createdAt: string + lastRequest: string | null + metadata: string +} + +function toValidDate(value: string | Date | null | undefined): Date | null { + if (!value) return null + const date = new Date(value) + return Number.isNaN(date.getTime()) ? null : date +} + // Parse API keys to extract tool usage data function parseToolUsage( - apiKeys: Array<{ - id: string - name: string - createdAt: string - lastRequest: string | null - metadata: string - }>, + apiKeys: ToolUsageApiKey[], + recentMcpDocuments: DocumentWithMemories[] = [], ): ToolUsageItem[] { const toolMap = new Map() + let latestMcpClientName: string | null = null + let latestMcpDocumentAt: Date | null = null for (const key of apiKeys) { let meta: Record = {} @@ -361,11 +372,8 @@ function parseToolUsage( if (smType === "plugin_auth" && smClient) { const catalog = PLUGIN_DISPLAY_CATALOG[smClient] const existingItem = toolMap.get(`plugin_${smClient}`) - const lastUsed = key.lastRequest - ? new Date(key.lastRequest) - : key.createdAt - ? new Date(key.createdAt) - : null + const lastUsed = + toValidDate(key.lastRequest) ?? toValidDate(key.createdAt) const existingLastUsed = existingItem?.lastUsedAt // Keep the most recent usage @@ -389,11 +397,8 @@ function parseToolUsage( // MCP keys if (smSource === "mcp" || smKind === "mcp_oauth_exchange") { const existingItem = toolMap.get("mcp") - const lastUsed = key.lastRequest - ? new Date(key.lastRequest) - : key.createdAt - ? new Date(key.createdAt) - : null + const lastUsed = + toValidDate(key.lastRequest) ?? toValidDate(key.createdAt) const existingLastUsed = existingItem?.lastUsedAt // Keep the most recent usage @@ -403,13 +408,9 @@ function parseToolUsage( (!existingLastUsed || lastUsed.getTime() > existingLastUsed.getTime())) ) { - // Try to get MCP client name from metadata - const mcpClientName = meta.sm_internal_mcp_client_name as - | string - | undefined toolMap.set("mcp", { id: "mcp", - name: mcpClientName || "Supermemory MCP", + name: "Supermemory MCP", type: "MCP", icon: null, lastUsedAt: lastUsed, @@ -419,6 +420,62 @@ function parseToolUsage( } } + for (const doc of recentMcpDocuments) { + const metadata = + doc.metadata && typeof doc.metadata === "object" ? doc.metadata : {} + const clientName = + typeof metadata.sm_internal_mcp_client_name === "string" + ? metadata.sm_internal_mcp_client_name + : null + const isMcpDocument = + doc.source === "mcp" || + metadata.sm_internal_event_from === "mcp" || + !!clientName + + if (!isMcpDocument) continue + + const createdAt = toValidDate(doc.createdAt) + if ( + clientName && + clientName !== "unknown" && + (!latestMcpDocumentAt || + (createdAt && createdAt.getTime() > latestMcpDocumentAt.getTime())) + ) { + latestMcpClientName = clientName + latestMcpDocumentAt = createdAt + } + + const existingItem = toolMap.get("mcp") + const existingLastUsed = existingItem?.lastUsedAt + if ( + !existingItem || + (createdAt && + (!existingLastUsed || createdAt.getTime() > existingLastUsed.getTime())) + ) { + toolMap.set("mcp", { + id: "mcp", + name: + clientName && clientName !== "unknown" + ? clientName + : "Supermemory MCP", + type: "MCP", + icon: null, + lastUsedAt: createdAt, + hasBeenUsed: true, + }) + } + } + + if (latestMcpClientName) { + const existingItem = toolMap.get("mcp") + if (existingItem) { + toolMap.set("mcp", { + ...existingItem, + name: latestMcpClientName, + }) + } + } + // Sort by lastUsedAt (most recent first), then by hasBeenUsed return Array.from(toolMap.values()).sort((a, b) => { // Items that have been used come first @@ -578,8 +635,8 @@ function RecommendedPluginsCard({ window.open(CHROME_EXTENSION_URL, "_blank", "noopener,noreferrer"), raycast: () => window.open(RAYCAST_EXTENSION_URL, "_blank", "noopener,noreferrer"), - notion: () => onOpenIntegrations("notion"), - "google-drive": () => onOpenIntegrations("google-drive"), + notion: () => onOpenIntegrations("connections"), + "google-drive": () => onOpenIntegrations("connections"), } const connected: Record = { mcp: hasMcp, @@ -750,8 +807,8 @@ function PluginPromoCard({ window.open(CHROME_EXTENSION_URL, "_blank", "noopener,noreferrer"), raycast: () => window.open(RAYCAST_EXTENSION_URL, "_blank", "noopener,noreferrer"), - notion: () => onOpenIntegrations("notion"), - "google-drive": () => onOpenIntegrations("google-drive"), + notion: () => onOpenIntegrations("connections"), + "google-drive": () => onOpenIntegrations("connections"), } const connected: Record = { mcp: hasMcp, @@ -890,7 +947,7 @@ export function DashboardView({ onResetHighlights: () => void memoryOfDay: MemoryOfDay | null }) { - const { user } = useAuth() + const { user, org } = useAuth() const { effectiveContainerTags } = useProject() const _router = useRouter() const { data: recentsData } = useQuery({ @@ -938,7 +995,7 @@ export function DashboardView({ // Fetch API keys for tool usage tracking const { data: apiKeysData } = useQuery({ - queryKey: ["api-keys-tool-usage"], + queryKey: ["api-keys-tool-usage", org?.id], queryFn: async () => { const API_URL = process.env.NEXT_PUBLIC_BACKEND_URL ?? "https://api.supermemory.ai" @@ -957,12 +1014,36 @@ export function DashboardView({ } }, staleTime: 5 * 60 * 1000, - enabled: !!user, + enabled: !!user && !!org?.id, + }) + + const { data: recentMcpDocumentsData } = useQuery({ + queryKey: ["dashboard-mcp-documents", org?.id, effectiveContainerTags], + queryFn: async (): Promise => { + const response = await $fetch("@post/documents/documents", { + body: { + page: 1, + limit: 50, + sort: "createdAt", + order: "desc", + containerTags: effectiveContainerTags, + }, + disableValidation: true, + }) + if (response.error) throw new Error(response.error?.message) + return response.data as DocumentsResponse + }, + staleTime: 5 * 60 * 1000, + enabled: !!user && !!org?.id, }) const toolUsageItems = useMemo( - () => parseToolUsage(apiKeysData?.keys ?? []), - [apiKeysData], + () => + parseToolUsage( + apiKeysData?.keys ?? [], + recentMcpDocumentsData?.documents ?? [], + ), + [apiKeysData, recentMcpDocumentsData], ) const { @@ -1204,18 +1285,16 @@ export function DashboardView({
{/* Mobile/tablet: Show pick up where you left off below */} - {toolUsageItems.length > 0 && ( -
-

- Pick up where you left off -

- -
- )} +
+

+ Pick up where you left off +

+ +
) : ( /* No recents yet — show suggestions and tool usage */ @@ -1226,13 +1305,11 @@ export function DashboardView({ Suggested for you

- {toolUsageItems.length > 0 && ( -
-

- Pick up where you left off -

-
- )} +
+

+ Pick up where you left off +

+
@@ -1245,29 +1322,25 @@ export function DashboardView({ onOpenIntegrations={onOpenIntegrations} />
- {toolUsageItems.length > 0 && ( -
- -
- )} -
- {/* Mobile: Show tool usage below */} - {toolUsageItems.length > 0 && ( -
-

- Pick up where you left off -

+
- )} +
+ {/* Mobile: Show tool usage below */} +
+

+ Pick up where you left off +

+ +
)}
From 5ce1247847e623ac2b9388886cf751a5f32ae220 Mon Sep 17 00:00:00 2001 From: ved015 Date: Tue, 12 May 2026 19:51:44 +0530 Subject: [PATCH 03/19] add codex plugin --- apps/web/app/auth/connect/page.tsx | 11 +++++++++++ apps/web/components/dashboard-view.tsx | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/apps/web/app/auth/connect/page.tsx b/apps/web/app/auth/connect/page.tsx index 544002e0d..816ad10ec 100644 --- a/apps/web/app/auth/connect/page.tsx +++ b/apps/web/app/auth/connect/page.tsx @@ -88,6 +88,17 @@ const PLUGIN_INFO: Record = { ], icon: "/images/plugins/cursor.svg", }, + codex: { + name: "OpenAI Codex", + description: + "Persistent memory for OpenAI Codex CLI. Remembers your coding context, patterns, and decisions across sessions.", + features: [ + "Auto-recalls relevant context before each prompt", + "Captures coding decisions and patterns automatically", + "Builds persistent user profile across projects", + ], + icon: "/images/plugins/codex.svg", + }, } function getPluginName(client: string): string { diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 04a6446e4..627d14ec4 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -320,6 +320,11 @@ const PLUGIN_DISPLAY_CATALOG: Record< icon: "/images/plugins/hermes.svg", type: "Plugin", }, + codex: { + name: "OpenAI Codex", + icon: "/images/plugins/codex.svg", + type: "Plugin", + }, } // Types for tool usage From f604f96a411ea504e898865bffa14c704674cd82 Mon Sep 17 00:00:00 2001 From: ved015 Date: Tue, 12 May 2026 19:55:46 +0530 Subject: [PATCH 04/19] add orgid back removed when testing in local --- apps/web/components/dashboard-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 627d14ec4..b7c10a9e1 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -972,7 +972,7 @@ export function DashboardView({ return response.data as DocumentsResponse }, staleTime: 60 * 1000, - enabled: !!user, + enabled: !!user && !!org?.id, }) const { data: connections = [] } = useQuery({ From 37a07184bff9e2329b245f8d37dd6849669dfa99 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Tue, 12 May 2026 20:27:18 +0530 Subject: [PATCH 05/19] enhance UI of personalisation section --- apps/web/components/dashboard-view.tsx | 238 +++++++++++++++++++------ 1 file changed, 181 insertions(+), 57 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index b7c10a9e1..37582908a 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -9,7 +9,9 @@ import { useQuery } from "@tanstack/react-query" import { useRouter } from "next/navigation" import Image from "next/image" import { + Activity, ArrowRight, + Clock, ExternalLink, FileText, Lightbulb, @@ -298,32 +300,37 @@ const PLUGIN_STATIC = [ // Plugin catalog for tool usage display - maps plugin IDs to display names and icons const PLUGIN_DISPLAY_CATALOG: Record< string, - { name: string; icon: string; type: "Plugin" } + { name: string; icon: string; type: "Plugin"; description: string } > = { claude_code: { name: "Claude Code", icon: "/images/plugins/claude-code.svg", type: "Plugin", + description: "Persistent memory across coding sessions", }, opencode: { name: "OpenCode", icon: "/images/plugins/opencode.svg", type: "Plugin", + description: "Context that carries forward automatically", }, openclaw: { name: "OpenClaw", icon: "/images/plugins/openclaw.svg", type: "Plugin", + description: "Cross-channel memory for messaging", }, hermes: { name: "Hermes", icon: "/images/plugins/hermes.svg", type: "Plugin", + description: "Persistent conversation memory", }, codex: { name: "OpenAI Codex", icon: "/images/plugins/codex.svg", type: "Plugin", + description: "AI-powered code generation with memory", }, } @@ -335,6 +342,8 @@ interface ToolUsageItem { icon: string | null lastUsedAt: Date | null hasBeenUsed: boolean + connectedAt: Date | null + description: string | null } type ToolUsageApiKey = { @@ -395,6 +404,8 @@ function parseToolUsage( icon: catalog?.icon ?? null, lastUsedAt: lastUsed, hasBeenUsed: !!key.lastRequest, + connectedAt: toValidDate(key.createdAt), + description: catalog?.description ?? null, }) } } @@ -420,6 +431,8 @@ function parseToolUsage( icon: null, lastUsedAt: lastUsed, hasBeenUsed: !!key.lastRequest, + connectedAt: toValidDate(key.createdAt), + description: "Query your saved knowledge from any AI client", }) } } @@ -467,6 +480,8 @@ function parseToolUsage( icon: null, lastUsedAt: createdAt, hasBeenUsed: true, + connectedAt: toolMap.get("mcp")?.connectedAt ?? null, + description: "Query your saved knowledge from any AI client", }) } } @@ -500,15 +515,35 @@ function formatToolUsageTime(date: Date | null, hasBeenUsed: boolean): string { if (!hasBeenUsed) return "Never used" if (!date) return "Connected" const diffMs = Date.now() - date.getTime() + const diffMins = Math.floor(diffMs / (1000 * 60)) const diffHours = Math.floor(diffMs / (1000 * 60 * 60)) const diffDays = Math.floor(diffHours / 24) - if (diffHours < 1) return "Just now" + if (diffMins < 1) return "Just now" + if (diffMins < 60) return `${diffMins}m ago` if (diffHours < 24) return `${diffHours}h ago` if (diffDays === 1) return "Yesterday" if (diffDays < 7) return `${diffDays}d ago` return date.toLocaleDateString() } +// Format absolute time for tooltip display +function formatAbsoluteTime(date: Date | null): string { + if (!date) return "" + return date.toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }) +} + +// Check if item was active recently (within last hour) +function isRecentlyActive(date: Date | null): boolean { + if (!date) return false + return Date.now() - date.getTime() < 60 * 60 * 1000 +} + function RecentToolUsageCard({ items, onOpenPlugins, @@ -548,66 +583,155 @@ function RecentToolUsageCard({ return (
-
    - {displayItems.map((item) => ( -
  • - -
  • - ))} + + + + + +
    +

    {item.name}

    + {item.hasBeenUsed && item.lastUsedAt && ( +

    + Last used: {formatAbsoluteTime(item.lastUsedAt)} +

    + )} + {item.connectedAt && ( +

    + Connected: {formatAbsoluteTime(item.connectedAt)} +

    + )} + {!item.hasBeenUsed && ( +

    Not yet used

    + )} +
    +
    +
    + + ) + })}
) From 5259180ccecd4a3f1b2648f72cd0c66ab86d52e6 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 15:00:23 +0000 Subject: [PATCH 06/19] fix lint errors in dashboard-view.tsx - Replace non-null assertions with nullish coalescing fallbacks - Rename unused parameter with underscore prefix - Apply Biome formatting fixes Co-Authored-By: Claude Opus 4.5 --- apps/web/components/dashboard-view.tsx | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 37582908a..08aa95fba 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -659,12 +659,14 @@ function RecentToolUsageCard({

{item.name}

- + {item.type} @@ -691,7 +693,10 @@ function RecentToolUsageCard({ : "text-fg-faint", )} > - {formatToolUsageTime(item.lastUsedAt, item.hasBeenUsed)} + {formatToolUsageTime( + item.lastUsedAt, + item.hasBeenUsed, + )} {/* Connected since */} @@ -699,7 +704,11 @@ function RecentToolUsageCard({ <> · - Since {item.connectedAt.toLocaleDateString(undefined, { month: "short", day: "numeric" })} + Since{" "} + {item.connectedAt.toLocaleDateString(undefined, { + month: "short", + day: "numeric", + })} )} @@ -777,7 +786,7 @@ function RecommendedPluginsCard({ return PLUGIN_STATIC.map((p) => ({ ...p, connected: connected[p.id] ?? false, - onClick: onClicks[p.id]!, + onClick: onClicks[p.id] ?? (() => {}), })) }, [hasMcp, connectedProviders, onOpenPlugins, onOpenIntegrations]) @@ -949,7 +958,7 @@ function PluginPromoCard({ return PLUGIN_STATIC.map((p) => ({ ...p, connected: connected[p.id] ?? false, - onClick: onClicks[p.id]!, + onClick: onClicks[p.id] ?? (() => {}), })).filter((p) => !p.connected) }, [hasMcp, connectedProviders, onOpenPlugins, onOpenIntegrations]) @@ -1052,7 +1061,7 @@ export function DashboardView({ onOpenSearch, onOpenIntegrations, onOpenPlugins, - onNavigateToMemories, + onNavigateToMemories: _onNavigateToMemories, onNavigateToGraph, onOpenDocument, onHighlightsChat, From eddd799504d66d4a59ec9c301df927712312e4f9 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Tue, 12 May 2026 20:55:55 +0530 Subject: [PATCH 07/19] enhance UI of personalisation section --- apps/web/components/dashboard-view.tsx | 251 ++++++++++++------------- 1 file changed, 123 insertions(+), 128 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 37582908a..92f2faf7f 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -300,37 +300,32 @@ const PLUGIN_STATIC = [ // Plugin catalog for tool usage display - maps plugin IDs to display names and icons const PLUGIN_DISPLAY_CATALOG: Record< string, - { name: string; icon: string; type: "Plugin"; description: string } + { name: string; icon: string; type: "Plugin" } > = { claude_code: { name: "Claude Code", icon: "/images/plugins/claude-code.svg", type: "Plugin", - description: "Persistent memory across coding sessions", }, opencode: { name: "OpenCode", icon: "/images/plugins/opencode.svg", type: "Plugin", - description: "Context that carries forward automatically", }, openclaw: { name: "OpenClaw", icon: "/images/plugins/openclaw.svg", type: "Plugin", - description: "Cross-channel memory for messaging", }, hermes: { name: "Hermes", icon: "/images/plugins/hermes.svg", type: "Plugin", - description: "Persistent conversation memory", }, codex: { name: "OpenAI Codex", icon: "/images/plugins/codex.svg", type: "Plugin", - description: "AI-powered code generation with memory", }, } @@ -343,7 +338,8 @@ interface ToolUsageItem { lastUsedAt: Date | null hasBeenUsed: boolean connectedAt: Date | null - description: string | null + lastDocumentTitle: string | null + lastDocumentId: string | null } type ToolUsageApiKey = { @@ -369,6 +365,9 @@ function parseToolUsage( let latestMcpClientName: string | null = null let latestMcpDocumentAt: Date | null = null + // Track latest document per plugin client + const latestDocPerPlugin = new Map() + for (const key of apiKeys) { let meta: Record = {} try { @@ -405,7 +404,8 @@ function parseToolUsage( lastUsedAt: lastUsed, hasBeenUsed: !!key.lastRequest, connectedAt: toValidDate(key.createdAt), - description: catalog?.description ?? null, + lastDocumentTitle: null, + lastDocumentId: null, }) } } @@ -432,7 +432,8 @@ function parseToolUsage( lastUsedAt: lastUsed, hasBeenUsed: !!key.lastRequest, connectedAt: toValidDate(key.createdAt), - description: "Query your saved knowledge from any AI client", + lastDocumentTitle: null, + lastDocumentId: null, }) } } @@ -481,9 +482,22 @@ function parseToolUsage( lastUsedAt: createdAt, hasBeenUsed: true, connectedAt: toolMap.get("mcp")?.connectedAt ?? null, - description: "Query your saved knowledge from any AI client", + lastDocumentTitle: doc.title?.trim() || null, + lastDocumentId: doc.id ?? null, }) } + + // Also track latest doc per plugin client name for plugin items + if (clientName && clientName !== "unknown" && createdAt && doc.id) { + const existing = latestDocPerPlugin.get(clientName) + if (!existing || createdAt.getTime() > existing.at.getTime()) { + latestDocPerPlugin.set(clientName, { + title: doc.title?.trim() || "Untitled", + id: doc.id, + at: createdAt, + }) + } + } } if (latestMcpClientName) { @@ -496,6 +510,18 @@ function parseToolUsage( } } + // Attach latest document info to plugin items where available from MCP documents + for (const [, item] of toolMap) { + if (item.type === "Plugin" && !item.lastDocumentTitle) { + // Try to find matching documents by plugin name + const docInfo = latestDocPerPlugin.get(item.name) + if (docInfo) { + item.lastDocumentTitle = docInfo.title + item.lastDocumentId = docInfo.id + } + } + } + // Sort by lastUsedAt (most recent first), then by hasBeenUsed return Array.from(toolMap.values()).sort((a, b) => { // Items that have been used come first @@ -547,11 +573,11 @@ function isRecentlyActive(date: Date | null): boolean { function RecentToolUsageCard({ items, onOpenPlugins, - onOpenIntegrations, + onNavigateToMemories, }: { items: ToolUsageItem[] onOpenPlugins: () => void - onOpenIntegrations: (integration?: IntegrationParamValue) => void + onNavigateToMemories: () => void }) { // Show at most 3 items const displayItems = items.slice(0, 3) @@ -590,7 +616,6 @@ function RecentToolUsageCard({
    {displayItems.map((item, index) => { const recentlyActive = isRecentlyActive(item.lastUsedAt) - const isMostRecent = index === 0 && item.hasBeenUsed return ( - - - - - -
    -

    {item.name}

    - {item.hasBeenUsed && item.lastUsedAt && ( -

    - Last used: {formatAbsoluteTime(item.lastUsedAt)} -

    - )} - {item.connectedAt && ( -

    - Connected: {formatAbsoluteTime(item.connectedAt)} -

    - )} - {!item.hasBeenUsed && ( -

    Not yet used

    + )}
    -
    -
    + +
    ) })} @@ -1408,7 +1403,7 @@ export function DashboardView({ @@ -1421,7 +1416,7 @@ export function DashboardView({ @@ -1455,7 +1450,7 @@ export function DashboardView({ @@ -1467,7 +1462,7 @@ export function DashboardView({ From e14e418854acdbc38b74b8d2844ffdf551530ca6 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 15:46:05 +0000 Subject: [PATCH 08/19] fix: apply Biome formatting to dashboard-view.tsx Co-Authored-By: Claude Opus 4.5 --- apps/web/components/dashboard-view.tsx | 51 ++++++++++++++++---------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 3a9371c45..de7fc6de3 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -243,14 +243,14 @@ const PROFESSION_LABELS: { value: Exclude label: string }[] = [ - { value: "developer", label: "Developer" }, - { value: "research", label: "Researcher" }, - { value: "finance", label: "Finance" }, - { value: "design", label: "Designer" }, - { value: "legal", label: "Legal" }, - { value: "marketing", label: "Marketing" }, - { value: "medical", label: "Medical" }, - ] + { value: "developer", label: "Developer" }, + { value: "research", label: "Researcher" }, + { value: "finance", label: "Finance" }, + { value: "design", label: "Designer" }, + { value: "legal", label: "Legal" }, + { value: "marketing", label: "Marketing" }, + { value: "medical", label: "Medical" }, +] // Static plugin metadata — shared between PluginPromoCard and RecommendedPluginsCard const PLUGIN_STATIC = [ @@ -366,7 +366,10 @@ function parseToolUsage( let latestMcpDocumentAt: Date | null = null // Track latest document per plugin client - const latestDocPerPlugin = new Map() + const latestDocPerPlugin = new Map< + string, + { title: string; id: string; at: Date } + >() for (const key of apiKeys) { let meta: Record = {} @@ -674,12 +677,14 @@ function RecentToolUsageCard({

    {item.name}

    - + {item.type} @@ -705,8 +710,10 @@ function RecentToolUsageCard({ > {item.hasBeenUsed && item.lastUsedAt ? `${formatToolUsageTime(item.lastUsedAt, true)} · ${formatAbsoluteTime(item.lastUsedAt)}` - : formatToolUsageTime(item.lastUsedAt, item.hasBeenUsed) - } + : formatToolUsageTime( + item.lastUsedAt, + item.hasBeenUsed, + )} {/* Connected since */} @@ -716,7 +723,11 @@ function RecentToolUsageCard({ - Since {item.connectedAt.toLocaleDateString(undefined, { month: "short", day: "numeric" })} + Since{" "} + {item.connectedAt.toLocaleDateString(undefined, { + month: "short", + day: "numeric", + })} @@ -772,7 +783,7 @@ function RecommendedPluginsCard({ return PLUGIN_STATIC.map((p) => ({ ...p, connected: connected[p.id] ?? false, - onClick: onClicks[p.id] ?? (() => { }), + onClick: onClicks[p.id] ?? (() => {}), })) }, [hasMcp, connectedProviders, onOpenPlugins, onOpenIntegrations]) @@ -944,7 +955,7 @@ function PluginPromoCard({ return PLUGIN_STATIC.map((p) => ({ ...p, connected: connected[p.id] ?? false, - onClick: onClicks[p.id] ?? (() => { }), + onClick: onClicks[p.id] ?? (() => {}), })).filter((p) => !p.connected) }, [hasMcp, connectedProviders, onOpenPlugins, onOpenIntegrations]) From 5cf770336a5efb5cf5e5ee51b7d49416b362524c Mon Sep 17 00:00:00 2001 From: ved015 Date: Tue, 12 May 2026 22:57:19 +0530 Subject: [PATCH 09/19] fix recent convo and redirect path --- apps/web/app/(app)/page.tsx | 18 ++- apps/web/components/dashboard-view.tsx | 191 +++++++++++++++++++------ 2 files changed, 163 insertions(+), 46 deletions(-) diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index e7d7caff6..58e500eac 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -106,7 +106,7 @@ export default function NewPage() { const isMobile = useIsMobile() const { user, session } = useAuth() - const { selectedProject, selectedProjects } = useProject() + const { selectedProject, selectedProjects, setSelectedProject } = useProject() const selectedProjectTag = selectedProjects[0] const { allProjects } = useContainerTags() const dashboardSpaceLabel = useMemo( @@ -402,6 +402,21 @@ export default function NewPage() { [setDocId], ) + const handleOpenToolDocument = useCallback( + (document: DocumentWithMemories) => { + const documentSpace = + (document as { containerTags?: string[] }).containerTags?.[0] ?? + document.memoryEntries.find((entry) => entry.spaceContainerTag) + ?.spaceContainerTag + if (documentSpace) { + setSelectedProject(documentSpace) + } + handleOpenDocument(document) + void setViewMode("list") + }, + [handleOpenDocument, setSelectedProject, setViewMode], + ) + const handleQuickNoteSave = useCallback( (content: string) => { if (content.trim()) { @@ -700,6 +715,7 @@ export default function NewPage() { onNavigateToMemories={() => void setViewMode("list")} onNavigateToGraph={() => void setViewMode("graph")} onOpenDocument={handleOpenDocument} + onOpenToolDocument={handleOpenToolDocument} onHighlightsChat={handleHighlightsChat} onHighlightsShowRelated={handleHighlightsShowRelated} onResetHighlights={handleResetHighlights} diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index de7fc6de3..30f989014 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -300,7 +300,7 @@ const PLUGIN_STATIC = [ // Plugin catalog for tool usage display - maps plugin IDs to display names and icons const PLUGIN_DISPLAY_CATALOG: Record< string, - { name: string; icon: string; type: "Plugin" } + { name: string; icon: string | null; type: "Plugin" } > = { claude_code: { name: "Claude Code", @@ -324,7 +324,7 @@ const PLUGIN_DISPLAY_CATALOG: Record< }, codex: { name: "OpenAI Codex", - icon: "/images/plugins/codex.svg", + icon: null, type: "Plugin", }, } @@ -340,6 +340,8 @@ interface ToolUsageItem { connectedAt: Date | null lastDocumentTitle: string | null lastDocumentId: string | null + lastDocumentPreview: string | null + lastDocument: DocumentWithMemories | null } type ToolUsageApiKey = { @@ -356,6 +358,76 @@ function toValidDate(value: string | Date | null | undefined): Date | null { return Number.isNaN(date.getTime()) ? null : date } +function compactText(value: string): string { + return value.replace(/\s+/g, " ").trim() +} + +function getDocumentText(document: DocumentWithMemories): string { + return typeof document.content === "string" ? document.content : "" +} + +function getPluginClientFromDocument( + document: DocumentWithMemories, +): string | null { + const metadata = + document.metadata && typeof document.metadata === "object" + ? document.metadata + : {} + const metadataClient = + typeof metadata.sm_client === "string" + ? metadata.sm_client + : typeof metadata.sm_internal_plugin_client === "string" + ? metadata.sm_internal_plugin_client + : typeof metadata.sm_internal_mcp_client_name === "string" + ? metadata.sm_internal_mcp_client_name + : null + if (metadataClient) return metadataClient.toLowerCase() + + const content = getDocumentText(document) + const title = document.title ?? "" + if ( + /\[Session\s+[^\]]+\]/i.test(content) || + /\[SAVE:[^\]]+\]/i.test(content) + ) { + return "codex" + } + if (/\bCodex\b/i.test(title)) return "codex" + + return null +} + +function getDocumentPreview(document: DocumentWithMemories): string | null { + const content = getDocumentText(document) + if (!content) return document.title?.trim() || null + + const transcriptTurns = Array.from( + content.matchAll( + /\d+\.\s+\[(user|assistant)\]\s*([\s\S]*?)(?=\d+\.\s+\[(?:user|assistant|tool|system)\]|---|\[\/?[A-Za-z]|$)/gi, + ), + ) + .slice(0, 2) + .map((match) => { + const role = match[1] === "assistant" ? "Assistant" : "You" + const text = compactText(match[2] ?? "") + return text ? `${role}: ${text}` : null + }) + .filter(Boolean) + + if (transcriptTurns.length > 0) return transcriptTurns.join(" · ") + + const cleaned = compactText( + content + .replace(/\[Session\s+[^\]]+\]/gi, "") + .replace(/\[SAVE:[^\]]+\]/gi, "") + .replace(/\[\/SAVE\]/gi, ""), + ) + return cleaned || document.title?.trim() || null +} + +function getToolDocumentTitle(document: DocumentWithMemories): string { + return document.title?.trim() || "Recent conversation" +} + // Parse API keys to extract tool usage data function parseToolUsage( apiKeys: ToolUsageApiKey[], @@ -365,10 +437,15 @@ function parseToolUsage( let latestMcpClientName: string | null = null let latestMcpDocumentAt: Date | null = null - // Track latest document per plugin client const latestDocPerPlugin = new Map< string, - { title: string; id: string; at: Date } + { + title: string + id: string + at: Date + preview: string | null + document: DocumentWithMemories + } >() for (const key of apiKeys) { @@ -409,6 +486,8 @@ function parseToolUsage( connectedAt: toValidDate(key.createdAt), lastDocumentTitle: null, lastDocumentId: null, + lastDocumentPreview: null, + lastDocument: null, }) } } @@ -437,6 +516,8 @@ function parseToolUsage( connectedAt: toValidDate(key.createdAt), lastDocumentTitle: null, lastDocumentId: null, + lastDocumentPreview: null, + lastDocument: null, }) } } @@ -449,15 +530,18 @@ function parseToolUsage( typeof metadata.sm_internal_mcp_client_name === "string" ? metadata.sm_internal_mcp_client_name : null + const pluginClient = getPluginClientFromDocument(doc) const isMcpDocument = doc.source === "mcp" || metadata.sm_internal_event_from === "mcp" || !!clientName + const isPluginDocument = !!pluginClient && pluginClient !== "mcp" - if (!isMcpDocument) continue + if (!isMcpDocument && !isPluginDocument) continue const createdAt = toValidDate(doc.createdAt) if ( + isMcpDocument && clientName && clientName !== "unknown" && (!latestMcpDocumentAt || @@ -467,37 +551,43 @@ function parseToolUsage( latestMcpDocumentAt = createdAt } - const existingItem = toolMap.get("mcp") - const existingLastUsed = existingItem?.lastUsedAt - if ( - !existingItem || - (createdAt && - (!existingLastUsed || createdAt.getTime() > existingLastUsed.getTime())) - ) { - toolMap.set("mcp", { - id: "mcp", - name: - clientName && clientName !== "unknown" - ? clientName - : "Supermemory MCP", - type: "MCP", - icon: null, - lastUsedAt: createdAt, - hasBeenUsed: true, - connectedAt: toolMap.get("mcp")?.connectedAt ?? null, - lastDocumentTitle: doc.title?.trim() || null, - lastDocumentId: doc.id ?? null, - }) + if (isMcpDocument) { + const existingItem = toolMap.get("mcp") + const existingLastUsed = existingItem?.lastUsedAt + if ( + !existingItem || + (createdAt && + (!existingLastUsed || + createdAt.getTime() > existingLastUsed.getTime())) + ) { + toolMap.set("mcp", { + id: "mcp", + name: + clientName && clientName !== "unknown" + ? clientName + : "Supermemory MCP", + type: "MCP", + icon: null, + lastUsedAt: createdAt, + hasBeenUsed: true, + connectedAt: toolMap.get("mcp")?.connectedAt ?? null, + lastDocumentTitle: doc.title?.trim() || null, + lastDocumentId: doc.id ?? null, + lastDocumentPreview: getDocumentPreview(doc), + lastDocument: doc, + }) + } } - // Also track latest doc per plugin client name for plugin items - if (clientName && clientName !== "unknown" && createdAt && doc.id) { - const existing = latestDocPerPlugin.get(clientName) + if (pluginClient && createdAt && doc.id) { + const existing = latestDocPerPlugin.get(pluginClient) if (!existing || createdAt.getTime() > existing.at.getTime()) { - latestDocPerPlugin.set(clientName, { - title: doc.title?.trim() || "Untitled", + latestDocPerPlugin.set(pluginClient, { + title: getToolDocumentTitle(doc), id: doc.id, at: createdAt, + preview: getDocumentPreview(doc), + document: doc, }) } } @@ -516,11 +606,13 @@ function parseToolUsage( // Attach latest document info to plugin items where available from MCP documents for (const [, item] of toolMap) { if (item.type === "Plugin" && !item.lastDocumentTitle) { - // Try to find matching documents by plugin name - const docInfo = latestDocPerPlugin.get(item.name) + const pluginId = item.id.replace(/^plugin_/, "") + const docInfo = latestDocPerPlugin.get(pluginId) if (docInfo) { item.lastDocumentTitle = docInfo.title item.lastDocumentId = docInfo.id + item.lastDocumentPreview = docInfo.preview + item.lastDocument = docInfo.document } } } @@ -577,10 +669,12 @@ function RecentToolUsageCard({ items, onOpenPlugins, onNavigateToMemories, + onOpenToolDocument, }: { items: ToolUsageItem[] onOpenPlugins: () => void onNavigateToMemories: () => void + onOpenToolDocument: (document: DocumentWithMemories) => void }) { // Show at most 3 items const displayItems = items.slice(0, 3) @@ -634,10 +728,13 @@ function RecentToolUsageCard({ + + ) +} + function RecommendedPluginsCard({ profession, setProfession, @@ -1154,7 +1209,7 @@ export function DashboardView({ onOpenSearch, onOpenIntegrations, onOpenPlugins, - onNavigateToMemories, + onNavigateToMemories: _onNavigateToMemories, onNavigateToGraph, onOpenDocument, onOpenToolDocument, @@ -1285,6 +1340,9 @@ export function DashboardView({ } = usePersonalization() const recents = recentsData?.documents ?? [] + const recentToolUsageItems = toolUsageItems + .filter((item) => item.type === "Plugin") + .slice(0, 3) const totalMemories = recentsData?.pagination?.totalItems ?? 0 const hasMcp = mcpData?.previousLogin ?? false const connectedProviders = new Set(connections.map((c) => c.provider)) @@ -1439,17 +1497,17 @@ export function DashboardView({

    - {/* Recently saved + Suggested for you + Pick up where you left off */} + {/* Recently saved + Suggested for you */} - {recents.length > 0 ? ( + {recents.length > 0 || recentToolUsageItems.length > 0 ? ( <> {/* Shared header row — all labels aligned */}
    -
    +

    Recently saved

    @@ -1459,16 +1517,11 @@ export function DashboardView({ Suggested for you

    -
    -

    - Pick up where you left off -

    -
    {/* Content row */}
    -
      +
        {recents.map((doc) => { const isLink = !!doc.url return ( @@ -1493,6 +1546,14 @@ export function DashboardView({ ) })} + {recentToolUsageItems.map((item) => ( + + ))}
      @@ -1505,29 +1566,6 @@ export function DashboardView({ onOpenIntegrations={onOpenIntegrations} />
      - - {/* Pick up where you left off - desktop only */} -
      - -
      -
    - - {/* Mobile/tablet: Show pick up where you left off below */} -
    -

    - Pick up where you left off -

    -
    ) : ( @@ -1539,11 +1577,6 @@ export function DashboardView({ Suggested for you

    -
    -

    - Pick up where you left off -

    -
    @@ -1556,26 +1589,6 @@ export function DashboardView({ onOpenIntegrations={onOpenIntegrations} />
    -
    - -
    -
    - {/* Mobile: Show tool usage below */} -
    -

    - Pick up where you left off -

    -
    )} From 6cd2547dc26a2fbdf8cba82f2856ffba681afa47 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 04:41:27 +0000 Subject: [PATCH 11/19] fix: add missing closing parenthesis in handleOpenToolDocument callback Co-Authored-By: Claude Opus 4.5 --- apps/web/app/(app)/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index b5911a312..7c7210c6c 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -415,6 +415,7 @@ export default function NewPage() { void setViewMode("list") }, [handleOpenDocument, setSelectedProject, setViewMode], + ) // Separate from handleOpenDocument because the graph view only has a document ID, // not the full document object. The modal will fetch the document via the docId From 89777ce8b08c53ec0df1b9bc1836a41145ba9c6a Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Wed, 13 May 2026 11:05:51 +0530 Subject: [PATCH 12/19] add plugin on top and on hover show summary --- apps/web/components/dashboard-view.tsx | 29 +++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 945f5b91d..26b9ab985 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -397,6 +397,10 @@ function getPluginClientFromDocument( } function getDocumentPreview(document: DocumentWithMemories): string | null { + const summary = + typeof document.summary === "string" ? compactText(document.summary) : "" + if (summary) return summary + const content = getDocumentText(document) if (!content) return document.title?.trim() || null @@ -859,7 +863,7 @@ function ToolUsageRecentRow({ } onOpenPlugins() }} - className="group flex w-full items-center gap-3 rounded-lg px-2.5 py-2 text-left transition-colors hover:bg-surface-hover" + className="group flex w-full items-start gap-3 rounded-lg px-2.5 py-2 text-left transition-all hover:bg-surface-hover hover:py-2.5" >
    {item.icon ? ( @@ -888,6 +892,11 @@ function ToolUsageRecentRow({

    {item.lastDocumentTitle ?? "No saved memory yet"}

    + {item.lastDocumentPreview ? ( +

    + {item.lastDocumentPreview} +

    + ) : null}
    @@ -1509,7 +1518,7 @@ export function DashboardView({

    - Recently saved + Recents

    @@ -1522,6 +1531,14 @@ export function DashboardView({ {/* Content row */}
      + {recentToolUsageItems.map((item) => ( + + ))} {recents.map((doc) => { const isLink = !!doc.url return ( @@ -1546,14 +1563,6 @@ export function DashboardView({ ) })} - {recentToolUsageItems.map((item) => ( - - ))}
    From bc97cb6862d92eeb303e9a8c65fad1375bdc4a44 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Wed, 13 May 2026 11:15:28 +0530 Subject: [PATCH 13/19] remove unused imports --- apps/web/components/dashboard-view.tsx | 194 ------------------------- 1 file changed, 194 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 26b9ab985..a344781d3 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -9,9 +9,7 @@ import { useQuery } from "@tanstack/react-query" import { useRouter } from "next/navigation" import Image from "next/image" import { - Activity, ArrowRight, - Clock, ExternalLink, FileText, Lightbulb, @@ -651,198 +649,6 @@ function formatToolUsageTime(date: Date | null, hasBeenUsed: boolean): string { return date.toLocaleDateString() } -// Format absolute time for tooltip display -function formatAbsoluteTime(date: Date | null): string { - if (!date) return "" - return date.toLocaleString(undefined, { - month: "short", - day: "numeric", - hour: "numeric", - minute: "2-digit", - hour12: true, - }) -} - -// Check if item was active recently (within last hour) -function isRecentlyActive(date: Date | null): boolean { - if (!date) return false - return Date.now() - date.getTime() < 60 * 60 * 1000 -} - -function _RecentToolUsageCard({ - items, - onOpenPlugins, - onNavigateToMemories, - onOpenToolDocument, -}: { - items: ToolUsageItem[] - onOpenPlugins: () => void - onNavigateToMemories: () => void - onOpenToolDocument: (document: DocumentWithMemories) => void -}) { - // Show at most 3 items - const displayItems = items.slice(0, 3) - - // Empty state - no tool activity - if (displayItems.length === 0) { - return ( -
    - -

    - No recent tool activity -

    - -
    - ) - } - - return ( -
    -
      - {displayItems.map((item, index) => { - const recentlyActive = isRecentlyActive(item.lastUsedAt) - - return ( - - - - ) - })} -
    -
    - ) -} - function ToolUsageRecentRow({ item, onOpenPlugins, From 0ae8200c4f79925dcc9c5535336f316e8e87b8d4 Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Wed, 13 May 2026 21:26:01 +0530 Subject: [PATCH 14/19] add title in place of name --- apps/web/components/dashboard-view.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index a344781d3..a6216e3df 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -689,15 +689,12 @@ function ToolUsageRecentRow({
    - {item.name} + {item.lastDocumentTitle ?? "No saved memory yet"} {formatToolUsageTime(item.lastUsedAt, item.hasBeenUsed)}
    -

    - {item.lastDocumentTitle ?? "No saved memory yet"} -

    {item.lastDocumentPreview ? (

    {item.lastDocumentPreview} From 6fa2a8c70e5ee5e00530d0f1813431908f7cca41 Mon Sep 17 00:00:00 2001 From: ved015 Date: Fri, 15 May 2026 13:13:31 +0530 Subject: [PATCH 15/19] add support for other plugins --- apps/web/app/auth/connect/page.tsx | 11 ++++ apps/web/components/dashboard-view.tsx | 83 ++++++++++++++++++++------ apps/web/middleware.ts | 11 +++- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/apps/web/app/auth/connect/page.tsx b/apps/web/app/auth/connect/page.tsx index 37f61d3d6..e8d0bac85 100644 --- a/apps/web/app/auth/connect/page.tsx +++ b/apps/web/app/auth/connect/page.tsx @@ -101,6 +101,17 @@ const PLUGIN_INFO: Record = { ], icon: "/images/plugins/codex.svg", }, + codex: { + name: "OpenAI Codex", + description: + "Memory layer for Codex. Keeps coding context, preferences, and decisions available across sessions.", + features: [ + "Auto-recalls relevant context before prompts", + "Captures useful coding decisions from sessions", + "Supports project and custom memory containers", + ], + icon: "/images/plugins/opencode.svg", + }, } function getPluginName(client: string): string { diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index a6216e3df..ab8b037d5 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -364,22 +364,66 @@ function getDocumentText(document: DocumentWithMemories): string { return typeof document.content === "string" ? document.content : "" } +function toMetadataRecord(value: unknown): Record | null { + if (!value) return null + if (typeof value === "object" && !Array.isArray(value)) { + return value as Record + } + if (typeof value !== "string") return null + + try { + const parsed = JSON.parse(value) + return parsed && typeof parsed === "object" && !Array.isArray(parsed) + ? (parsed as Record) + : null + } catch { + return null + } +} + +function getDocumentMetadataRecords( + document: DocumentWithMemories, +): Record[] { + const records = [toMetadataRecord(document.metadata)] + + for (const entry of document.memoryEntries ?? []) { + records.push( + toMetadataRecord(entry.metadata), + toMetadataRecord(entry.sourceMetadata), + ) + } + + return records.filter((record): record is Record => !!record) +} + +function hasClaudeCodeContainer(document: DocumentWithMemories): boolean { + const containerTags = + (document as { containerTags?: string[] }).containerTags ?? [] + if (containerTags.some((tag) => tag.startsWith("claudecode_"))) return true + + return (document.memoryEntries ?? []).some((entry) => + entry.spaceContainerTag?.startsWith("claudecode_"), + ) +} + function getPluginClientFromDocument( document: DocumentWithMemories, ): string | null { - const metadata = - document.metadata && typeof document.metadata === "object" - ? document.metadata - : {} - const metadataClient = - typeof metadata.sm_client === "string" - ? metadata.sm_client - : typeof metadata.sm_internal_plugin_client === "string" - ? metadata.sm_internal_plugin_client - : typeof metadata.sm_internal_mcp_client_name === "string" - ? metadata.sm_internal_mcp_client_name - : null - if (metadataClient) return metadataClient.toLowerCase() + for (const metadata of getDocumentMetadataRecords(document)) { + const metadataClient = + typeof metadata.sm_client === "string" + ? metadata.sm_client + : typeof metadata.sm_internal_plugin_client === "string" + ? metadata.sm_internal_plugin_client + : typeof metadata.sm_internal_mcp_client_name === "string" + ? metadata.sm_internal_mcp_client_name + : null + if (metadataClient) return metadataClient.toLowerCase() + + if (metadata.sm_source === "claude-code-plugin") return "claude_code" + } + + if (hasClaudeCodeContainer(document)) return "claude_code" const content = getDocumentText(document) const title = document.title ?? "" @@ -526,16 +570,17 @@ function parseToolUsage( } for (const doc of recentMcpDocuments) { - const metadata = - doc.metadata && typeof doc.metadata === "object" ? doc.metadata : {} + const metadataRecords = getDocumentMetadataRecords(doc) const clientName = - typeof metadata.sm_internal_mcp_client_name === "string" - ? metadata.sm_internal_mcp_client_name - : null + metadataRecords + .map((record) => record.sm_internal_mcp_client_name) + .find((value): value is string => typeof value === "string") ?? null const pluginClient = getPluginClientFromDocument(doc) const isMcpDocument = doc.source === "mcp" || - metadata.sm_internal_event_from === "mcp" || + metadataRecords.some( + (record) => record.sm_internal_event_from === "mcp", + ) || !!clientName const isPluginDocument = !!pluginClient && pluginClient !== "mcp" diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 885144acd..9d42e1bb8 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -8,7 +8,14 @@ export default async function proxy(request: Request) { console.debug("[PROXY] Path:", url.pathname) console.debug("[PROXY] Method:", request.method) - const sessionCookie = getSessionCookie(request) + const isDevHost = + url.hostname === "localhost" || + url.hostname.includes(".localhost") || + url.hostname.includes(".dev.supermemory.ai") + + const sessionCookie = isDevHost + ? getSessionCookie(request, { cookiePrefix: "better-auth-dev" }) + : getSessionCookie(request) console.debug("[PROXY] Session cookie exists:", !!sessionCookie) // Always allow access to login and waitlist pages @@ -71,6 +78,6 @@ export default async function proxy(request: Request) { export const config = { matcher: [ - "/((?!_next/static|_next/image|images|icon.png|monitoring|opengraph-image.png|bg-rectangle.png|onboarding|ingest|login|api/emails|mcp-supported-tools|mcp-icon.svg).*)", + "/((?!_next/static|_next/image|images|icon.png|manifest.webmanifest|monitoring|opengraph-image.png|bg-rectangle.png|onboarding|ingest|login|api/emails|mcp-supported-tools|mcp-icon.svg).*)", ], } From 91b96c679f1abe326099f510df802d3cb6ac40fd Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 08:08:04 +0000 Subject: [PATCH 16/19] fix: remove duplicate codex key in PLUGIN_INFO Co-Authored-By: Claude Opus 4.5 --- apps/web/app/auth/connect/page.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/apps/web/app/auth/connect/page.tsx b/apps/web/app/auth/connect/page.tsx index e8d0bac85..37f61d3d6 100644 --- a/apps/web/app/auth/connect/page.tsx +++ b/apps/web/app/auth/connect/page.tsx @@ -101,17 +101,6 @@ const PLUGIN_INFO: Record = { ], icon: "/images/plugins/codex.svg", }, - codex: { - name: "OpenAI Codex", - description: - "Memory layer for Codex. Keeps coding context, preferences, and decisions available across sessions.", - features: [ - "Auto-recalls relevant context before prompts", - "Captures useful coding decisions from sessions", - "Supports project and custom memory containers", - ], - icon: "/images/plugins/opencode.svg", - }, } function getPluginName(client: string): string { From 504490f2a889273fde03cb83ac2759625011aff7 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 10:45:16 +0000 Subject: [PATCH 17/19] fix(web): restore UTF-8 encoding in dashboard-view.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix corrupted Unicode characters (⌘, —, ✓, →, ·) that were displaying as mojibake (⌘, â€", âœ", â†', ·) in the TIPS array and other strings. Co-authored-by: Ishaan Gupta --- apps/web/components/dashboard-view.tsx | 110 ++++++++++++------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index 123f2aa6e..5b0af8469 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -141,108 +141,108 @@ export type MemoryOfDay = { const TIPS: Record = { developer: [ - "Use ⌘K to search code snippets and docs by intent, not just keywords", + "Use ⌘K to search code snippets and docs by intent, not just keywords", "Connect Claude MCP to query your saved knowledge from any IDE", - "Save GitHub repos and READMEs — ask questions across all of them", + "Save GitHub repos and READMEs — ask questions across all of them", "Use 'Related' on highlights to find connected technical concepts", - "Save a Stack Overflow answer once — find it again by what it does", + "Save a Stack Overflow answer once — find it again by what it does", "Drop in your last 3 PRs and ask Supermemory for the review patterns", "Save your team's RFCs and surface the ones touching your work", - "Save error messages with their fixes — search by symptom next time", - "Save framework docs once — semantic search beats Cmd+F across pages", + "Save error messages with their fixes — search by symptom next time", + "Save framework docs once — semantic search beats Cmd+F across pages", "Connect Notion to make your engineering specs instantly findable", "Save the docs for libraries you keep forgetting and grep them by intent", "Use Daily Brief to resurface the design doc you skimmed last week", - "Save changelogs as you skim — pull breaking changes back later", - "Save a debugging session as a note — find it again by the symptom", + "Save changelogs as you skim — pull breaking changes back later", + "Save a debugging session as a note — find it again by the symptom", ], research: [ "Save papers and ask questions across your entire reading list", "Use 'Related' on highlights to surface connected research", "Connect Notion to index your notes alongside your papers", "Semantic search means you can ask questions, not just search titles", - "Save a paper once — Supermemory finds it later by what it argued", + "Save a paper once — Supermemory finds it later by what it argued", "Drop in 5 papers on a topic and ask for the consensus and disagreements", - "Save citations as you read — pull them back out by claim", + "Save citations as you read — pull them back out by claim", "Connect Google Drive to make your dataset notes searchable", "Use Daily Brief to resurface a finding you almost forgot", - "Save a methodology note once — find it next time you need that protocol", - "Save preprints alongside your reading list — ask what's new since last week", - "Save quotes with their source — find them later by the idea", + "Save a methodology note once — find it next time you need that protocol", + "Save preprints alongside your reading list — ask what's new since last week", + "Save quotes with their source — find them later by the idea", ], finance: [ "Save articles and ask follow-up questions across your research", "Connect Notion to keep your investment thesis searchable", - "Use ⌘K to find specific data points across all your saves", + "Use ⌘K to find specific data points across all your saves", "Daily Brief surfaces connections you may have missed", - "Save earnings call transcripts once — pull guidance back by ticker or theme", - "Save a thesis once — find it months later by the conviction, not the filename", + "Save earnings call transcripts once — pull guidance back by ticker or theme", + "Save a thesis once — find it months later by the conviction, not the filename", "Drop in three sell-side reports and ask for the disagreements", - "Save market commentary daily — surface the calls that aged well", + "Save market commentary daily — surface the calls that aged well", "Connect Google Drive to query your models without opening them", - "Save a chart with a note — find it again by what it showed", - "Save analyst takes — pull them back when the thesis matters again", + "Save a chart with a note — find it again by what it showed", + "Save analyst takes — pull them back when the thesis matters again", ], design: [ - "Save inspiration and search by concept — 'minimalist UI' finds the right ones", - "Use ⌘K to rediscover references by meaning, not filename", + "Save inspiration and search by concept — 'minimalist UI' finds the right ones", + "Use ⌘K to rediscover references by meaning, not filename", "Connect Notion to make your briefs and moodboards searchable", "Chrome extension saves any page in one click while you browse", - "Save a screenshot with a note — find it later by what it taught you", + "Save a screenshot with a note — find it later by what it taught you", "Drop in 10 onboarding flows and ask Supermemory for the common patterns", - "Save your design crits — find the feedback on a specific decision later", - "Save a brand guideline once — search it by intent, not page number", + "Save your design crits — find the feedback on a specific decision later", + "Save a brand guideline once — search it by intent, not page number", "Connect Google Drive to index your Figma exports and briefs", "Use Daily Brief to resurface a reference that fits today's work", - "Save references by mood — pull them back when the brief calls for it", + "Save references by mood — pull them back when the brief calls for it", ], legal: [ "Save documents and search across them semantically in seconds", "Connect Notion to index your memos and case notes together", "Use Daily Brief to resurface relevant precedents automatically", "Google Drive sync keeps your contracts indexed and queryable", - "Save a clause once — find it next time by what it does, not where it lives", - "Save case law as you read — pull precedents back by argument", + "Save a clause once — find it next time by what it does, not where it lives", + "Save case law as you read — pull precedents back by argument", "Drop in three contracts and ask for the diffs in indemnity language", - "Save regulator updates — surface the ones touching your matter", - "Save a memo once — search by issue, not by file name", - "Save deposition notes — find specific testimony by claim later", + "Save regulator updates — surface the ones touching your matter", + "Save a memo once — search by issue, not by file name", + "Save deposition notes — find specific testimony by claim later", ], marketing: [ - "Save campaigns and resources — ask what worked across all of them", + "Save campaigns and resources — ask what worked across all of them", "Chrome extension captures competitor pages in one click", "Use 'Related' to find similar campaigns in your archive", "Connect Notion to make your campaign briefs instantly searchable", - "Save a competitor's landing page — surface their positioning later by claim", + "Save a competitor's landing page — surface their positioning later by claim", "Drop in five launch retros and ask for the patterns that drove growth", "Save ad references and find them by mood, not by URL", - "Save your weekly metrics notes — pull trends back by quarter", + "Save your weekly metrics notes — pull trends back by quarter", "Use Daily Brief to resurface a positioning note from last campaign", - "Save creative briefs — find similar ones when starting a new one", + "Save creative briefs — find similar ones when starting a new one", ], medical: [ "Save studies and query across your entire reading list", "Connect Notion to keep clinical notes alongside research", - "Use ⌘K to find specific findings across hundreds of papers", + "Use ⌘K to find specific findings across hundreds of papers", "Daily Brief surfaces relevant research from your saves automatically", - "Save a guideline once — pull it back by clinical scenario", + "Save a guideline once — pull it back by clinical scenario", "Drop in three trials and ask Supermemory for the methodological diffs", - "Save case reports — surface them later by symptom or finding", + "Save case reports — surface them later by symptom or finding", "Connect Google Drive to index protocols across your team", - "Save teaching points from rounds — find them by topic next month", - "Save differentials as notes — pull them back when the presentation repeats", + "Save teaching points from rounds — find them by topic next month", + "Save differentials as notes — pull them back when the presentation repeats", ], default: [ - "Use ⌘K to search by meaning — ask questions, not just keywords", + "Use ⌘K to search by meaning — ask questions, not just keywords", "Daily Brief surfaces insights from your saves each morning", "Chrome extension saves any page in one click while you browse", "Connect integrations to make all your knowledge searchable here", - "Save a page once — find it later by what it said, not its title", - "Save the thing you'd normally bookmark — find it again by intent", + "Save a page once — find it later by what it said, not its title", + "Save the thing you'd normally bookmark — find it again by intent", "Drop in 10 articles on a topic and ask for the through-line", - "Save an idea — Supermemory connects it to your earlier ones", + "Save an idea — Supermemory connects it to your earlier ones", "Use Daily Brief to resurface something useful you forgot you saved", - "Save a thread you liked — pull it back later by what it was about", + "Save a thread you liked — pull it back later by what it was about", ], } @@ -270,7 +270,7 @@ const PROFESSION_LABELS: { { value: "medical", label: "Medical" }, ] -// Static plugin metadata — shared between PluginPromoCard and RecommendedPluginsCard +// Static plugin metadata — shared between PluginPromoCard and RecommendedPluginsCard const PLUGIN_STATIC = [ { id: "mcp", @@ -285,7 +285,7 @@ const PLUGIN_STATIC = [ name: "Chrome Extension", Icon: ChromeIcon, accentColor: "#4BA0FA", - tagline: "Save any page in one click — findable by meaning, forever", + tagline: "Save any page in one click — findable by meaning, forever", cta: "Install", }, { @@ -310,7 +310,7 @@ const PLUGIN_STATIC = [ Icon: GoogleDrive, accentColor: "#4BA0FA", tagline: - "Index your Drive files — ask questions across docs, slides, sheets", + "Index your Drive files — ask questions across docs, slides, sheets", cta: "Connect", }, ] as const @@ -479,7 +479,7 @@ function getDocumentPreview(document: DocumentWithMemories): string | null { }) .filter(Boolean) - if (transcriptTurns.length > 0) return transcriptTurns.join(" · ") + if (transcriptTurns.length > 0) return transcriptTurns.join(" · ") const cleaned = compactText( content @@ -870,7 +870,7 @@ function RecommendedPluginsCard({ ) : suggestions.length === 0 ? (

    - You're all set ✓ + You're all set ✓

    ) : ( @@ -893,7 +893,7 @@ function RecommendedPluginsCard({

    - {plugin.cta} → + {plugin.cta} → @@ -908,7 +908,7 @@ function RecommendedPluginsCard({ {PROFESSION_LABELS.find( (p) => p.value === profession, )?.label.toLowerCase()} - ? Change → + ? Change → )} @@ -946,7 +946,7 @@ function MemoryOfDayCard({ data }: { data: MemoryOfDay }) {
    - View memories → + View memories → ) @@ -1301,7 +1301,7 @@ export function DashboardView({ )} - {/* Daily Brief — hero */} + {/* Daily Brief — hero */} - {/* Actions + connection status — single unified row */} + {/* Actions + connection status — single unified row */} - · + · - · + ·