diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index 440d8fac5..83f00c853 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -71,6 +71,7 @@ export default defineConfig({ "**/channel-dense-second-reach.spec.ts", "**/channel-window-mock-paging.spec.ts", "**/live-broadcast-reply-timeline.spec.ts", + "**/markdown-parse-cache.spec.ts", "**/overscroll-boundary.spec.ts", "**/cold-switch-longtask.perf.ts", "**/timeline-no-shift.spec.ts", diff --git a/desktop/src/features/workspaces/useWorkspaceInit.ts b/desktop/src/features/workspaces/useWorkspaceInit.ts index d368735b6..d9df6113f 100644 --- a/desktop/src/features/workspaces/useWorkspaceInit.ts +++ b/desktop/src/features/workspaces/useWorkspaceInit.ts @@ -17,6 +17,7 @@ import { resetActiveAgentTurnsStore } from "@/features/agents/activeAgentTurnsSt import { resetAgentWorkingSignal } from "@/features/agents/agentWorkingSignal"; import { resetAgentObserverStore } from "@/features/agents/observerRelayStore"; import { resetSidebarRelayConnectionCardState } from "@/features/sidebar/ui/useSidebarRelayConnectionCard"; +import { clearMarkdownNodeCache } from "@/shared/ui/markdown/nodeCache"; import { resetVideoPlayerState } from "@/shared/ui/videoPlayerState"; import { initFirstWorkspace } from "./workspaceStorage"; @@ -40,6 +41,7 @@ function resetWorkspaceState(): void { resetRenderScopedReactionHydration(); clearSearchHitEventCache(); clearAllDrafts(); + clearMarkdownNodeCache(); } type WorkspaceInitResult = diff --git a/desktop/src/shared/ui/markdown.tsx b/desktop/src/shared/ui/markdown.tsx index 8493b7239..ee510677a 100644 --- a/desktop/src/shared/ui/markdown.tsx +++ b/desktop/src/shared/ui/markdown.tsx @@ -1,6 +1,6 @@ import * as React from "react"; import { createPortal } from "react-dom"; -import ReactMarkdown, { type Components } from "react-markdown"; +import type { Components } from "react-markdown"; import { ChevronLeft, ChevronRight, @@ -9,8 +9,6 @@ import { ZoomOut, } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; -import remarkBreaks from "remark-breaks"; -import remarkGfm from "remark-gfm"; import { toast } from "sonner"; import { useAppNavigation } from "@/app/navigation/useAppNavigation"; @@ -29,13 +27,6 @@ import { } from "@/shared/lib/linkPreview"; import { useResolvedLinkPreviews } from "@/shared/lib/useResolvedLinkPreviews"; import { rewriteRelayUrl } from "@/shared/lib/mediaUrl"; -import rehypeImageGallery from "@/shared/lib/rehypeImageGallery"; -import rehypeSearchHighlight from "@/shared/lib/rehypeSearchHighlight"; -import remarkChannelLinks from "@/shared/lib/remarkChannelLinks"; -import remarkCustomEmoji from "@/shared/lib/remarkCustomEmoji"; -import remarkMentions from "@/shared/lib/remarkMentions"; -import remarkSpoilers from "@/shared/lib/remarkSpoilers"; -import remarkMessageLinks from "@/features/messages/lib/remarkMessageLinks"; import { AttachmentGroup } from "@/shared/ui/attachment"; import { ConfigNudgeCard } from "@/shared/ui/config-nudge-attachment"; import { LinkPreviewAttachment } from "@/shared/ui/link-preview-attachment"; @@ -75,84 +66,28 @@ import { MarkdownInput } from "./markdown/MarkdownInput"; import { MarkdownTable } from "./markdown/MarkdownTable"; import { MaskedLinkTooltip } from "./markdown/MaskedLinkTooltip"; import { MessageLinkPill } from "./markdown/MessageLinkPill"; +import { renderCachedMarkdown } from "./markdown/nodeCache"; +import { + MarkdownRuntimeContext, + useMarkdownRuntime, +} from "./markdown/runtimeContext"; import { resolveFileCard } from "./markdownFileCard"; -import type { - ImetaEntry, - MarkdownProps, - MarkdownRuntime, -} from "./markdown/types"; +import type { MarkdownProps, MarkdownRuntime } from "./markdown/types"; import { SpoilerInline } from "./markdown/SpoilerInline"; import { - aspectRatioFromDim, dimensionsFromDim, getDecodedImageDimensions, imageReserveStyle, isInsideHiddenSpoiler, getReactNodeText, - messageLinkUrlTransform, rememberDecodedImageDimensions, useFrozenImageReserve, useStableArray, } from "./markdown/utils"; -import { VideoPlayer, type VideoReviewContext } from "./VideoPlayer"; - -/** - * Video review context flows through React context instead of - * `createMarkdownComponents` arguments. The component map must keep a stable - * identity across re-renders: a new map means new element types, which makes - * React unmount and remount every rendered node — including `