From debac33ebaea4cbdbc6e927d98536df4f75d6be3 Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Fri, 8 May 2026 22:42:55 +0000 Subject: [PATCH 01/22] feat: autumn migration (#902) --- apps/web/app/auth/connect/page.tsx | 2 +- .../components/add-document/connections.tsx | 10 +- apps/web/components/add-document/index.tsx | 61 ++----- apps/web/components/integrations-view.tsx | 2 +- .../integrations/plugins-detail.tsx | 4 +- apps/web/components/settings/account.tsx | 165 ++++++------------ .../components/settings/connections-mcp.tsx | 11 +- apps/web/hooks/use-token-usage.ts | 49 ++++-- apps/web/lib/billing-utils.ts | 9 - apps/web/package.json | 2 +- bun.lock | 16 +- packages/lib/auth-context.tsx | 12 +- packages/lib/queries.ts | 16 +- 13 files changed, 139 insertions(+), 220 deletions(-) diff --git a/apps/web/app/auth/connect/page.tsx b/apps/web/app/auth/connect/page.tsx index 544002e0..5839062c 100644 --- a/apps/web/app/auth/connect/page.tsx +++ b/apps/web/app/auth/connect/page.tsx @@ -169,7 +169,7 @@ function AuthConnectContent() { setIsUpgrading(true) const safeSuccessUrl = `${window.location.origin}${window.location.pathname}?callback=${encodeURIComponent(callback ?? "")}&client=${encodeURIComponent(validClient ?? "")}` await autumn.attach({ - productId: "api_pro", + planId: "api_pro", successUrl: safeSuccessUrl, }) } catch (err) { diff --git a/apps/web/components/add-document/connections.tsx b/apps/web/components/add-document/connections.tsx index e9114276..5814f55c 100644 --- a/apps/web/components/add-document/connections.tsx +++ b/apps/web/components/add-document/connections.tsx @@ -226,7 +226,7 @@ interface ConnectContentProps { export function ConnectContent({ selectedProject }: ConnectContentProps) { const queryClient = useQueryClient() const autumn = useCustomer() - const isProUser = hasActivePlan(autumn.customer?.products, "api_pro") + const isProUser = hasActivePlan(autumn.data?.subscriptions, "api_pro") const [connectingProvider, setConnectingProvider] = useState(null) const [gdriveSyncScope, setGdriveSyncScope] = @@ -244,7 +244,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { setIsUpgrading(true) try { await autumn.attach({ - productId: "api_pro", + planId: "api_pro", successUrl: window.location.href, }) } catch (error) { @@ -254,9 +254,9 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { } } - const connectionsFeature = autumn.customer?.features?.connections - const connectionsUsed = connectionsFeature?.usage ?? 0 - const connectionsLimit = connectionsFeature?.included_usage ?? 10 + const connectionsBalance = autumn.data?.balances?.connections + const connectionsUsed = connectionsBalance?.usage ?? 0 + const connectionsLimit = connectionsBalance?.granted ?? 10 const canAddConnection = connectionsUsed < connectionsLimit // Fetch connections diff --git a/apps/web/components/add-document/index.tsx b/apps/web/components/add-document/index.tsx index 4c441ce1..f44ad898 100644 --- a/apps/web/components/add-document/index.tsx +++ b/apps/web/components/add-document/index.tsx @@ -16,7 +16,7 @@ import { toast } from "sonner" import { useDocumentMutations } from "../../hooks/use-document-mutations" import { useCustomer } from "autumn-js/react" import { useTokenUsage } from "@/hooks/use-token-usage" -import { tokensToCredits, formatUsageNumber } from "@/lib/billing-utils" +import { formatUsageNumber } from "@/lib/billing-utils" import { SpaceSelector } from "../space-selector" import { useIsMobile } from "@hooks/use-mobile" import { addDocumentParam } from "@/lib/search-params" @@ -127,11 +127,8 @@ export function AddDocument({ const autumn = useCustomer() const { tokensUsed, - tokensLimit, - tokensPercent, searchesUsed, - searchesLimit, - searchesPercent, + planUsagePct, hasPaidPlan, isLoading: isLoadingUsage, } = useTokenUsage(autumn) @@ -298,72 +295,46 @@ export function AddDocument({ dmSansClassName(), )} > - Credits + Plan usage {isLoadingUsage ? "…" - : `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}`} + : `${planUsagePct < 1 && planUsagePct > 0 ? "< 1" : Math.round(planUsagePct)}% used`}
80 + planUsagePct > 80 ? "#ef4444" : hasPaidPlan ? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)" : "#0054AD", }} + title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`} />
-
- -
-
- - Search Queries - - - {isLoadingUsage - ? "…" - : `${formatUsageNumber(searchesUsed)} / ${formatUsageNumber(searchesLimit)}`} - -
-
-
80 - ? "#ef4444" - : hasPaidPlan - ? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)" - : "#0054AD", - }} - /> -
+ {formatUsageNumber(tokensUsed)} tokens ·{" "} + {formatUsageNumber(searchesUsed)} queries +

+ )}
{!hasPaidPlan && ( @@ -373,7 +344,7 @@ export function AddDocument({ setIsUpgrading(true) try { await autumn.attach({ - productId: "api_pro", + planId: "api_pro", successUrl: "https://app.supermemory.ai/settings#account", }) window.location.reload() diff --git a/apps/web/components/integrations-view.tsx b/apps/web/components/integrations-view.tsx index ab8b0b1b..70424e2e 100644 --- a/apps/web/components/integrations-view.tsx +++ b/apps/web/components/integrations-view.tsx @@ -170,7 +170,7 @@ export function IntegrationsView() { const [, setAddDoc] = useQueryState("add", addDocumentParam) const { org } = useAuth() const autumn = useCustomer() - const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro") + const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro") const { data: connections = [] } = useQuery({ queryKey: ["connections"], diff --git a/apps/web/components/integrations/plugins-detail.tsx b/apps/web/components/integrations/plugins-detail.tsx index 8f157a69..0b4cb684 100644 --- a/apps/web/components/integrations/plugins-detail.tsx +++ b/apps/web/components/integrations/plugins-detail.tsx @@ -386,7 +386,7 @@ export function PluginsDetail() { }) const [keyCopied, setKeyCopied] = useState(false) - const hasProProduct = hasActivePlan(autumn.customer?.products, "api_pro") + const hasProProduct = hasActivePlan(autumn.data?.subscriptions, "api_pro") const { data: pluginsData } = useQuery({ queryFn: async () => { @@ -496,7 +496,7 @@ export function PluginsDetail() { const handleUpgrade = async () => { try { await autumn.attach({ - productId: "api_pro", + planId: "api_pro", successUrl: "https://app.supermemory.ai/?view=integrations", }) window.location.reload() diff --git a/apps/web/components/settings/account.tsx b/apps/web/components/settings/account.tsx index 4ca5a9cb..dfe34d66 100644 --- a/apps/web/components/settings/account.tsx +++ b/apps/web/components/settings/account.tsx @@ -9,7 +9,7 @@ import { } from "@/hooks/use-account-settings" import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar" import { useTokenUsage } from "@/hooks/use-token-usage" -import { formatUsageNumber, tokensToCredits } from "@/lib/billing-utils" +import { formatUsageNumber } from "@/lib/billing-utils" import { Dialog, DialogContent, @@ -153,11 +153,8 @@ export default function Account() { const { tokensUsed, - tokensLimit, - tokensPercent, searchesUsed, - searchesLimit, - searchesPercent, + planUsagePct, currentPlan, hasPaidPlan, isLoading: isCheckingStatus, @@ -176,7 +173,7 @@ export default function Account() { setIsUpgrading(true) try { await autumn.attach({ - productId: "api_pro", + planId: "api_pro", successUrl: "https://app.supermemory.ai/settings#account", }) window.location.reload() @@ -388,7 +385,7 @@ export default function Account() {

- {/* Credits Usage Progress */} + {/* Plan usage (unified) */}

- Credits Used + Plan usage

- {tokensToCredits(tokensUsed)} /{" "} - {tokensToCredits(tokensLimit)} + {planUsagePct < 1 && planUsagePct > 0 + ? "< 1" + : Math.round(planUsagePct)} + % used
80 + planUsagePct > 80 ? "#ef4444" : "linear-gradient(to right, #4BA0FA 80%, #002757 100%)", }} + title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`} />
-
- - {/* Search Queries Progress */} -
-
-

- Search Queries -

- - {formatUsageNumber(searchesUsed)} /{" "} - {formatUsageNumber(searchesLimit)} - -
-
-
80 - ? "#ef4444" - : "linear-gradient(to right, #4BA0FA 80%, #002757 100%)", - }} - /> -
-
- - {/* Days remaining indicator */} - {daysRemaining !== null && (

- Resets in {daysRemaining} day - {daysRemaining !== 1 ? "s" : ""} + {formatUsageNumber(tokensUsed)} tokens ·{" "} + {formatUsageNumber(searchesUsed)} queries + {daysRemaining !== null && ( + <> + {" · resets in "} + {daysRemaining} day{daysRemaining !== 1 ? "s" : ""} + + )}

- )} +
+
+ )}
{tabs.map((tab) => ( @@ -285,6 +310,31 @@ export function AddDocument({ ))}
+ {isMobile && ( +
+ + +
+ )} + {!isMobile && (
@@ -385,11 +435,11 @@ export function AddDocument({
-
+
{activeTab === "note" && (
{!isMobile && ( @@ -438,13 +490,19 @@ export function AddDocument({ /> )}
@@ -455,6 +513,7 @@ export function AddDocument({ disabled={ activeTab === "file" ? fileTabSubmitDisabled : isSubmitting } + className={cn(isMobile && "h-11 min-w-[8rem] px-5")} > {isSubmitting ? ( <> @@ -485,6 +544,53 @@ export function AddDocument({ ) } +function UsageMeter({ + label, + value, + percent, + active, +}: { + label: string + value: string + percent: number + active: boolean +}) { + const safePercent = Math.max(0, Math.min(100, percent)) + + return ( +
+
+ + {label} + + + {value} + +
+
+
80 + ? "#ef4444" + : active + ? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)" + : "#0054AD", + }} + /> +
+
+ ) +} + function TabButton({ active, onClick, @@ -508,19 +614,24 @@ function TabButton({ type="button" onClick={onClick} className={cn( - "flex items-center gap-2 px-3 py-2 rounded-full text-left transition-colors whitespace-nowrap focus:outline-none focus:ring-0 shrink-0", - active ? "bg-[#14161A] shadow-inside-out" : "hover:bg-[#14161A]/50", + "relative flex h-14 min-w-0 flex-col items-center justify-center gap-1 rounded-xl px-1 text-center transition-colors focus:outline-none focus:ring-0", + active + ? "bg-[#0F141B] text-white shadow-inside-out ring-1 ring-[#2261CA33]" + : "text-[#8B8B8B] hover:bg-[#14161A]/50", dmSansClassName(), )} > - + {title.split(" ")[0]} {isPro && ( - + PRO )} diff --git a/apps/web/components/add-document/note.tsx b/apps/web/components/add-document/note.tsx index 4f833ca2..aa0fac97 100644 --- a/apps/web/components/add-document/note.tsx +++ b/apps/web/components/add-document/note.tsx @@ -40,11 +40,12 @@ export function NoteContent({ }, [isOpen, onContentChange]) return ( -
+
) diff --git a/apps/web/components/graph-layout-view.tsx b/apps/web/components/graph-layout-view.tsx index 0a21c357..97e6a1ab 100644 --- a/apps/web/components/graph-layout-view.tsx +++ b/apps/web/components/graph-layout-view.tsx @@ -3,6 +3,7 @@ import { memo, useCallback, useRef } from "react" import { useQueryState } from "nuqs" import Image from "next/image" +import { Share2 } from "lucide-react" import { MemoryGraph } from "./memory-graph" import { useProject } from "@/stores" import { useGraphHighlights } from "@/stores/highlights" @@ -30,7 +31,7 @@ export const GraphLayoutView = memo(function GraphLayoutView() { }, [setIsShareModalOpen]) return ( -
+
{/* Full-width graph */}
{/* Share graph button - top left */} -
+
diff --git a/apps/web/components/text-editor/index.tsx b/apps/web/components/text-editor/index.tsx index 18de5893..71771e49 100644 --- a/apps/web/components/text-editor/index.tsx +++ b/apps/web/components/text-editor/index.tsx @@ -17,10 +17,12 @@ export function TextEditor({ content: initialContent, onContentChange, onSubmit, + debounceMs = 500, }: { content: string | undefined onContentChange: (content: string) => void onSubmit: () => void + debounceMs?: number }) { const containerRef = useRef(null) const editorRef = useRef(null) @@ -36,7 +38,7 @@ export function TextEditor({ const json = editor.getJSON() const markdown = editor.storage.markdown?.manager?.serialize(json) ?? "" onContentChange?.(markdown) - }, 500) + }, debounceMs) const editor = useEditor({ extensions, @@ -48,6 +50,13 @@ export function TextEditor({ }, onUpdate: ({ editor }) => { editorRef.current = editor + if (!hasUserEditedRef.current) return + if (debounceMs === 0) { + const json = editor.getJSON() + const markdown = editor.storage.markdown?.manager?.serialize(json) ?? "" + onContentChange?.(markdown) + return + } debouncedUpdates(editor) }, editorProps: { @@ -120,7 +129,7 @@ export function TextEditor({ tabIndex={0} ref={containerRef} onClick={handleClick} - className="w-full h-full outline-none prose prose-invert max-w-none [&_.ProseMirror]:outline-none [&_.ProseMirror]:focus:outline-none [&_.ProseMirror-focused]:outline-none text-editor-prose cursor-text" + className="h-full w-full cursor-text outline-none prose prose-invert max-w-none text-editor-prose [&_.ProseMirror]:min-h-full [&_.ProseMirror]:outline-none [&_.ProseMirror]:text-[15px] [&_.ProseMirror]:leading-6 [&_.ProseMirror]:text-[#D7DEE8] [&_.ProseMirror-focused]:outline-none [&_.ProseMirror]:focus:outline-none" >
From 5771618dbb0ba0cc10112a946564e2352a86b428 Mon Sep 17 00:00:00 2001 From: Vedant Mahajan Date: Sat, 9 May 2026 21:48:08 +0530 Subject: [PATCH 03/22] Fix missing blue gradient (Nova tint) on mobile and adjust position (#911) --- apps/web/app/(app)/page.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/web/app/(app)/page.tsx b/apps/web/app/(app)/page.tsx index 4c54f582..9be8924f 100644 --- a/apps/web/app/(app)/page.tsx +++ b/apps/web/app/(app)/page.tsx @@ -78,7 +78,7 @@ const GRADIENT_TOP_WIDTH_MAX = 1440 function gradientTopPositionForWidth(width: number) { const minW = 320 const pctWide = 15 - const pctNarrow = 70 + const pctNarrow = 55 const w = Math.min(GRADIENT_TOP_WIDTH_MAX, Math.max(minW, width)) const t = (w - minW) / (GRADIENT_TOP_WIDTH_MAX - minW) const eased = t * t @@ -515,11 +515,11 @@ export default function NewPage() { const gradientTopPosition = gradientTopPositionForWidth(viewportWidth) const isChatView = viewMode === "chat" + const showNovaBackdrop = + viewMode === "graph" || viewMode === "list" || viewMode === "dashboard" + const isDashboardShell = + viewMode === "dashboard" || (viewMode === "graph" && isMobile) const isGraphMode = viewMode === "graph" - const isMemoriesDesktop = viewMode === "list" && !isMobile - const isHomeDesktop = viewMode === "dashboard" && !isMobile - const showNovaBackdrop = isGraphMode || isMemoriesDesktop || isHomeDesktop - const isDashboardShell = viewMode === "dashboard" return ( From 03e37df455aa1eedcc359a2133672138c39de61a Mon Sep 17 00:00:00 2001 From: Dhravya Shah Date: Sat, 9 May 2026 10:25:33 -0700 Subject: [PATCH 04/22] fix: update web app for presigned R2 URL compatibility (#916) --- .../document-cards/file-preview.tsx | 17 ++++++++++-- .../document-modal/content/image-preview.tsx | 21 +++++++++++---- .../components/document-modal/content/pdf.tsx | 26 +++++++++++++++---- apps/web/components/memories-grid.tsx | 6 ++--- apps/web/lib/url-helpers.ts | 23 ++++++++++++++++ 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/apps/web/components/document-cards/file-preview.tsx b/apps/web/components/document-cards/file-preview.tsx index 6013a203..9965d9be 100644 --- a/apps/web/components/document-cards/file-preview.tsx +++ b/apps/web/components/document-cards/file-preview.tsx @@ -1,6 +1,6 @@ "use client" -import { memo, useState } from "react" +import { memo, useCallback, useState } from "react" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import type { z } from "zod" import { dmSansClassName } from "@/lib/fonts" @@ -49,6 +49,7 @@ export const FilePreview = memo(function FilePreview({ document: DocumentWithMemories }) { const [imageError, setImageError] = useState(false) + const [retryKey, setRetryKey] = useState(0) const { extension, color } = getFileTypeInfo(document) const type = document.type?.toLowerCase() @@ -58,6 +59,17 @@ export const FilePreview = memo(function FilePreview({ document.url && !imageError + // On first failure, wait briefly then force a re-render with a new key to + // retry the fetch (covers transient R2 timing issues). + // On second failure, give up and show the fallback file icon view. + const handleImageError = useCallback(() => { + if (retryKey === 0) { + setTimeout(() => setRetryKey(1), 500) + return + } + setImageError(true) + }, [retryKey]) + return (
{color && ( @@ -80,10 +92,11 @@ export const FilePreview = memo(function FilePreview({ />
{document.title setImageError(true)} + onError={handleImageError} loading="lazy" />
diff --git a/apps/web/components/document-modal/content/image-preview.tsx b/apps/web/components/document-modal/content/image-preview.tsx index 31d5c1c0..cfa200de 100644 --- a/apps/web/components/document-modal/content/image-preview.tsx +++ b/apps/web/components/document-modal/content/image-preview.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useCallback, useState } from "react" import { cn } from "@lib/utils" interface ImagePreviewProps { @@ -11,6 +11,19 @@ interface ImagePreviewProps { export function ImagePreview({ url, title }: ImagePreviewProps) { const [imageError, setImageError] = useState(false) const [isLoading, setIsLoading] = useState(true) + const [retryKey, setRetryKey] = useState(0) + + // On first failure, wait briefly then force a re-render with a new key to + // retry the fetch (covers transient R2 timing issues). + // On second failure, give up and show the error state. + const handleImageError = useCallback(() => { + if (retryKey === 0) { + setTimeout(() => setRetryKey(1), 500) + return + } + setImageError(true) + setIsLoading(false) + }, [retryKey]) if (imageError || !url) { return ( @@ -38,16 +51,14 @@ export function ImagePreview({ url, title }: ImagePreviewProps) { />
{title { - setImageError(true) - setIsLoading(false) - }} + onError={handleImageError} onLoad={() => setIsLoading(false)} loading="lazy" /> diff --git a/apps/web/components/document-modal/content/pdf.tsx b/apps/web/components/document-modal/content/pdf.tsx index a025cf61..a4b896ac 100644 --- a/apps/web/components/document-modal/content/pdf.tsx +++ b/apps/web/components/document-modal/content/pdf.tsx @@ -1,7 +1,7 @@ "use client" import { Document, Page, pdfjs } from "react-pdf" -import { useState } from "react" +import { useCallback, useState } from "react" import "react-pdf/dist/Page/AnnotationLayer.css" import "react-pdf/dist/Page/TextLayer.css" @@ -19,6 +19,7 @@ export function PdfViewer({ url }: PdfViewerProps) { const [numPages, setNumPages] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const [retryKey, setRetryKey] = useState(0) if (!url) { return ( @@ -34,10 +35,24 @@ export function PdfViewer({ url }: PdfViewerProps) { setError(null) } - function onDocumentLoadError(error: Error) { - setError(error.message || "Failed to load PDF") - setLoading(false) - } + // On first failure, wait briefly then force a re-mount of the Document + // component to retry (covers transient R2 timing issues). + // On second failure, give up and show the error state. + const onDocumentLoadError = useCallback( + (err: Error) => { + if (retryKey === 0) { + setTimeout(() => { + setRetryKey(1) + setLoading(true) + setError(null) + }, 500) + return + } + setError(err.message || "Failed to load PDF") + setLoading(false) + }, + [retryKey], + ) return (
@@ -53,6 +68,7 @@ export function PdfViewer({ url }: PdfViewerProps) { )}
{document.url && - !document.url.includes("files.supermemory.ai") && + !isSupermemoryFileUrl(document.url) && (document.title || (!document.url.includes("x.com") && !document.url.includes("twitter.com"))) && ( diff --git a/apps/web/lib/url-helpers.ts b/apps/web/lib/url-helpers.ts index d171a6c2..832b8722 100644 --- a/apps/web/lib/url-helpers.ts +++ b/apps/web/lib/url-helpers.ts @@ -189,6 +189,29 @@ export function toLinkedInProfileUrl(handle: string): string { return `https://linkedin.com/in/${handle.trim()}` } +/** + * Checks if a URL points to a supermemory-hosted file. + * Matches the public bucket domain (files.supermemory.ai) and + * presigned R2 URLs whose hostname ends with `.r2.cloudflarestorage.com`. + * + * Note: The R2 check is intentionally broad — it matches any Cloudflare R2 + * presigned URL, not only supermemory's account. This is acceptable because + * the function is only called on `document.url` values returned by our own + * backend, where all R2 URLs originate from the supermemory bucket. + * If user-supplied external R2 URLs ever appear in this field, tighten the + * check by also validating the account-id subdomain or the bucket path prefix. + */ +export const isSupermemoryFileUrl = (url: string): boolean => { + try { + const parsed = new URL(url) + if (parsed.hostname === "files.supermemory.ai") return true + if (parsed.hostname.endsWith(".r2.cloudflarestorage.com")) return true + return false + } catch { + return false + } +} + /** * Gets the favicon URL for a given URL. */ From 0cbcd44e765959cdce469ea554fa1487a933b06c Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommu Date: Sat, 9 May 2026 11:29:14 -0700 Subject: [PATCH 05/22] fix: mobile view usage bar fix (#914) --- apps/web/components/add-document/index.tsx | 116 +++++++++------------ 1 file changed, 48 insertions(+), 68 deletions(-) diff --git a/apps/web/components/add-document/index.tsx b/apps/web/components/add-document/index.tsx index f422ef6b..ee7efee9 100644 --- a/apps/web/components/add-document/index.tsx +++ b/apps/web/components/add-document/index.tsx @@ -311,27 +311,54 @@ export function AddDocument({
{isMobile && ( -
- - +
+
+ + Plan usage + + + {isLoadingUsage + ? "…" + : `${planUsagePct < 1 && planUsagePct > 0 ? "< 1" : Math.round(planUsagePct)}% used`} + +
+
+
80 + ? "#ef4444" + : hasPaidPlan + ? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)" + : "#0054AD", + }} + title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`} + /> +
+ {!isLoadingUsage && ( +

+ {formatUsageNumber(tokensUsed)} tokens ·{" "} + {formatUsageNumber(searchesUsed)} queries +

+ )}
)} @@ -544,53 +571,6 @@ export function AddDocument({ ) } -function UsageMeter({ - label, - value, - percent, - active, -}: { - label: string - value: string - percent: number - active: boolean -}) { - const safePercent = Math.max(0, Math.min(100, percent)) - - return ( -
-
- - {label} - - - {value} - -
-
-
80 - ? "#ef4444" - : active - ? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)" - : "#0054AD", - }} - /> -
-
- ) -} - function TabButton({ active, onClick, From 8f6c16de49641abddd10058cb4c8dce63da6c6eb Mon Sep 17 00:00:00 2001 From: MaheshtheDev <38828053+MaheshtheDev@users.noreply.github.com> Date: Sat, 9 May 2026 19:12:01 +0000 Subject: [PATCH 06/22] chore: improve the codebase with react doctor (#917) --- apps/web/app/(app)/onboarding/page.tsx | 10 +-- apps/web/app/(auth)/login/new/page.tsx | 16 ++--- apps/web/app/api/emails/welcome/route.tsx | 2 +- apps/web/app/auth/connect/page.tsx | 10 +-- apps/web/app/ref/[code]/page.tsx | 14 ++-- apps/web/app/ref/page.tsx | 4 +- apps/web/app/upgrade-mcp/page.tsx | 12 ++-- .../components/add-document/connections.tsx | 26 +++---- apps/web/components/add-document/file.tsx | 4 +- apps/web/components/add-document/index.tsx | 71 ++++++------------- apps/web/components/add-document/link.tsx | 6 +- apps/web/components/add-space-modal.tsx | 4 +- apps/web/components/chat/index.tsx | 24 +++---- apps/web/components/chat/input/actions.tsx | 2 +- .../components/chat/message/agent-message.tsx | 8 +-- apps/web/components/connect-ai-modal.tsx | 28 ++++---- apps/web/components/dashboard-view.tsx | 4 +- .../document-cards/file-preview.tsx | 4 +- .../document-cards/google-docs-preview.tsx | 2 +- .../document-cards/note-preview.tsx | 2 +- .../document-cards/notion-preview.tsx | 12 ++-- .../document-cards/tweet-preview.tsx | 2 +- .../document-cards/website-preview.tsx | 2 +- .../document-cards/youtube-preview.tsx | 2 +- apps/web/components/document-icon.tsx | 12 ++-- .../document-modal/content/google-doc.tsx | 6 +- .../document-modal/content/image-preview.tsx | 6 +- .../document-modal/content/index.tsx | 2 +- .../components/document-modal/content/pdf.tsx | 4 +- .../content/text-editor-content.tsx | 2 +- .../document-modal/content/tweet.tsx | 4 +- .../document-modal/content/yt-video.tsx | 4 +- .../document-modal/graph-list-memories.tsx | 50 ++++++------- apps/web/components/document-modal/index.tsx | 20 +++--- .../web/components/document-modal/summary.tsx | 4 +- apps/web/components/document-modal/title.tsx | 2 +- .../components/documents-command-palette.tsx | 3 +- apps/web/components/feedback-modal.tsx | 6 +- apps/web/components/fullscreen-note-modal.tsx | 2 +- apps/web/components/header.tsx | 28 ++++---- apps/web/components/integration-icons.tsx | 20 +++--- .../components/integrations/chrome-detail.tsx | 2 +- .../integrations/plugins-detail.tsx | 14 ++-- .../integrations/raycast-detail.tsx | 8 +-- .../integrations/shortcuts-detail.tsx | 6 +- .../claude-desktop-manual-timeline.tsx | 2 +- apps/web/components/memories-grid.tsx | 28 ++++---- .../components/memory-graph/graph-card.tsx | 4 +- .../memory-graph/memory-graph-wrapper.tsx | 2 +- apps/web/components/next-app-research-cta.tsx | 2 +- apps/web/components/quick-note-card.tsx | 2 +- .../components/remove-connection-dialog.tsx | 4 +- apps/web/components/select-spaces-modal.tsx | 8 +-- apps/web/components/settings/account.tsx | 2 +- apps/web/components/settings/integrations.tsx | 24 +++---- apps/web/components/share-modal.tsx | 28 +++----- apps/web/components/space-selector.tsx | 10 +-- apps/web/components/superloader.tsx | 4 +- apps/web/components/text-editor/index.tsx | 2 +- apps/web/components/timeline-view.tsx | 8 +-- apps/web/components/user-profile-menu.tsx | 12 ++-- 61 files changed, 292 insertions(+), 326 deletions(-) diff --git a/apps/web/app/(app)/onboarding/page.tsx b/apps/web/app/(app)/onboarding/page.tsx index ac554055..fb4403c1 100644 --- a/apps/web/app/(app)/onboarding/page.tsx +++ b/apps/web/app/(app)/onboarding/page.tsx @@ -53,7 +53,7 @@ function XIcon({ className }: { className?: string }) { fill="currentColor" aria-hidden="true" > - + ) } @@ -66,7 +66,7 @@ function LinkedInIcon({ className }: { className?: string }) { fill="currentColor" aria-hidden="true" > - + ) } @@ -76,7 +76,7 @@ function SubmitArrow() { Submit @@ -809,7 +809,7 @@ export default function OnboardingPage() { Finishing your first save

- Most finish in under a minute. Below is optional — ways to add + Most finish in under a minute. Below is optional: ways to add more later.

@@ -948,7 +948,7 @@ export default function OnboardingPage() {

Your first save is ready. When you want more, use Integrations - for browser, phone, editor, and AI tools — all in one place. + for browser, phone, editor, and AI tools, all in one place.

diff --git a/apps/web/app/(auth)/login/new/page.tsx b/apps/web/app/(auth)/login/new/page.tsx index 421d0f06..82d0fd48 100644 --- a/apps/web/app/(auth)/login/new/page.tsx +++ b/apps/web/app/(auth)/login/new/page.tsx @@ -313,7 +313,7 @@ export default function LoginPage() { Google @@ -378,7 +378,7 @@ export default function LoginPage() { @@ -399,7 +399,7 @@ export default function LoginPage() { diff --git a/apps/web/app/api/emails/welcome/route.tsx b/apps/web/app/api/emails/welcome/route.tsx index d8f6c59f..ecc26964 100644 --- a/apps/web/app/api/emails/welcome/route.tsx +++ b/apps/web/app/api/emails/welcome/route.tsx @@ -3,7 +3,7 @@ import { ImageResponse } from "next/og" export async function GET() { return new ImageResponse( -
+
Google Logo
-
+
{pluginInfo ? ( {pluginInfo.name}
-
+
{pluginInfo ? ( {pluginInfo.name} - Upgrading... + Upgrading… ) : ( "Upgrade to Pro \u2014 $19/month" @@ -418,9 +418,9 @@ function AuthConnectContent() {

- {status === "creating" && `Connecting ${displayName}...`} + {status === "creating" && `Connecting ${displayName}…`} {status === "success" && - `Success! Redirecting back to ${displayName}...`} + `Success! Redirecting back to ${displayName}…`}

diff --git a/apps/web/app/ref/[code]/page.tsx b/apps/web/app/ref/[code]/page.tsx index 2086d6b4..23f5ce3a 100644 --- a/apps/web/app/ref/[code]/page.tsx +++ b/apps/web/app/ref/[code]/page.tsx @@ -82,8 +82,8 @@ export default function ReferralPage() { return (
- -

Checking invitation...

+ +

Checking invitation…

) @@ -119,8 +119,8 @@ export default function ReferralPage() { {/* Welcome Card */} -
- +
+
You're invited to supermemory! @@ -181,9 +181,9 @@ export default function ReferralPage() { className="shrink-0 border-white/10 hover:bg-white/5" > {copiedLink ? ( - + ) : ( - + )}
@@ -193,7 +193,7 @@ export default function ReferralPage() { variant="outline" className="w-full border-white/10 text-white hover:bg-white/5" > - + Share this link
diff --git a/apps/web/app/ref/page.tsx b/apps/web/app/ref/page.tsx index 853ed900..9c97cdd6 100644 --- a/apps/web/app/ref/page.tsx +++ b/apps/web/app/ref/page.tsx @@ -16,8 +16,8 @@ export default function ReferralHomePage() {
-
- +
+
Missing Referral Code diff --git a/apps/web/app/upgrade-mcp/page.tsx b/apps/web/app/upgrade-mcp/page.tsx index 0b4fb05b..fbe52520 100644 --- a/apps/web/app/upgrade-mcp/page.tsx +++ b/apps/web/app/upgrade-mcp/page.tsx @@ -152,7 +152,7 @@ export default function MigrateMCPPage() {
- +
@@ -177,7 +177,7 @@ export default function MigrateMCPPage() { className="text-sm font-medium text-slate-200 flex items-center gap-2" htmlFor="mcpUrl" > - + MCP URL
@@ -231,13 +231,13 @@ export default function MigrateMCPPage() { > {migrateMutation.isPending ? ( <> - - Migrating documents... + + Migrating documents… ) : ( <> Start Upgrade - + )} @@ -260,7 +260,7 @@ export default function MigrateMCPPage() {
- +

Migration completed successfully!

diff --git a/apps/web/components/add-document/connections.tsx b/apps/web/components/add-document/connections.tsx index 5814f55c..955a0345 100644 --- a/apps/web/components/add-document/connections.tsx +++ b/apps/web/components/add-document/connections.tsx @@ -409,7 +409,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { className="bg-[#14161A] rounded-[12px] px-4 py-3 flex items-center justify-between gap-3" >
- +

{config.title}

@@ -431,7 +431,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { className="bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 text-[14px] font-medium px-3 h-8 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" > {isConnecting ? ( - + ) : ( "Connect" )} @@ -443,7 +443,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { type="button" className="bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 px-1.5 h-8 flex items-center transition-colors" > - + @@ -463,7 +463,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { > {label} {gdriveSyncScope === scope && ( - + )} ))} @@ -483,7 +483,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { className="bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 text-[14px] font-medium px-3 py-1.5 h-8" > {isConnecting ? ( - + ) : ( "Connect" )} @@ -524,7 +524,7 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { className="flex items-center gap-1.5 bg-[#4BA0FA] text-black hover:bg-[#4BA0FA]/90 disabled:opacity-50 disabled:cursor-not-allowed text-[13px] font-medium rounded-full h-8 px-3 transition-colors shrink-0" > {isAnyConnecting ? ( - + ) : ( <> + Add a connection @@ -650,14 +650,14 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) { id="no-active-connections" className="bg-[#14161A] shadow-inside-out rounded-[12px] px-4 py-6 h-full mb-4 flex flex-col justify-center items-center" > - + {!isProUser ? ( <>

{isUpgrading || autumn.isLoading ? ( - - Upgrading... + + Upgrading… ) : ( <> @@ -676,19 +676,19 @@ export function ConnectContent({ selectedProject }: ConnectContentProps) {

- + Unlimited memories
- + 10 connections
- + Advanced search
- + Priority support
diff --git a/apps/web/components/add-document/file.tsx b/apps/web/components/add-document/file.tsx index 67e70d01..b9212bc3 100644 --- a/apps/web/components/add-document/file.tsx +++ b/apps/web/components/add-document/file.tsx @@ -210,10 +210,10 @@ export function FileContent({ multiple onChange={handleFileSelect} disabled={isSubmitting} - className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed" + className="absolute inset-0 size-full opacity-0 cursor-pointer disabled:cursor-not-allowed" accept={FILE_ACCEPT} /> -
+
{hasItems ? ( diff --git a/apps/web/components/add-document/index.tsx b/apps/web/components/add-document/index.tsx index ee7efee9..eb286cff 100644 --- a/apps/web/components/add-document/index.tsx +++ b/apps/web/components/add-document/index.tsx @@ -311,54 +311,27 @@ export function AddDocument({
{isMobile && ( -
-
- - Plan usage - - - {isLoadingUsage +
+ 0 ? "< 1" : Math.round(planUsagePct)}% used`} - -
-
-
80 - ? "#ef4444" - : hasPaidPlan - ? "linear-gradient(to right, #4BA0FA 80%, #002757 100%)" - : "#0054AD", - }} - title={`${formatUsageNumber(tokensUsed)} tokens · ${formatUsageNumber(searchesUsed)} queries`} - /> -
- {!isLoadingUsage && ( -

- {formatUsageNumber(tokensUsed)} tokens ·{" "} - {formatUsageNumber(searchesUsed)} queries -

- )} + : `${tokensToCredits(tokensUsed)} / ${tokensToCredits(tokensLimit)}` + } + percent={tokensPercent} + active={hasPaidPlan} + /> +
)} @@ -448,7 +421,7 @@ export function AddDocument({ {isUpgrading ? ( <> - Upgrading... + Upgrading… ) : ( "Upgrade to Pro" @@ -545,7 +518,7 @@ export function AddDocument({ {isSubmitting ? ( <> - Adding... + Adding… ) : ( <> diff --git a/apps/web/components/add-document/link.tsx b/apps/web/components/add-document/link.tsx index 2efb67dc..93e8821a 100644 --- a/apps/web/components/add-document/link.tsx +++ b/apps/web/components/add-document/link.tsx @@ -173,7 +173,7 @@ export function LinkContent({ {isPreviewLoading ? ( <> - Loading... + Loading… ) : ( "Preview Link" @@ -216,7 +216,7 @@ export function LinkContent({ {title { e.currentTarget.style.display = "none" e.currentTarget.parentElement?.classList.add("opacity-50") @@ -228,7 +228,7 @@ export function LinkContent({
) : (
- +
)}
diff --git a/apps/web/components/add-space-modal.tsx b/apps/web/components/add-space-modal.tsx index be666083..7c9f3cf3 100644 --- a/apps/web/components/add-space-modal.tsx +++ b/apps/web/components/add-space-modal.tsx @@ -150,7 +150,7 @@ export function AddSpaceModal({

- Creating... + Creating… ) : ( <> diff --git a/apps/web/components/chat/index.tsx b/apps/web/components/chat/index.tsx index 4a148929..8ef1d22c 100644 --- a/apps/web/components/chat/index.tsx +++ b/apps/web/components/chat/index.tsx @@ -69,12 +69,12 @@ function ChatEmptyStatePlaceholder({ id="chat-empty-state" className="flex flex-col items-center justify-center h-full" > -
- - +
+ +
-

Ask me anything about your memories...

+

Ask me anything about your memories…

{isLoadingThreads ? (
- +
) : threads.length === 0 ? (
@@ -659,7 +659,7 @@ export function ChatSidebar({ e.stopPropagation() deleteThread(thread.id) }} - className="h-7 w-7 bg-red-500 text-white hover:bg-red-600" + className="size-7 bg-red-500 text-white hover:bg-red-600" > @@ -671,7 +671,7 @@ export function ChatSidebar({ e.stopPropagation() setConfirmingDeleteId(null) }} - className="h-7 w-7" + className="size-7" > @@ -685,7 +685,7 @@ export function ChatSidebar({ e.stopPropagation() setConfirmingDeleteId(thread.id) }} - className="ml-2 h-7 w-7" + className="ml-2 size-7" > @@ -883,7 +883,7 @@ export function ChatSidebar({ ))} {(status === "submitted" || status === "streaming") && (
- +
)}
@@ -965,10 +965,10 @@ export function ChatSidebar({ isResponding={status === "submitted" || status === "streaming"} activeStatus={ status === "submitted" - ? "Thinking..." + ? "Thinking…" : status === "streaming" - ? "Structuring response..." - : "Waiting for input..." + ? "Structuring response…" + : "Waiting for input…" } onExpandedChange={setIsInputExpanded} chainOfThoughtComponent={ diff --git a/apps/web/components/chat/input/actions.tsx b/apps/web/components/chat/input/actions.tsx index 44f8132f..f8122f8e 100644 --- a/apps/web/components/chat/input/actions.tsx +++ b/apps/web/components/chat/input/actions.tsx @@ -29,7 +29,7 @@ export function SendButton({ > Send Icon diff --git a/apps/web/components/chat/message/agent-message.tsx b/apps/web/components/chat/message/agent-message.tsx index 28f34919..63fb84f5 100644 --- a/apps/web/components/chat/message/agent-message.tsx +++ b/apps/web/components/chat/message/agent-message.tsx @@ -165,11 +165,9 @@ function BashToolDisplay({ part }: { part: ToolCallDisplayPart }) { : "text-white/70", )} > - {cmd ?? "..."} + {cmd ?? "…"} - {isLoading && ( - running... - )} + {isLoading && running…} {isDone && !hasOutput && ( done )} @@ -261,7 +259,7 @@ function ToolCallDisplay({ part }: { part: ToolCallDisplayPart }) { > {label} - {isLoading && running...} + {isLoading && running…} {isDone && done} {isError && error} {expanded ? ( diff --git a/apps/web/components/connect-ai-modal.tsx b/apps/web/components/connect-ai-modal.tsx index 70487cd6..b0691258 100644 --- a/apps/web/components/connect-ai-modal.tsx +++ b/apps/web/components/connect-ai-modal.tsx @@ -351,7 +351,7 @@ export function ConnectAIModal({ {/* Step 1: Client Selection */}
-
+
1

Select Your AI Client

@@ -373,7 +373,7 @@ export function ConnectAIModal({ type="button" >
-
+
{clientName} { analytics.mcpInstallCmdCopied() - toast.success("Opening Cursor installer...") + toast.success("Opening Cursor installer…") }} > {createMcpApiKeyMutation.isPending ? (
- +
) : ( <> @@ -684,7 +684,7 @@ export function ConnectAIModal({ @@ -919,8 +919,8 @@ export function ConnectAIModal({ > {migrateMCPMutation.isPending ? ( <> - - Migrating... + + Migrating… ) : ( "Migrate" diff --git a/apps/web/components/dashboard-view.tsx b/apps/web/components/dashboard-view.tsx index a29faf88..8649899b 100644 --- a/apps/web/components/dashboard-view.tsx +++ b/apps/web/components/dashboard-view.tsx @@ -454,7 +454,7 @@ function MemoryOfDayCard({ data }: { data: MemoryOfDay }) { type="button" onClick={() => router.push(href)} className={cn( - "group w-full h-full text-left bg-surface-card/60 backdrop-blur-md rounded-[18px] p-3 flex flex-col justify-between transition-colors cursor-pointer shadow-[0_12px_40px_rgba(0,0,0,0.22)]", + "group size-full text-left bg-surface-card/60 backdrop-blur-md rounded-[18px] p-3 flex flex-col justify-between transition-colors cursor-pointer shadow-[0_12px_40px_rgba(0,0,0,0.22)]", dmSansClassName(), )} > @@ -873,7 +873,7 @@ export function DashboardView({ onClick={() => onOpenDocument(doc)} className="group flex w-full items-center gap-3 rounded-lg px-2.5 py-2 text-left transition-colors hover:bg-surface-hover" > -
+
{isLink ? ( ) : ( diff --git a/apps/web/components/document-cards/file-preview.tsx b/apps/web/components/document-cards/file-preview.tsx index 9965d9be..2f0015fa 100644 --- a/apps/web/components/document-cards/file-preview.tsx +++ b/apps/web/components/document-cards/file-preview.tsx @@ -95,7 +95,7 @@ export const FilePreview = memo(function FilePreview({ key={retryKey} src={document.url} alt={document.title || "Image preview"} - className="relative max-w-full max-h-full w-auto h-auto object-contain z-10" + className="relative max-w-full max-h-full size-auto object-contain z-10" onError={handleImageError} loading="lazy" /> @@ -106,7 +106,7 @@ export const FilePreview = memo(function FilePreview({

{label} diff --git a/apps/web/components/document-cards/note-preview.tsx b/apps/web/components/document-cards/note-preview.tsx index ef16948b..34bcadba 100644 --- a/apps/web/components/document-cards/note-preview.tsx +++ b/apps/web/components/document-cards/note-preview.tsx @@ -13,7 +13,7 @@ export function NotePreview({ document }: { document: DocumentWithMemories }) { return (

- +

Note

diff --git a/apps/web/components/document-cards/notion-preview.tsx b/apps/web/components/document-cards/notion-preview.tsx index f514e4aa..aebc4edc 100644 --- a/apps/web/components/document-cards/notion-preview.tsx +++ b/apps/web/components/document-cards/notion-preview.tsx @@ -73,9 +73,9 @@ export function NotionPreview({
{/* Decorative dots mimicking Notion's block handles */}
-
-
-
+
+
+
@@ -118,7 +118,7 @@ export function NotionPreview({
@@ -159,7 +159,7 @@ export function NotionPreview({ if (block.type === "bullet") { return (
-
+

{block.text}

diff --git a/apps/web/components/document-cards/tweet-preview.tsx b/apps/web/components/document-cards/tweet-preview.tsx index ba3b8d0f..807ba5b9 100644 --- a/apps/web/components/document-cards/tweet-preview.tsx +++ b/apps/web/components/document-cards/tweet-preview.tsx @@ -105,7 +105,7 @@ function CustomTweetMedia({ Tweet media {isVideo && (
diff --git a/apps/web/components/document-cards/website-preview.tsx b/apps/web/components/document-cards/website-preview.tsx index 067004fc..509b1e64 100644 --- a/apps/web/components/document-cards/website-preview.tsx +++ b/apps/web/components/document-cards/website-preview.tsx @@ -32,7 +32,7 @@ export const WebsitePreview = memo(function WebsitePreview({ {document.title setImageError(true)} loading="lazy" /> diff --git a/apps/web/components/document-cards/youtube-preview.tsx b/apps/web/components/document-cards/youtube-preview.tsx index 008ce35d..14089661 100644 --- a/apps/web/components/document-cards/youtube-preview.tsx +++ b/apps/web/components/document-cards/youtube-preview.tsx @@ -42,7 +42,7 @@ export const YoutubePreview = memo(function YoutubePreview({