From b48be80d5d1837ca2848ca7ce227b15bc201ec7e Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 11 Apr 2026 06:40:37 -0500 Subject: [PATCH] fix: address PR 3355 review regressions --- ui/src/components/IssueDocumentsSection.tsx | 26 ++++++++++++--------- ui/src/components/MarkdownBody.test.tsx | 16 +++++++++++++ ui/src/components/MarkdownBody.tsx | 25 +++++++++++++++----- ui/src/pages/CompanyExport.tsx | 4 ++-- ui/src/pages/CompanyImport.tsx | 4 ++-- ui/src/pages/CompanySkills.tsx | 2 +- 6 files changed, 55 insertions(+), 22 deletions(-) diff --git a/ui/src/components/IssueDocumentsSection.tsx b/ui/src/components/IssueDocumentsSection.tsx index d2dade97..0acbca9a 100644 --- a/ui/src/components/IssueDocumentsSection.tsx +++ b/ui/src/components/IssueDocumentsSection.tsx @@ -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({ Revision history - {revisionMenuOpenKey === doc.key && isFetchingDocumentRevisions && revisionHistory.length === 0 ? ( + {revisionMenuOpenKey === doc.key && isFetchingDocumentRevisions && rawRevisionHistory.length === 0 ? ( Loading revisions... ) : revisionHistory.length > 0 ? ( - + {revisionHistory.map((revision) => { - const isCurrentRevision = revision.id === doc.latestRevisionId; + const isCurrentRevision = revision.id === currentRevision.id; return ( { expect(html).toContain("PAP-1271"); expect(html).toContain("text-green-600"); }); + + it("can opt out of issue reference linkification for offline previews", () => { + const html = renderToStaticMarkup( + + + + {"Depends on PAP-1271 and [manual link](PAP-1271)."} + + + , + ); + + expect(html).not.toContain('href="/issues/PAP-1271"'); + expect(html).toContain("Depends on PAP-1271"); + expect(html).toContain('href="PAP-1271"'); + }); }); diff --git a/ui/src/components/MarkdownBody.tsx b/ui/src/components/MarkdownBody.tsx index c0430a3a..c9fdf859 100644 --- a/ui/src/components/MarkdownBody.tsx +++ b/ui/src/components/MarkdownBody.tsx @@ -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 = [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
{preChildren}
; }, a: ({ href, children: linkChildren }) => { - const issueRef = parseIssueReferenceFromHref(href); + const issueRef = linkIssueReferences ? parseIssueReferenceFromHref(href) : null; if (issueRef) { return ( diff --git a/ui/src/pages/CompanyExport.tsx b/ui/src/pages/CompanyExport.tsx index 61221398..8c4f46c9 100644 --- a/ui/src/pages/CompanyExport.tsx +++ b/ui/src/pages/CompanyExport.tsx @@ -532,10 +532,10 @@ function ExportPreviewPane({ {parsed ? ( <> - {parsed.body.trim() && {parsed.body}} + {parsed.body.trim() && {parsed.body}} ) : isMarkdown ? ( - {textContent ?? ""} + {textContent ?? ""} ) : imageSrc ? (
{selectedFile} diff --git a/ui/src/pages/CompanyImport.tsx b/ui/src/pages/CompanyImport.tsx index c772c7b4..b2c6676c 100644 --- a/ui/src/pages/CompanyImport.tsx +++ b/ui/src/pages/CompanyImport.tsx @@ -241,10 +241,10 @@ function ImportPreviewPane({ {parsed ? ( <> - {parsed.body.trim() && {parsed.body}} + {parsed.body.trim() && {parsed.body}} ) : isMarkdown ? ( - {textContent ?? ""} + {textContent ?? ""} ) : imageSrc ? (
{selectedFile} diff --git a/ui/src/pages/CompanySkills.tsx b/ui/src/pages/CompanySkills.tsx index 2f170dcd..5a953375 100644 --- a/ui/src/pages/CompanySkills.tsx +++ b/ui/src/pages/CompanySkills.tsx @@ -742,7 +742,7 @@ function SkillPane({ /> ) ) : file.markdown && viewMode === "preview" ? ( - {body} + {body} ) : (
             {file.content}