arm-core/docs/superpowers/plans/2026-10-27-document-comments-ui.md
Andrey Epifancev 07648ba3ac AURORA-1683 OpenSpec document-comments-ui и план
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:20:39 +04:00

12 KiB
Raw Blame History

Комментарии к документу в 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: antd Drawer справа, 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: управляемый antd Popover (trigger='click', open/onOpenChange), title={\Комментарии к документу ${fileName}`}; при открытии load(), при закрытии reset(). Содержимое — обёртка с onClick/onKeyDown stopPropagation(как в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.md change (группы по задачам плана, отмеченные выполненными); openspec validate document-comments-ui --strict — valid.

  • Step 4: make web-check из корня репозитория — зелёный (вывод — в описание PR).

  • Step 5: Commit «AURORA-1683 OpenSpec: tasks, проверки».