fix: address PR 3355 review regressions

This commit is contained in:
Dotta
2026-04-11 06:40:37 -05:00
parent dc94e3d1df
commit b48be80d5d
6 changed files with 55 additions and 22 deletions
+15 -11
View File
@@ -12,6 +12,7 @@ import { useLocation } from "@/lib/router";
import { ApiError } from "../api/client";
import { issuesApi } from "../api/issues";
import { useAutosaveIndicator } from "../hooks/useAutosaveIndicator";
import { deriveDocumentRevisionState } from "../lib/document-revisions";
import { queryKeys } from "../lib/queryKeys";
import { cn, relativeTime } from "../lib/utils";
import { MarkdownBody } from "./MarkdownBody";
@@ -536,10 +537,10 @@ export function IssueDocumentsSection({
}, []);
const previewRevision = useCallback((doc: IssueDocument, revisionId: string) => {
const revisions = getDocumentRevisions(doc.key);
const selectedRevision = revisions.find((revision) => revision.id === revisionId);
const revisionState = deriveDocumentRevisionState(doc, getDocumentRevisions(doc.key));
const selectedRevision = revisionState.revisions.find((revision) => revision.id === revisionId);
if (!selectedRevision) return;
if (selectedRevision.id === doc.latestRevisionId) {
if (selectedRevision.id === revisionState.currentRevision.id) {
returnToLatestRevision(doc.key);
return;
}
@@ -787,7 +788,10 @@ export function IssueDocumentsSection({
const activeDraft = draft?.key === doc.key && !draft.isNew ? draft : null;
const activeConflict = documentConflict?.key === doc.key ? documentConflict : null;
const isFolded = foldedDocumentKeys.includes(doc.key);
const revisionHistory = getDocumentRevisions(doc.key);
const rawRevisionHistory = getDocumentRevisions(doc.key);
const revisionState = deriveDocumentRevisionState(doc, rawRevisionHistory);
const revisionHistory = revisionState.revisions;
const currentRevision = revisionState.currentRevision;
const selectedRevisionId = selectedRevisionIds[doc.key] ?? null;
const selectedHistoricalRevision = selectedRevisionId
? revisionHistory.find((revision) => revision.id === selectedRevisionId) ?? null
@@ -795,10 +799,10 @@ export function IssueDocumentsSection({
const isHistoricalPreview = Boolean(selectedHistoricalRevision);
const displayedTitle = selectedHistoricalRevision
? selectedHistoricalRevision.title ?? ""
: activeDraft?.title ?? doc.title ?? "";
const displayedBody = selectedHistoricalRevision?.body ?? activeDraft?.body ?? doc.body;
const displayedRevisionNumber = selectedHistoricalRevision?.revisionNumber ?? doc.latestRevisionNumber;
const displayedUpdatedAt = selectedHistoricalRevision?.createdAt ?? doc.updatedAt;
: activeDraft?.title ?? currentRevision.title ?? "";
const displayedBody = selectedHistoricalRevision?.body ?? activeDraft?.body ?? currentRevision.body;
const displayedRevisionNumber = selectedHistoricalRevision?.revisionNumber ?? currentRevision.revisionNumber;
const displayedUpdatedAt = selectedHistoricalRevision?.createdAt ?? currentRevision.createdAt;
const showTitle = !isPlanKey(doc.key) && !!displayedTitle.trim() && !titlesMatchKey(displayedTitle, doc.key);
const canVoteOnDocument = Boolean(doc.latestRevisionId && doc.updatedByAgentId && !doc.updatedByUserId && onVote);
@@ -845,12 +849,12 @@ export function IssueDocumentsSection({
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-72">
<DropdownMenuLabel>Revision history</DropdownMenuLabel>
{revisionMenuOpenKey === doc.key && isFetchingDocumentRevisions && revisionHistory.length === 0 ? (
{revisionMenuOpenKey === doc.key && isFetchingDocumentRevisions && rawRevisionHistory.length === 0 ? (
<DropdownMenuItem disabled>Loading revisions...</DropdownMenuItem>
) : revisionHistory.length > 0 ? (
<DropdownMenuRadioGroup value={selectedRevisionId ?? doc.latestRevisionId ?? ""}>
<DropdownMenuRadioGroup value={selectedRevisionId ?? currentRevision.id ?? ""}>
{revisionHistory.map((revision) => {
const isCurrentRevision = revision.id === doc.latestRevisionId;
const isCurrentRevision = revision.id === currentRevision.id;
return (
<DropdownMenuRadioItem
key={revision.id}
+16
View File
@@ -155,4 +155,20 @@ describe("MarkdownBody", () => {
expect(html).toContain("<code>PAP-1271</code>");
expect(html).toContain("text-green-600");
});
it("can opt out of issue reference linkification for offline previews", () => {
const html = renderToStaticMarkup(
<QueryClientProvider client={new QueryClient()}>
<ThemeProvider>
<MarkdownBody linkIssueReferences={false}>
{"Depends on PAP-1271 and [manual link](PAP-1271)."}
</MarkdownBody>
</ThemeProvider>
</QueryClientProvider>,
);
expect(html).not.toContain('href="/issues/PAP-1271"');
expect(html).toContain("Depends on PAP-1271");
expect(html).toContain('href="PAP-1271"');
});
});
+19 -6
View File
@@ -1,6 +1,6 @@
import { isValidElement, useEffect, useId, useState, type ReactNode } from "react";
import { useQuery } from "@tanstack/react-query";
import Markdown, { type Components } from "react-markdown";
import Markdown, { type Components, type Options } from "react-markdown";
import remarkGfm from "remark-gfm";
import { cn } from "../lib/utils";
import { useTheme } from "../context/ThemeContext";
@@ -17,6 +17,7 @@ interface MarkdownBodyProps {
className?: string;
style?: React.CSSProperties;
softBreaks?: boolean;
linkIssueReferences?: boolean;
/** Optional resolver for relative image paths (e.g. within export packages) */
resolveImageSrc?: (src: string) => string | null;
/** Called when a user clicks an inline image */
@@ -125,11 +126,23 @@ function MermaidDiagramBlock({ source, darkMode }: { source: string; darkMode: b
);
}
export function MarkdownBody({ children, className, style, softBreaks = true, resolveImageSrc, onImageClick }: MarkdownBodyProps) {
export function MarkdownBody({
children,
className,
style,
softBreaks = true,
linkIssueReferences = true,
resolveImageSrc,
onImageClick,
}: MarkdownBodyProps) {
const { theme } = useTheme();
const remarkPlugins = softBreaks
? [remarkGfm, remarkLinkIssueReferences, remarkSoftBreaks]
: [remarkGfm, remarkLinkIssueReferences];
const remarkPlugins: NonNullable<Options["remarkPlugins"]> = [remarkGfm];
if (linkIssueReferences) {
remarkPlugins.push(remarkLinkIssueReferences);
}
if (softBreaks) {
remarkPlugins.push(remarkSoftBreaks);
}
const components: Components = {
pre: ({ node: _node, children: preChildren, ...preProps }) => {
const mermaidSource = extractMermaidSource(preChildren);
@@ -139,7 +152,7 @@ export function MarkdownBody({ children, className, style, softBreaks = true, re
return <pre {...preProps}>{preChildren}</pre>;
},
a: ({ href, children: linkChildren }) => {
const issueRef = parseIssueReferenceFromHref(href);
const issueRef = linkIssueReferences ? parseIssueReferenceFromHref(href) : null;
if (issueRef) {
return (
<MarkdownIssueLink issuePathId={issueRef.issuePathId} href={issueRef.href}>
+2 -2
View File
@@ -532,10 +532,10 @@ function ExportPreviewPane({
{parsed ? (
<>
<FrontmatterCard data={parsed.data} onSkillClick={onSkillClick} />
{parsed.body.trim() && <MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false}>{parsed.body}</MarkdownBody>}
{parsed.body.trim() && <MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false} linkIssueReferences={false}>{parsed.body}</MarkdownBody>}
</>
) : isMarkdown ? (
<MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false}>{textContent ?? ""}</MarkdownBody>
<MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false} linkIssueReferences={false}>{textContent ?? ""}</MarkdownBody>
) : imageSrc ? (
<div className="flex min-h-[520px] items-center justify-center rounded-lg border border-border bg-accent/10 p-6">
<img src={imageSrc} alt={selectedFile} className="max-h-[480px] max-w-full object-contain" />
+2 -2
View File
@@ -241,10 +241,10 @@ function ImportPreviewPane({
{parsed ? (
<>
<FrontmatterCard data={parsed.data} />
{parsed.body.trim() && <MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false}>{parsed.body}</MarkdownBody>}
{parsed.body.trim() && <MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false} linkIssueReferences={false}>{parsed.body}</MarkdownBody>}
</>
) : isMarkdown ? (
<MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false}>{textContent ?? ""}</MarkdownBody>
<MarkdownBody resolveImageSrc={resolveImageSrc} softBreaks={false} linkIssueReferences={false}>{textContent ?? ""}</MarkdownBody>
) : imageSrc ? (
<div className="flex min-h-[520px] items-center justify-center rounded-lg border border-border bg-accent/10 p-6">
<img src={imageSrc} alt={selectedFile} className="max-h-[480px] max-w-full object-contain" />
+1 -1
View File
@@ -742,7 +742,7 @@ function SkillPane({
/>
)
) : file.markdown && viewMode === "preview" ? (
<MarkdownBody softBreaks={false}>{body}</MarkdownBody>
<MarkdownBody softBreaks={false} linkIssueReferences={false}>{body}</MarkdownBody>
) : (
<pre className="overflow-x-auto whitespace-pre-wrap wrap-break-word border-0 bg-transparent p-0 font-mono text-sm text-foreground">
<code>{file.content}</code>