From a5ff55c99f6edb594b245cb50b5232ddce7ed24f Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommu Date: Fri, 5 Jun 2026 17:33:08 -0700 Subject: [PATCH] fix(web): render full document content in viewer The memories list endpoint caps content at 2000 chars; the document modal rendered straight from that list cache, so long Claude Code sessions (and any doc over the cap) were truncated mid-transcript. Fetch the full document by id when the modal opens and use it for parsing, the editor, and content rendering. Also closes a latent data-loss path where the editor initialized from truncated content, so a save could overwrite the stored document. --- apps/web/components/document-modal/index.tsx | 20 ++++++++++---- apps/web/hooks/use-full-document.ts | 29 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 apps/web/hooks/use-full-document.ts diff --git a/apps/web/components/document-modal/index.tsx b/apps/web/components/document-modal/index.tsx index b5a5e0f0..77d80826 100644 --- a/apps/web/components/document-modal/index.tsx +++ b/apps/web/components/document-modal/index.tsx @@ -27,6 +27,7 @@ import type { UseMutationResult } from "@tanstack/react-query" import { toast } from "sonner" import { useIsMobile } from "@hooks/use-mobile" import { parsePluginDocument } from "@/lib/plugin-document" +import { useFullDocumentContent } from "@/hooks/use-full-document" type DocumentsResponse = z.infer type DocumentWithMemories = DocumentsResponse["documents"][0] @@ -198,16 +199,25 @@ export function DocumentModal({ const isMobile = useIsMobile() const { updateMutation, deleteMutation } = useDocumentMutations({ onClose }) + const fullContent = useFullDocumentContent(_document?.id, isOpen) + const effectiveDocument = useMemo( + () => + _document && fullContent !== null + ? { ..._document, content: fullContent } + : _document, + [_document, fullContent], + ) + const { initialEditorContent, initialEditorString } = useMemo(() => { - const content = _document?.content as string | null | undefined + const content = effectiveDocument?.content as string | null | undefined return { initialEditorContent: content ?? undefined, initialEditorString: content ?? "", } - }, [_document?.content]) + }, [effectiveDocument?.content]) const pluginDocument = useMemo( - () => parsePluginDocument(_document), - [_document], + () => parsePluginDocument(effectiveDocument), + [effectiveDocument], ) const [draftContentString, setDraftContentString] = @@ -281,7 +291,7 @@ export function DocumentModal({ className="bg-[#14161A] rounded-[14px] overflow-hidden flex min-h-0 flex-1 flex-col shadow-[inset_0_2px_4px_rgba(0,0,0,0.3),inset_0_1px_2px_rgba(0,0,0,0.1)] relative" > diff --git a/apps/web/hooks/use-full-document.ts b/apps/web/hooks/use-full-document.ts new file mode 100644 index 00000000..b954ae75 --- /dev/null +++ b/apps/web/hooks/use-full-document.ts @@ -0,0 +1,29 @@ +import { useQuery } from "@tanstack/react-query" +import { $fetch } from "@lib/api" + +// List endpoint truncates `content` to ~2000 chars; fetch the full doc by id for the viewer. +export function useFullDocumentContent( + documentId: string | null | undefined, + enabled: boolean, +): string | null { + const isReal = !!documentId && !documentId.startsWith("temp-") + + const { data } = useQuery({ + queryKey: ["document-full", documentId], + enabled: enabled && isReal, + staleTime: 5 * 60 * 1000, + queryFn: async () => { + const res = await $fetch("@get/documents/:id", { + params: { id: documentId as string }, + disableValidation: true, + }) + if (res.error) { + throw new Error(res.error?.message || "Failed to fetch document") + } + const doc = res.data as { content?: string | null } | null + return typeof doc?.content === "string" ? doc.content : null + }, + }) + + return data ?? null +}