diff --git a/web/src/components/MemoCommentSection.tsx b/web/src/components/MemoCommentSection.tsx index 3904fbb0..a1e7e936 100644 --- a/web/src/components/MemoCommentSection.tsx +++ b/web/src/components/MemoCommentSection.tsx @@ -102,7 +102,7 @@ const MemoCommentSection = ({ memo, comments, parentPage, hasMoreComments, isFet {showEditor && EditorComponent && (
)} {comments.map((comment) => ( -
+
))} diff --git a/web/src/components/MemoContent/TaskListItem.tsx b/web/src/components/MemoContent/TaskListItem.tsx index 0f89f7d2..22254290 100644 --- a/web/src/components/MemoContent/TaskListItem.tsx +++ b/web/src/components/MemoContent/TaskListItem.tsx @@ -65,6 +65,10 @@ export const TaskListItem: React.FC = ({ checked, node: _node }); }; - // Override the disabled prop from remark-gfm (which defaults to true) - return ; + // Override the disabled prop from remark-gfm (which defaults to true). + // `checked` is undefined for unchecked tasks; Base UI locks controlled/uncontrolled + // mode at mount, so coerce to boolean to keep the checkbox always controlled. + return ( + + ); }; diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx index 9ef79fd8..c80ca910 100644 --- a/web/src/components/PagedMemoList/PagedMemoList.tsx +++ b/web/src/components/PagedMemoList/PagedMemoList.tsx @@ -20,8 +20,9 @@ import Placeholder from "../Placeholder"; import { estimateMemoCardHeight } from "./memoCardHeight"; // Memo identity for React keys and grid planning. The pages use it for their renderer keys too, -// so flow-list and grid identity can never drift apart. -export const getMemoKey = (memo: Memo) => `${memo.name}-${memo.updateTime}`; +// so flow-list and grid identity can never drift apart. Deliberately name-only: content updates +// reconcile in place (updateTime is a protobuf Timestamp object, not usable in a template string). +export const getMemoKey = (memo: Memo) => memo.name; // Columns never stretch past this, so 2 columns on a wide monitor stay readable and the // grid centers in the leftover space instead of filling it. diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx index ea153243..05758e47 100644 --- a/web/src/pages/MemoDetail.tsx +++ b/web/src/pages/MemoDetail.tsx @@ -135,7 +135,7 @@ const MemoDetail = () => {
)} vi.fn()); + +vi.mock("@/hooks/useMemoQueries", () => ({ + useUpdateMemo: () => ({ mutate: mockUpdateMemo }), +})); + +vi.mock("@/components/MemoView/MemoViewContext", () => ({ + useMemoViewContext: () => ({ + memo: { + name: "memos/1", + content: "- [ ] task one", + relations: [], + attachments: [], + reactions: [], + }, + }), + useMemoViewDerived: () => ({ + readonly: false, + }), +})); + +describe(" check-all regression (#6143)", () => { + it("reflects checked=true arriving after mounting unchecked (Check all tasks)", () => { + // remark-gfm renders `- [ ]` with `checked` undefined. + const { rerender } = render(); + + const checkbox = screen.getByRole("checkbox"); + expect(checkbox).toHaveAttribute("aria-checked", "false"); + + // "Check all tasks" updates memo content; the markdown re-renders with checked=true. + rerender(); + expect(checkbox).toHaveAttribute("aria-checked", "true"); + }); + + it("reflects checked reverting to undefined (Uncheck all tasks)", () => { + const { rerender } = render(); + + const checkbox = screen.getByRole("checkbox"); + expect(checkbox).toHaveAttribute("aria-checked", "true"); + + // remark-gfm passes undefined (not false) for `- [ ]` items. + rerender(); + expect(checkbox).toHaveAttribute("aria-checked", "false"); + }); +});