fix(ui): harden issue comment editor sync

This commit is contained in:
dotta
2026-04-09 06:12:43 -05:00
parent 996c7eb727
commit 327eadb45c
4 changed files with 59 additions and 2 deletions
+14 -1
View File
@@ -364,6 +364,19 @@ export const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>
return map;
}, [mentions]);
const setEditorRef = useCallback((instance: MDXEditorMethods | null) => {
ref.current = instance;
if (!instance) {
return;
}
if (valueRef.current !== latestValueRef.current) {
// Re-apply the latest controlled value once MDXEditor exposes its imperative API.
echoIgnoreMarkdownRef.current = valueRef.current;
instance.setMarkdown(valueRef.current);
latestValueRef.current = valueRef.current;
}
}, []);
const filteredMentions = useMemo<AutocompleteOption[]>(() => {
if (!mentionState) return [];
const q = mentionState.query.trim().toLowerCase();
@@ -798,7 +811,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>
onPasteCapture={handlePasteCapture}
>
<MDXEditor
ref={ref}
ref={setEditorRef}
markdown={value}
placeholder={placeholder}
onChange={(next) => {
@@ -6,6 +6,7 @@ import {
applyOptimisticIssueCommentUpdate,
createOptimisticIssueComment,
flattenIssueCommentPages,
getNextIssueCommentPageParam,
isQueuedIssueComment,
matchesIssueRef,
mergeIssueComments,
@@ -171,6 +172,40 @@ describe("optimistic issue comments", () => {
expect(flattened.map((comment) => comment.id)).toEqual(["comment-1", "comment-2", "comment-3"]);
});
it("returns no next page param when the last page is missing", () => {
expect(getNextIssueCommentPageParam(undefined, 50)).toBeUndefined();
});
it("returns the oldest id when the last page is full", () => {
expect(
getNextIssueCommentPageParam(
[
{
id: "comment-2",
companyId: "company-1",
issueId: "issue-1",
authorAgentId: null,
authorUserId: "board-1",
body: "Second",
createdAt: new Date("2026-03-28T14:00:02.000Z"),
updatedAt: new Date("2026-03-28T14:00:02.000Z"),
},
{
id: "comment-1",
companyId: "company-1",
issueId: "issue-1",
authorAgentId: null,
authorUserId: "board-1",
body: "First",
createdAt: new Date("2026-03-28T14:00:01.000Z"),
updatedAt: new Date("2026-03-28T14:00:01.000Z"),
},
],
2,
),
).toBe("comment-1");
});
it("upserts paged comments without dropping older pages", () => {
const nextPages = upsertIssueCommentInPages(
[
+8
View File
@@ -102,6 +102,14 @@ export function flattenIssueCommentPages(
return sortIssueComments((pages ?? []).flatMap((page) => page));
}
export function getNextIssueCommentPageParam(
lastPage: ReadonlyArray<IssueComment> | undefined,
pageSize: number,
): string | undefined {
if (!lastPage || lastPage.length < pageSize) return undefined;
return lastPage[lastPage.length - 1]?.id;
}
export function upsertIssueComment(
comments: IssueComment[] | undefined,
nextComment: IssueComment,
+2 -1
View File
@@ -36,6 +36,7 @@ import {
applyOptimisticIssueCommentUpdate,
createOptimisticIssueComment,
flattenIssueCommentPages,
getNextIssueCommentPageParam,
isQueuedIssueComment,
matchesIssueRef,
mergeIssueComments,
@@ -416,7 +417,7 @@ export function IssueDetail() {
enabled: !!issueId,
initialPageParam: null as string | null,
getNextPageParam: (lastPage) =>
lastPage.length === ISSUE_COMMENT_PAGE_SIZE ? lastPage[lastPage.length - 1]?.id : undefined,
getNextIssueCommentPageParam(lastPage, ISSUE_COMMENT_PAGE_SIZE),
placeholderData: keepPreviousData,
});
const comments = useMemo(