12 KiB
Комментарии к документу в UI — план реализации
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: список комментариев документа на экране проверки [41] (боковая панель) и счётчик с поповером в панели «Первичные документы» [39-01].
Architecture: только фронт web/; API ядра готов (GET/POST /documents/{id}/comments, comments_count). Общий компонент CommentsList используют боковая панель экрана документа и поповер строки панели. Чистые функции (форматирование времени, строки панели) — с тестами vitest; компоненты проверяются в браузере.
Tech Stack: React 18, TS strict, antd 6, vitest, biome + ESLint + Prettier.
Spec: openspec/changes/document-comments-ui/ (proposal, design D-1…D-5, дельты document-review-ui, primary-files-ui).
Ветка: feat/180-document-comments (worktree /Users/andrey.epifancev/projects/awp-ca/.wt/doc-comments/arm-core), issue RSHB/arm-core#180, Jira AURORA-1683.
Global Constraints
- Тексты UI: «Комментарии (N)», «Комментарии к документу», «Комментариев нет», «Не удалось загрузить комментарии», «Добавить», «Комментарии к документу <имя файла>», «Комментарий отсутствует», «Закрыть».
- Время — по Москве, «dd.mm.yyyy hh:mm».
- Текст комментария — до 4000 символов; пустой после обрезки не отправляется.
- Правок и удаления комментариев нет; бэкенд и DBML не меняются.
- Стиль: табы, одинарные кавычки, ширина 140 (biome); комментарии в коде — по-русски.
Review Focus
- Клик по кнопке комментариев и внутри поповера в строке панели не должен открывать документ (всплытие к
onRow.onClick) — проверка в браузере, Task 3. - Длинный текст без пробелов и многострочный текст не ломают ширину панели/поповера (
pre-wrap+overflow-wrap: anywhere) — проверка в браузере, Task 4. - Ответ на запрос списка от прошлого открытия, пришедший после закрытия или переоткрытия, не должен перетирать текущий (счётчик запросов, как в
FileWarningsPopover) — Task 2/3. - Ошибка добавления (403/422) оставляет текст в поле — Task 2, проверка в браузере.
- Ядро без
comments_countв документе — счётчик 0 (тест в Task 1).
Task 1: Данные — тип, запрос, время, счётчик строки
Files:
- Modify:
web/src/types/api.ts(послеDocumentBrief) - Modify:
web/src/services/api/documents.ts - Modify:
web/src/utils/dates.ts, Test:web/src/utils/dates.test.ts - Modify:
web/src/widgets/FilesDrawer/documentRows.ts, Test:web/src/widgets/FilesDrawer/documentRows.test.ts
Interfaces:
-
Produces:
interface CommentOut { id: string; text: string; author: { id: string; full_name: string }; created_at: string };getComments(documentId: string): Promise<CommentOut[]>;postCommentтипизируетсяapi.post<CommentOut>;formatMoscowDateTime(iso: string | null | undefined): string | null;DocumentRow.commentsCount: number. -
Step 1: Падающие тесты
describe('formatMoscowDateTime', () => {
it('момент из API (UTC) — дата и время по Москве', () => {
expect(formatMoscowDateTime('2026-10-07T23:11:42.981145Z')).toBe('08.10.2026 02:11');
expect(formatMoscowDateTime('2024-03-12T10:05:00Z')).toBe('12.03.2024 13:05');
});
it('пусто и мусор', () => {
expect(formatMoscowDateTime(null)).toBeNull();
expect(formatMoscowDateTime('не дата')).toBeNull();
});
});
В documentRows.test.ts: buildDocumentRows([file({ documents: [doc({ comments_count: 2 })] })])[0].commentsCount === 2;
строка файла без документов — commentsCount === 0; документ без поля ({ ...doc(), comments_count: undefined } as unknown as DocumentBrief) — 0.
- Step 2:
cd web && npx vitest run src/utils/dates.test.ts src/widgets/FilesDrawer/documentRows.test.ts— FAIL (нет функции/поля). - Step 3: Реализация.
formatMoscowDateTime—Intl.DateTimeFormat('ru-RU', { timeZone: 'Europe/Moscow', day, month, year, hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }), разделитель «, » изIntlзаменить пробелом.commentsCount: d.comments_count ?? 0, у строки файла — 0.getComments—api.get<CommentOut[]>(\/documents/${id}/comments`)`. - Step 4: тот же vitest — PASS;
npx tsc --noEmit— без ошибок. - Step 5: Commit «AURORA-1683 комментарии: тип, запрос, время по Москве, счётчик строки панели».
Task 2: Экран документа — кнопка «Комментарии (N)» и боковая панель
Files:
- Create:
web/src/widgets/DocumentComments/CommentsList.tsx - Create:
web/src/widgets/DocumentComments/useDocumentComments.ts - Create:
web/src/widgets/DocumentComments/DocumentCommentsDrawer.tsx - Modify:
web/src/layouts/DocumentPage.tsx:1-136 - Delete:
web/src/widgets/DocumentReview/CommentModal.tsx
Interfaces:
-
Consumes:
CommentOut,getComments,postComment,formatMoscowDateTime(Task 1). -
Produces:
type CommentsState = { status: 'loading' } | { status: 'error' } | { status: 'done'; comments: CommentOut[] };CommentsList({ state, emptyText }: { state: CommentsState | null; emptyText: string });useDocumentComments(documentId: string | null): { state: CommentsState | null; load: () => void; reset: () => void }—loadзапрашивает список, ответы устаревших запросов игнорируются (счётчик вuseRef),resetсбрасывает вnullи отменяет ожидаемый ответ;DocumentCommentsDrawer({ documentId, open, onClose, onAdded }: { documentId: string; open: boolean; onClose: () => void; onAdded: () => void }). -
Step 1:
CommentsList:null/loading —Spin; error — «Не удалось загрузить комментарии» (цвет#cf1322); пусто —emptyText(цвет#8c8c8c); иначе на комментарий блок: ФИО жирным + времяformatMoscowDateTime(secondary, 12px) в строке, текст ниже сwhiteSpace: 'pre-wrap', overflowWrap: 'anywhere'; ключ —id. -
Step 2:
DocumentCommentsDrawer: antdDrawerсправа,title='Комментарии к документу', ширина 420; приopen—load(), при закрытии —reset(). Тело —CommentsList(emptyText='Комментариев нет') с прокруткой, футер —Input.TextArea(maxLength={4000},showCount,autoSize={{ minRows: 3, maxRows: 8 }}) и кнопка «Добавить» (disabledпри пустомtrim(),loadingво время запроса). Успех: очистить поле,message.success('Комментарий добавлен'),load(),onAdded(). Ошибка:message.error(e instanceof ApiError ? e.message : 'Не удалось добавить комментарий'), текст остаётся. -
Step 3:
DocumentPage: кнопка<Button icon={<CommentOutlined />}>Комментарии ({doc.comments_count})</Button>на месте «Добавить комментарий»;DocumentCommentsDrawerвместоCommentModal,onAdded={load}(перечитывает документ — счётчик). УдалитьCommentModal.tsxи его импорт. -
Step 4:
cd web && npx tsc --noEmit && npm run lint && npx biome check src— чисто;npm test— зелёный. -
Step 5: Commit «AURORA-1683 экран документа: список комментариев в боковой панели».
Task 3: Панель «Первичные документы» — счётчик и поповер
Files:
- Create:
web/src/widgets/DocumentComments/CommentsPopover.tsx - Modify:
web/src/widgets/FilesDrawer/FilesList.tsx:136-150(колонкаactions)
Interfaces:
-
Consumes:
useDocumentComments,CommentsList(Task 2),DocumentRow.commentsCount(Task 1). -
Produces:
CommentsPopover({ documentId, fileName, count }: { documentId: string; fileName: string; count: number }). -
Step 1:
CommentsPopover: управляемый antdPopover(trigger='click',open/onOpenChange),title={\Комментарии к документу ${fileName}`}; при открытииload(), при закрытииreset(). Содержимое — обёртка сonClick/onKeyDownstopPropagation(как вFileWarningsPopover),maxWidth: 420, maxHeight: 320, overflowY: 'auto',CommentsList(emptyText='Комментарий отсутствует'), справа внизуButton size='small'«Закрыть». Триггер —ButtonсCommentOutlinedи{count > 0 ? count : null},aria-label={`Комментарии: ${count}`},onClickсstopPropagation`. -
Step 2:
FilesList, колонкаactions:<div style={{ display: 'flex', gap: 8 }}>—CommentsPopover(только приr.documentId), затем существующая кнопка «Удалить». -
Step 3:
npx tsc --noEmit && npm run lint && npx biome check src && npm test— чисто. -
Step 4: Commit «AURORA-1683 панель документов: счётчик и поповер комментариев».
Task 4: Проверка в браузере, сверка с макетом, OpenSpec
Files:
-
Create:
openspec/changes/document-comments-ui/tasks.md -
Step 1: Стенд по
docs/dev/qa-guide.md(api + web из worktree). Окно 1600×1000. Сценарии спеки: добавить комментарий на экране документа → «Комментарии (1)», после F5 он виден; ошибка (например, остановить api) — текст в поле остаётся; многострочный и длинный текст без пробелов не ломает панель; в панели «Первичные документы» число у иконки, поповер со списком, клик по иконке и внутри поповера не открывает документ; документ без комментариев — «Комментарий отсутствует»; замена документа с причиной → причина в комментариях заменённого. -
Step 2: Скриншоты сравнить с
../docs/mockup/screens/39-01-edit-documents.pngи41-document-check.png; расхождения — списком в отчёт. -
Step 3:
tasks.mdchange (группы по задачам плана, отмеченные выполненными);openspec validate document-comments-ui --strict— valid. -
Step 4:
make web-checkиз корня репозитория — зелёный (вывод — в описание PR). -
Step 5: Commit «AURORA-1683 OpenSpec: tasks, проверки».