From be8ed579a9668d80a1479c58b5b923bf821c3c4c Mon Sep 17 00:00:00 2001 From: agent_coder Date: Sun, 12 Jul 2026 21:15:51 +0300 Subject: [PATCH] fix(comments): review fixes for the edit-card redesign (#561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - F1: restore the "Applied" badge on an applied suggested-edit card (green light Badge + aria-label, reusing the pre-redesign t("Applied") key). Renders only when suggestionAppliedAt is set (a reply-kept, resolved suggestion — a childless applied one is hard-deleted per #329), never for a pending edit. - F2: add the four new t() keys introduced by the redesign to both locales — "Critical", "Major", "{{count}} edits", "{{count}} major" (en value==key; ru translated, ICU plural for the count keys, placeholders intact). - F3: test RunHeader — edit count, major = major+critical only (not minor/unknown, non-vacuous), provenance line, no "Accept all" control. Uses an isolated initialised i18n instance so the interpolated count keys render. - F4: restore the card aria-label (t("Jump to comment selection")) on the clickable edit card for screen readers. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../public/locales/en-US/translation.json | 4 + .../public/locales/ru-RU/translation.json | 4 + .../components/agent-edit-card.test.tsx | 99 ++++++++++++++++++- .../comment/components/agent-edit-card.tsx | 18 ++++ 4 files changed, 124 insertions(+), 1 deletion(-) diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 0b90b2c7..fd5db6a9 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -48,6 +48,7 @@ "Create page": "Create page", "Create space": "Create space", "Create workspace": "Create workspace", + "Critical": "Critical", "Current password": "Current password", "Dark": "Dark", "Date": "Date", @@ -125,6 +126,7 @@ "Link copied": "Link copied", "Login": "Login", "Logout": "Logout", + "Major": "Major", "Manage Group": "Manage Group", "Manage members": "Manage members", "member": "member", @@ -453,6 +455,8 @@ "{{count}} Columns": "{{count}} Columns", "{{count}} command available_one": "1 command available", "{{count}} command available_other": "{{count}} commands available", + "{{count}} edits": "{{count}} edits", + "{{count}} major": "{{count}} major", "{{count}} result available_one": "1 result available", "{{count}} result available_other": "{{count}} results available", "{{count}} result found_one": "{{count}} result found", diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index 2aa13e5b..174ea79d 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -48,6 +48,7 @@ "Create page": "Создать страницу", "Create space": "Создать пространство", "Create workspace": "Создать рабочую область", + "Critical": "Критично", "Current password": "Текущий пароль", "Dark": "Темная", "Date": "Дата", @@ -127,6 +128,7 @@ "Link copied": "Ссылка скопирована", "Login": "Войти", "Logout": "Выйти", + "Major": "Существенно", "Manage Group": "Управление группой", "Manage members": "Управление участниками", "member": "участник", @@ -461,6 +463,8 @@ "{{count}} Columns": "{count, plural, one{# столбец} few{# столбца} many{# столбцов} other{# столбца}}", "{{count}} command available_one": "Доступна 1 команда", "{{count}} command available_other": "Доступно {{count}} команд", + "{{count}} edits": "{count, plural, one{# правка} few{# правки} many{# правок} other{# правки}}", + "{{count}} major": "{count, plural, one{# существенная} few{# существенных} many{# существенных} other{# существенных}}", "{{count}} result available_one": "Доступен 1 результат", "{{count}} result available_other": "Доступно {{count}} результатов", "{{count}} result found_one": "Найден {{count}} результат", diff --git a/apps/client/src/features/comment/components/agent-edit-card.test.tsx b/apps/client/src/features/comment/components/agent-edit-card.test.tsx index f1722670..46849f35 100644 --- a/apps/client/src/features/comment/components/agent-edit-card.test.tsx +++ b/apps/client/src/features/comment/components/agent-edit-card.test.tsx @@ -1,6 +1,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; import { MantineProvider } from "@mantine/core"; +import { createInstance } from "i18next"; +import { initReactI18next, I18nextProvider } from "react-i18next"; import { IComment } from "@/features/comment/types/comment.types"; // matchMedia (read by MantineProvider) is stubbed globally in vitest.setup.ts. @@ -37,7 +39,7 @@ vi.mock("@/features/space/queries/space-query.ts", () => ({ useGetSpaceBySlugQuery: () => ({ data: undefined }), })); -import AgentEditCard from "./agent-edit-card"; +import AgentEditCard, { RunHeader } from "./agent-edit-card"; const body = (text: string) => JSON.stringify({ @@ -109,6 +111,18 @@ describe("AgentEditCard — suggested edit diff + Apply (#315)", () => { expect(screen.queryByRole("button", { name: "Apply" })).toBeNull(); }); + it("shows the Applied badge for an applied suggestion, not a pending one (F1)", () => { + // Pending: no Applied badge. + const { unmount } = renderCard(edit(), true); + expect(screen.queryByText("Applied")).toBeNull(); + unmount(); + // Applied (kept alive by replies -> resolved, #329): the badge is restored. + renderCard(edit({ suggestionAppliedAt: new Date() }), true); + expect(screen.getByText("Applied")).toBeDefined(); + expect(screen.queryByRole("button", { name: "Apply" })).toBeNull(); + expect(screen.queryByRole("button", { name: "Dismiss" })).toBeNull(); + }); + it("calls the apply mutation with {commentId, pageId} on click", () => { applyMutateAsync.mockClear(); renderCard(edit(), true); @@ -185,3 +199,86 @@ describe("AgentEditCard — provenance", () => { beforeEach(() => localStorage.clear()); afterEach(() => localStorage.clear()); }); + +// The RunHeader's "N edits · M major" is built with interpolated t() keys; the +// default (uninitialized) react-i18next t returns the key verbatim WITHOUT +// interpolating, so a numeric assertion needs a real, initialised i18n instance. +// This isolated instance carries just the two count keys with escapeValue off so +// "{{count}}" is substituted and the rendered numbers are assertable. +const headerI18n = createInstance(); +headerI18n.use(initReactI18next).init({ + lng: "en", + fallbackLng: "en", + resources: { + en: { + translation: { + "{{count}} edits": "{{count}} edits", + "{{count}} major": "{{count}} major", + }, + }, + }, + interpolation: { escapeValue: false }, +}); + +const runComment = (id: string, tag: string): IComment => + edit({ + id, + content: body(`${tag} some rationale`), + }); + +function renderRunHeader(comments: IComment[]) { + return render( + + + + + , + ); +} + +describe("RunHeader — agent-run series header (F3)", () => { + // 5 edits: 2 critical + 1 major + 1 minor + 1 unknown(verdict) => 3 "major". + const series = () => [ + runComment("e1", "[Критично]"), + runComment("e2", "[Критично]"), + runComment("e3", "[Существенно]"), + runComment("e4", "[Незначительно]"), + runComment("e5", "[Неверно]"), + ]; + + it("shows the total edit count", () => { + renderRunHeader(series()); + expect(screen.getByText(/5 edits/)).toBeDefined(); + }); + + it("counts ONLY major+critical as major (not minor/unknown)", () => { + renderRunHeader(series()); + // 2 critical + 1 major = 3; minor and the [Неверно] verdict are excluded. + expect(screen.getByText(/3 major/)).toBeDefined(); + // Non-vacuous: the wrong tally (counting all 5, or 4) must NOT appear. + expect(screen.queryByText(/5 major/)).toBeNull(); + expect(screen.queryByText(/4 major/)).toBeNull(); + }); + + it("omits the major segment entirely when there are no significant edits", () => { + renderRunHeader([ + runComment("e1", "[Незначительно]"), + runComment("e2", "[Неверно]"), + ]); + expect(screen.getByText(/2 edits/)).toBeDefined(); + expect(screen.queryByText(/major/)).toBeNull(); + }); + + it("renders the provenance line (agent role name)", () => { + renderRunHeader(series()); + expect(screen.getAllByText("Corrector").length).toBeGreaterThan(0); + }); + + it("renders NO 'Accept all' control (rejected by product)", () => { + renderRunHeader(series()); + expect(screen.queryByText(/accept all/i)).toBeNull(); + expect( + screen.queryByRole("button", { name: /accept all/i }), + ).toBeNull(); + }); +}); diff --git a/apps/client/src/features/comment/components/agent-edit-card.tsx b/apps/client/src/features/comment/components/agent-edit-card.tsx index 8aecd0b0..dc008ec8 100644 --- a/apps/client/src/features/comment/components/agent-edit-card.tsx +++ b/apps/client/src/features/comment/components/agent-edit-card.tsx @@ -1,5 +1,6 @@ import React, { useMemo } from "react"; import { + Badge, Box, Button, Group, @@ -222,6 +223,7 @@ function AgentEditCard({ const isOwnerOrAdmin = currentUser?.user?.id === comment.creatorId || userSpaceRole === "admin"; + const isApplied = comment.suggestionAppliedAt != null; const showApply = canShowApply(comment, canEdit); const showDismiss = canShowDismiss(comment, canComment, isOwnerOrAdmin); const pending = @@ -263,6 +265,7 @@ function AgentEditCard({ p="10px 12px" role="button" tabIndex={0} + aria-label={t("Jump to comment selection")} onClick={() => scrollToCommentMark(comment.id)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { @@ -313,6 +316,21 @@ function AgentEditCard({ )} + {/* Applied state (#315): a suggestion that was applied but kept alive by + its replies (so #329 resolved instead of hard-deleting it) still + shows it was applied — the badge the pre-redesign card carried. A + childless applied suggestion is gone from the list entirely, so this + only renders in the Resolved tab. */} + {isApplied && ( + + {t("Applied")} + + )} {showDismiss && (