authorgravatar for simon.doba@hotmail.deSimon Doba <simon.doba@hotmail.de> 2026-07-29 20:01:03+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-29 18:01:03+00:00
loge6987965f65914861f0dabd0db03729fe5cd2508
tree1415a5eca66eff5815f2b6dbbc0cf56924f0ddfa
parentfc28abf24b034502402cba1fedf5fa38658ee2e8
signature Signed by PGP key B5690EEEBB952194

fix(web): remember the rendered-markdown choice across threads (#4853)

Co-authored-by: Simon Doba <simon.doba@orbit.de> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

1 files changed, 25 insertions(+), 11 deletions(-)

apps/web/src/components/files/FilePreviewPanel.tsx+25-11
......@@ -22,7 +22,7 @@ import ChatMarkdown from "~/components/ChatMarkdown";
2222import { OpenInPicker } from "~/components/chat/OpenInPicker";
2323import { useClientSettings } from "~/hooks/useSettings";
2424import { useTheme } from "~/hooks/useTheme";
25import { getLocalStorageItem, setLocalStorageItem } from "~/hooks/useLocalStorage";
25import { getLocalStorageItem, setLocalStorageItem, useLocalStorage } from "~/hooks/useLocalStorage";
2626import { resolveDiffThemeName } from "~/lib/diffRendering";
2727import { cn } from "~/lib/utils";
2828import { isPreviewSupportedInRuntime } from "~/previewStateStore";
......@@ -79,6 +79,7 @@ interface FilePreviewPanelProps {
7979}
8080
8181const FILE_EXPLORER_STORAGE_KEY = "t3code.fileExplorerOpen";
82const RENDER_MARKDOWN_STORAGE_KEY = "t3code.renderMarkdown";
8283const FILE_SAVE_DEBOUNCE_MS = 500;
8384const FILE_LINK_REVEAL_ATTRIBUTE = "data-file-link-reveal";
8485const FILE_LINK_REVEAL_UNSAFE_CSS = `
......@@ -680,16 +681,27 @@ export default function FilePreviewPanel({
680681 const isImage = relativePath !== null && isWorkspaceImagePreviewPath(relativePath);
681682 const file = useProjectFileQuery(environmentId, cwd, relativePath, !isImage);
682683 const [explorerOpen, setExplorerOpen] = useState(initialExplorerOpen);
683 const [markdownView, setMarkdownView] = useState<{
684 path: string | null;
685 revealRequestId: number | null;
686 }>({ path: null, revealRequestId: null });
684 // Reading markdown rendered is a preference, not a property of one file. Keeping
685 // it on the panel meant a thread switch dropped it and forced source back.
686 const [renderMarkdownPreferred, setRenderMarkdownPreferred] = useLocalStorage(
687 RENDER_MARKDOWN_STORAGE_KEY,
688 false,
689 Schema.Boolean,
690 );
691 // Paired with the path on purpose: each file surface counts its reveals from
692 // one, so a bare id would let a dismissed reveal on one file swallow the first
693 // reveal on the next.
694 const [handledReveal, setHandledReveal] = useState<{ path: string; requestId: number } | null>(
695 null,
696 );
687697 const breadcrumbRef = useRef<HTMLDivElement>(null);
688698 const isMarkdown = relativePath ? isMarkdownPreviewFile(relativePath) : false;
699 // A reveal still wins over the preference: the line only exists in the source.
689700 const renderMarkdown =
690701 isMarkdown &&
691 markdownView.path === relativePath &&
692 (revealLine === null || markdownView.revealRequestId === revealRequestId);
702 renderMarkdownPreferred &&
703 (revealLine === null ||
704 (handledReveal?.path === relativePath && handledReveal.requestId === revealRequestId));
693705 const canOpenInBrowser =
694706 relativePath !== null && isPreviewSupportedInRuntime() && isBrowserPreviewFile(relativePath);
695707 const absolutePath = relativePath ? resolvePathLinkTarget(relativePath, cwd) : null;
......@@ -796,10 +808,12 @@ export default function FilePreviewPanel({
796808 className="shrink-0"
797809 pressed={renderMarkdown}
798810 onPressedChange={(pressed) => {
799 setMarkdownView({
800 path: pressed ? relativePath : null,
801 revealRequestId: pressed ? revealRequestId : null,
802 });
811 setRenderMarkdownPreferred(pressed);
812 setHandledReveal(
813 pressed && relativePath !== null
814 ? { path: relativePath, requestId: revealRequestId }
815 : null,
816 );
803817 }}
804818 aria-label={renderMarkdown ? "Show markdown source" : "Show rendered markdown"}
805819 variant="ghost"