Andrey Epifancev 372a195ae0 chore(openspec): архив wiki-render (#53)
Дельта web-app влита в openspec/specs: изменено «Карточка задачи», добавлено «Разметка Jira в карточке».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:23:28 +04:00

40 KiB
Raw Blame History

web-app Specification

Purpose

Веб-приложение хаба (web/, React + TypeScript + Vite) под префиксом /app/: вход по клиентскому токену, доска связей (перенос links.html) и страницы задач со служебными заметками; сборка в src/jira_hub/web/app/.

Requirements

Requirement: Вход по токену

Приложение SHALL работать с API хаба того же хоста под клиентским токеном: хранить его в localStorage под ключом jira-hub-token (и в памяти, если хранилище закрыто) и передавать в заголовке Authorization: Bearer каждого запроса. Если токена нет или любой запрос ответил 401, приложение SHALL показывать экран входа вместо страниц: поле токена с подсказкой uv run jira-hub tokens, без панелей страниц и без фоновых запросов, которые могли бы изменить поле ввода. Введённый токен SHALL проверяться запросом GET /settings и сохраняться только при успехе. Действие «Сменить токен» SHALL очищать токен и показывать экран входа. Ответ 401 SHALL выводить на экран входа, только если запрос ушёл с текущим токеном: запоздавший 401 на запрос с прежним токеном после входа с новым не учитывается. Если проверка токена получила ответ 4xx, кроме 401, экран входа SHALL показывать текст ошибки хаба, а не «хаб не отвечает».

Scenario: Первый вход

  • WHEN приложение открыто впервые, токена нет
  • THEN показан экран входа; после ввода верного токена открывается запрошенная страница, токен сохранён

Scenario: Устаревший токен

  • WHEN сохранённый токен не подходит и запрос ответил 401
  • THEN показан экран входа с пояснением, что токен не подошёл; панели доски и задач не видны

Scenario: Токен от прежней доски

  • WHEN в localStorage есть jira-hub-token, сохранённый страницей links.html
  • THEN приложение входит с ним без запроса токена

Scenario: Запоздавший 401 от прежнего токена

  • WHEN запрос ушёл с прежним токеном, пользователь вошёл с новым, и после этого запрос ответил 401
  • THEN приложение остаётся на странице, новый токен сохранён

Scenario: Хаб отверг проверку токена

  • WHEN проверка GET /settings ответила 400 с detail
  • THEN экран входа показывает текст detail, а не «хаб не отвечает»

Requirement: Оболочка и адреса

Приложение SHALL открываться под префиксом /app/ и иметь страницы /app/board (доска связей), /app/issues (список задач) и /app/issues/{key} (карточка задачи — окно поверх страницы, требование «Карточка задачи»); /app и /app/ SHALL вести на /app/board, неизвестный путь — на страницу «не найдено» со ссылками на разделы. Верхняя панель SHALL содержать переходы «Доска», «Задачи» и действие «Сменить токен». Неожиданная ошибка отрисовки страницы SHALL показываться сообщением с кнопкой «Перезагрузить», не пустым экраном.

Scenario: Переход между разделами

  • WHEN на доске выбран раздел «Задачи»
  • THEN адрес — /app/issues, показан список задач; «назад» возвращает на доску

Requirement: Ошибки хаба

Приложение SHALL различать ошибки запросов к хабу: 401 — экран входа; хаб не отвечает (сеть, 5xx) — сообщение «хаб не отвечает» с возможностью повторить, ранее загруженные данные остаются на экране; прочие 4xx — текст detail ответа рядом с действием, которое его вызвало.

Scenario: Хаб остановлен

  • WHEN доска показана, хаб остановлен, нажато «Перечитать»
  • THEN на доске прежние данные и сообщение «хаб не отвечает — показаны прежние данные»

Requirement: Справочники из настроек хаба

Приложение SHALL брать перевод меток видов работ (kind_labels) и типов связей Jira (link_types) из GET /settings и не содержать собственных копий этих словарей. Если нужного словаря в настройках нет, страница SHALL сообщать, какого словаря не хватает, вместо пустой доски или списка без подписей.

Scenario: Новый тип связи в настройках

  • WHEN в link_types настроек добавлен тип и в зеркале есть связь этого типа
  • THEN после перезагрузки страницы доска показывает связь этого типа без изменения кода приложения

Requirement: Доска связей

Страница /app/board SHALL воспроизводить поведение доски связей хаба (links.html ревизии 3931a88) без изменений, кроме перечисленных в этом требовании и в требовании «Источник данных доски»: построение модели из GET /board, CSV фильтра Jira и JSON доски (arm-ka-links/1); дорожки по эпикам со свёрткой и перестановкой, служебные дорожки внизу; карточки с перетаскиванием, растяжкой, размером по содержимому и привязкой к сетке; стрелки связей с выравниванием; фильтры по спринтам и исполнителям; легенда со скрытием видов работ и типов связей; «+ соседи по связям», «⇄ передачи», «скрыть без связей», «⚑ критический путь», «▥ по датам» с колонками дат и их растяжкой; ящик «Проблемы»; таблица связей; экспорт и импорт JSON; тема; «Сбросить раскладку». Раскладка, дорожки, фильтры и тема SHALL храниться в localStorage под теми же ключами graph-<имя>…, что у links.html, с той же миграцией старых ключей. Результат построения модели, видимой выборки, критического пути и проблем SHALL совпадать с результатом links.html на одних и тех же данных и фильтрах. У карточки задачи (не у заглушки) SHALL быть значок «Открыть карточку», открывающий /app/issues/{key} окном поверх доски; ключ карточки по-прежнему открывает задачу в Jira. Пока окно открыто и после его закрытия доска SHALL сохранять прокрутку, раскладку, фильтры и строку состояния пакета. Действия «Сменить токен» в меню доски нет — оно в верхней панели приложения. Ещё два отличия от links.html (R-10, R-18): двойной щелчок по уголку карточки меряет высоту по содержимому при новой ширине, а не при прежней; указатель при перетаскивании и растяжке захватывается на нажатом элементе, а не на карточке, поэтому двойной щелчок по уголку сбрасывает размер. Загрузка файла CSV/JSON доступна только после входа, с экрана входа её нет. При возврате на /app/board с другой страницы приложения в той же вкладке доска SHALL восстанавливать прежнюю прокрутку; после перезагрузки страницы доска открывается сверху.

Scenario: Раскладка переживает переход

  • WHEN раскладка карточек сохранена страницей links.html, открыта /app/board с теми же данными
  • THEN карточки стоят на тех же местах, дорожки в том же порядке и свёртке, фильтры те же

Scenario: Совпадение со старой доской

  • WHEN на одной фикстуре GET /board и одном наборе фильтров построены модель, видимая выборка, критический путь и проблемы старой страницей и приложением
  • THEN результаты совпадают

Scenario: Открыть карточку

  • WHEN на карточке AURORA-1620 нажат значок «Открыть карточку»
  • THEN адрес /app/issues/AURORA-1620, карточка задачи открыта окном, под ним доска

Scenario: Возврат с карточки

  • WHEN доска прокручена вниз, нажат значок «Открыть карточку», затем окно закрыто
  • THEN адрес /app/board, доска показана с той же прокруткой и раскладкой, без повторной загрузки GET /board

Scenario: Возврат из другого раздела

  • WHEN доска прокручена вниз, выбран раздел «Задачи», затем раздел «Доска»
  • THEN доска показана с той же прокруткой

Requirement: Источник данных доски

Доска SHALL хранить явный источник данных — хаб или файл. Пока показан снимок из файла (CSV или JSON-снимок), ответы хаба SHALL его не заменять; «Перечитать» SHALL возвращать источник «хаб» и загружать GET /board заново без обращения к Jira. Импорт JSON в режиме хаба SHALL применять раскладку, дорожки и фильтры и перечитывать хаб. «Обновить из Jira» SHALL проверять агента (GET /agent; офлайн — предупреждение, пакет всё равно ставится), ставить POST /refresh {full: true, author: "viewer"}, опрашивать GET /batches/{id} каждые 3 с одним опросом до терминального статуса пакета и затем перечитывать GET /board. Кнопка «Обновить из Jira» SHALL быть недоступна, пока запрос /refresh в пути или пакет выполняется. Незавершённый пакет refresh с автором viewer SHALL подхватываться при каждой успешной загрузке из хаба, кроме перечитывания снимка после завершения собственного пакета; пакеты viewer с другими командами не подхватываются. Опрос пакета SHALL прекращаться при ответе 404 и после 5 ошибок «хаб не отвечает» подряд, с сообщением и снова доступной кнопкой. Шапка доски SHALL показывать источник данных (данные хаба на время последнего полного прогона; «из файла») и одно текущее событие (загрузка; пакет выполняется / выполнен / ошибка с текстом; хаб не отвечает — прежние данные, а при снимке из файла — «на доске снимок из файла»; агент офлайн — пакет поставлен; ошибка разбора файла — «файл не разобран: <причина>»); новое событие заменяет предыдущее целиком. Пока пакет выполняется, «Перечитать» и загрузка файла SHALL не стирать событие пакета.

Scenario: Поздний ответ хаба не затирает файл

  • WHEN запрос GET /board в пути, и в это время загружен CSV
  • THEN после ответа хаба на доске по-прежнему данные CSV с пометкой «из файла»

Scenario: Нет второго полного прогона

  • WHEN нажата «Обновить из Jira» и, пока пакет выполняется, доска перерисована (фильтр, «Перечитать»)
  • THEN кнопка остаётся недоступной, в хабе один пакет, его опрашивает один опрос

Scenario: Пакет пропал

  • WHEN опрос пакета получил 404
  • THEN опрос остановлен, показано сообщение, кнопка «Обновить из Jira» снова доступна

Scenario: Пакет после «Перечитать»

  • WHEN пакет viewer поставлен с другой вкладки и не завершён, нажато «Перечитать»
  • THEN доска показывает «пакет #N выполняется» и по завершении перечитывает данные

Scenario: «Перечитать» во время пакета

  • WHEN пакет viewer выполняется и нажато «Перечитать»
  • THEN шапка по-прежнему показывает «пакет #N выполняется»

Scenario: Хаб недоступен при снимке из файла

  • WHEN хаб не отвечает и загружен JSON-снимок доски, либо на доске снимок из CSV и хаб не ответил на перечитывание снимка после завершения пакета
  • THEN шапка показывает «хаб не отвечает — на доске снимок из файла», а не «показаны прежние данные»

Scenario: Чужой пакет не подхватывается

  • WHEN незавершённый пакет автора viewer состоит из команд ensure_fields
  • THEN доска его не опрашивает, кнопка «Обновить из Jira» доступна

Requirement: Список задач

Страница /app/issues SHALL показывать задачи зеркала из GET /issues таблицей: ключ (открывает карточку задачи окном поверх списка), тема, тип, статус Jira, исполнитель, спринт, срок (planned_end, иначе duedate), эпик (имя эпика, иначе ключ), вид работы, статус по факту, признак «сосед». Фильтры SHALL соответствовать параметрам GET /issues (спринт, исполнитель, вид работы, статус по факту, статус Jira, метка, эпик, только соседи, поиск по ключу или теме), храниться в query-строке адреса и применяться сервером; варианты выпадающих списков SHALL браться из снимка GET /board; значение фильтра из адреса, которого нет среди вариантов, SHALL показываться в списке с пометкой «(нет на доске)». Задачи SHALL загружаться порциями по 50 строк; «Показать ещё» SHALL быть доступна, пока последняя порция полная.

Scenario: Фильтр в адресе

  • WHEN выбран спринт «AWPCA_Спринт 2» и исполнитель
  • THEN адрес содержит sprint= и assignee=, таблица — результат GET /issues с этими параметрами; открытие этого адреса в новой вкладке показывает ту же выборку

Scenario: Конец списка

  • WHEN вторая порция вернула 17 задач
  • THEN в таблице 67 задач, «Показать ещё» не показана

Scenario: Фильтр по закрытому спринту

  • WHEN открыт адрес /app/issues?sprint=AWPCA_Спринт 0, а в снимке доски такого спринта нет
  • THEN выпадающий список «Спринт» показывает «AWPCA_Спринт 0 (нет на доске)» выбранным, таблица — по этому фильтру

Scenario: Карточка поверх списка

  • WHEN список отфильтрован по спринту, прокручен и догружен «Показать ещё», открыта карточка задачи и окно закрыто
  • THEN адрес списка с тем же фильтром, в таблице те же строки, прокрутка та же

Requirement: Карточка задачи

Адрес /app/issues/{key} SHALL открывать карточку задачи из GET /issues/{key} модальным окном поверх страницы, с которой её открыли (список «Задачи» или доска); страница под окном SHALL оставаться смонтированной и сохранять фильтры, загруженные строки, прокрутку и раскладку. Перезагрузка страницы с открытым окном SHALL сохранять страницу под окном. Открытый напрямую адрес (новая вкладка, закладка) SHALL показывать окно поверх списка задач без фильтров. Окно SHALL закрываться кнопкой ✕, клавишей Esc и щелчком по фону (нажатие и отпускание — на фоне; выделение текста, отпущенное за краем окна, окно не закрывает) и возвращать на страницу, с которой его открыли, даже после переходов по связям внутри окна; «назад» браузера после закрытия MUST NOT открывать карточку снова. Переход по связанной задаче SHALL менять содержимое окна и адрес, «назад» браузера — возвращать к прежней карточке. Пока окно открыто, фокус SHALL оставаться в окне, страница под ним не прокручивается.

Содержимое окна:

  • шапка: ключ, статус Jira, статус по факту (цветом класса из status_labels), «Открыть в Jira», ✕, тема; строка «исполнитель · срок · спринт · эпик» без пустых частей, срок — planned_end, иначе duedate, с пометкой «просрочено», если он раньше сегодняшней даты и класс статуса по факту не fact-done и не fact-cancelled; эпик — темой (epic_summary), иначе ключом;
  • «Связи» по группам в порядке: «Зависит от» (входящие связи логических типов блокирует начало и блокирует завершение из link_types, с меткой «начало» / «завершение»), «Блокирует» (исходящие тех же типов, с теми же метками), «Связана с» (тип связанные в обе стороны), «Прочие» (тип Jira вне link_types: имя типа и направление «→» / «←»); в строке — ключ (открывает связанную задачу в окне), тема и статус Jira, а если задачи нет в зеркале — ключ и «нет в зеркале»; пустые группы не показываются, без связей — «связей нет»;
  • «Описание» — текст, отрендеренный по требованию «Разметка Jira в карточке», с кнопкой «исходник» / «текст» в заголовке раздела; длиннее 8 строк — свёрнуто с кнопкой «развернуть» / «свернуть» в обоих режимах; без описания раздела нет;
  • «Комментарии (N)» — новые сверху, автор, время, кнопка «исходник» / «текст» у каждого комментария, текст, отрендеренный по требованию «Разметка Jira в карточке»; кнопка «Скрыть» / «Показать» сворачивает раздел, выбор SHALL запоминаться в браузере для всех карточек; без комментариев — «комментариев нет»;
  • «Служебные заметки» (требование «Служебные заметки»);
  • «Прочие поля» — непустые из: тип, приоритет, автор, метки, плановое начало, обновлена в Jira, сосед по связям;
  • «Команды моста (N)» — 10 последних команд по задаче (id, пакет, тип, статус, время завершения, ошибка), раздел свёрнут при открытии; если команд не было — текст «команд по задаче не было». На ширине от 900 px «Связи», «Описание», «Комментарии» — в левой колонке, заметки, прочие поля и команды — в правой; уже — одна колонка: заметки, связи, описание, комментарии, прочие поля, команды. Если задачи нет в зеркале (404), окно SHALL сообщать об этом с кнопкой «Закрыть»; хаб не отвечает — сообщение и «Повторить», ранее загруженные данные остаются.

Scenario: Связанная задача

  • WHEN у AURORA-1620 исходящая связь ASUPR_Блокер_Начала на AURORA-1621 «Расчёт свода» в статусе «В работе»
  • THEN в карточке в группе «Блокирует» строка «начало · AURORA-1621 · Расчёт свода · В работе»; щелчок по ключу меняет содержимое окна на AURORA-1621, адрес — /app/issues/AURORA-1621; «назад» браузера возвращает AURORA-1620

Scenario: Входящий блокер

  • WHEN у AURORA-1620 входящая связь ASUPR_Блокер_Завершения от AURORA-1500
  • THEN строка с AURORA-1500 и меткой «завершение» — в группе «Зависит от»

Scenario: Задача без команд

  • WHEN по AURORA-1620 не было команд
  • THEN в разделе «Команды моста» — текст «команд по задаче не было», таблицы нет

Scenario: Закрытие после переходов

  • WHEN со списка «Задачи» открыта AURORA-1620, в окне — переход по связи на AURORA-1621, нажата Esc
  • THEN окно закрыто, адрес — адрес списка с прежними фильтрами; «назад» браузера не открывает карточку

Scenario: Прямой адрес

  • WHEN в новой вкладке открыт /app/issues/AURORA-1620, затем нажат ✕
  • THEN под окном был список задач без фильтров; после закрытия адрес /app/issues, «назад» не открывает карточку

Scenario: Скрытые комментарии

  • WHEN в карточке нажато «Скрыть» у комментариев, затем страница перезагружена и открыта другая карточка
  • THEN комментарии в ней скрыты, видна кнопка «Показать» и число комментариев

Requirement: Служебные заметки

Карточка задачи SHALL показывать служебные заметки в режиме чтения: «Вид работы», «Статус по факту», «Сделано», «Осталось», «Заметка владельца» (пустые — «—») и время и автор последнего изменения; кнопка «Править» SHALL открывать форму: «вид работы» — выбор из значений kind_labels настроек (и текущего значения, если его там нет, и пустого), «статус по факту» — текст с подсказками из ключей status_labels, «сделано», «осталось», «заметка владельца» — многострочный текст; «Отмена» SHALL возвращать режим чтения, при несохранённых правках — после подтверждения. Сохранение SHALL отправлять PUT /issues/{key}/note только с изменёнными полями и updated_by: "web"; очищенное поле отправляется как null. После успеха SHALL показываться режим чтения с сохранённой заметкой; список задач под окном SHALL показывать новые «вид работы» и «статус по факту» без «Перечитать», доска под окном SHALL перечитать снимок (GET /board) без потери раскладки и прокрутки; при 422 — текст ошибки у формы без потери введённого. Закрытие окна, переход по связи и уход в другой раздел с несохранёнными правками SHALL требовать подтверждения. Заметки пишутся только в хаб, в Jira приложение ничего не отправляет.

Scenario: Только изменённые поля

  • WHEN в заметке нажато «Править», изменено только «осталось» и нажато «Сохранить»
  • THEN тело PUT /issues/{key}/note — {rest_text: "…", updated_by: "web"}; карточка снова в режиме чтения с новым «Осталось»

Scenario: Уход с несохранёнными правками

  • WHEN в форме заметки есть несохранённая правка и нажата Esc
  • THEN приложение спрашивает подтверждение; при отказе окно открыто, форма с введённым текстом

Scenario: Заметка видна на доске

  • WHEN карточка AURORA-9002 открыта с доски, вид работы изменён на «тестирование» и сохранён
  • THEN доска под окном остаётся на месте и перезапрашивается (GET /board отдаёт work_kind «тестирование»); окно закрывается без потери раскладки и прокрутки доски

Requirement: Сборка и разработка

Приложение SHALL быть npm-проектом в web/ (React, TypeScript, Vite) с точными версиями зависимостей и package-lock.json. make web SHALL устанавливать зависимости (npm ci) и собирать приложение в src/jira_hub/web/app/; каталог сборки не хранится в git. Шрифты и прочие ресурсы страницы SHALL входить в сборку: приложение не обращается к внешним адресам. make check SHALL запускать uv run pytest, проверку типов и линтер фронта и тесты фронта; зелёным считается только весь набор. npm run dev SHALL поднимать сервер разработки с прокси маршрутов API на хаб из JIRA_HUB_URL (по умолчанию тестовый http://127.0.0.1:8791).

Scenario: Сборка с нуля

  • WHEN в чистом checkout выполнен make web
  • THEN в src/jira_hub/web/app/ есть index.html и assets/; git status не показывает файлов сборки

Scenario: Без интернета

  • WHEN приложение открыто на машине без доступа в интернет
  • THEN страница не запрашивает ресурсов вне хаба, текст набран IBM Plex Sans

Requirement: Разметка Jira в карточке

Описание задачи и текст каждого комментария в карточке SHALL показываться отрендеренной вики-разметкой Jira (хаб хранит их как есть, в wiki) так, как их показывает Jira. Разбираются:

  • блоки: заголовки h1.–h6. в начале строки; списки — строки, начинающиеся (после необязательных пробелов) с маркеров *, # и пробела (уровень вложенности — числом маркеров, * — маркированный, # — нумерованный, смешанная вложенность *# — по последнему маркеру) или с одного - и пробела (маркированный, первый уровень); --, *- и прочие сочетания с - — не список; таблицы — строки, начинающиеся с |, ячейки ||заголовок|| и |ячейка|; {code} / {code:язык} … {code} и {noformat} … {noformat} — моноширинный блок без разбора разметки внутри; {quote} … {quote} и строка bq. — цитата; открывающий тег блока — в начале строки, закрывающий — в любом месте той же или последующей строки, текст после закрывающего тега разбирается дальше как обычные строки; тег не в начале строки — текст как есть; строка ---- — горизонтальная черта; прочие строки — абзацы, разделённые пустыми строками, одиночный перенос внутри абзаца — перенос строки;
  • внутри строки: *жирный*, _курсив_, -зачёркнутый-, +подчёркнутый+, ^верхний^, ~нижний~, {{моно}} ({{…}} — без разбора внутри); маркер выделения действует, только если открывающий стоит в начале текста или после пробела либо знака препинания и за ним не пробел и не тот же символ (**x** — текст как есть), а закрывающий стоит после не-пробела и за ним не буква и не цифра (data_type, 2026-10-15, счёт-фактура, a*b*c — обычный текст); \\ — перенос строки; \ перед символом разметки — сам символ;
  • ссылки: [текст|адрес], [адрес] и голые адреса http://, https:// — ссылка в новой вкладке, только для схем http, https, mailto, иначе текст как есть; [ключ задачи], [текст|ключ задачи] и ключ задачи в тексте вида ПРОЕКТ-123, где ПРОЕКТ — project_key из настроек хаба, — ссылка, открывающая карточку этой задачи в окне (UTF-8, SHA-256 и ключи других проектов — текст); [~логин] — «@логин». Всё, что разборщик не распознал ({panel}, {color}, !картинка!, незакрытые маркеры и блоки и прочее), SHALL показываться как текст исходника без потерь. Разметка MUST NOT превращаться в HTML строкой (innerHTML, dangerouslySetInnerHTML): текст задачи не может добавить на страницу элементы, атрибуты или скрипты. Если рендер блока завершился ошибкой, блок SHALL показываться исходником.

Кнопка «исходник» у описания и у каждого комментария SHALL переключать этот блок на исходник — текст с сохранёнными переносами строк, как без разбора разметки, — и обратно кнопкой «текст»; остальные блоки не переключаются. При каждом открытии карточки (в том числе при переходе по связи) все блоки показываются отрендеренными, выбор не запоминается.

Scenario: Описание со структурой

  • WHEN описание AURORA-1620 — h3. Контекст, строка Шаги:, затем # Загрузить ОСВ и # Открыть документ, затем пустая строка и *Итог:* ошибка в {{data_type}}
  • THEN в «Описании» заголовок «Контекст», абзац «Шаги:», нумерованный список из двух пунктов и абзац, где «Итог:» — жирным, «data_type» — моноширинным; символов h3., #, *, {{ не видно

Scenario: Переключение на исходник

  • WHEN в карточке AURORA-1620 у описания нажато «исходник»
  • THEN описание показано исходником с переносами строк (видны h3. Контекст и # Загрузить ОСВ), кнопка — «текст»; комментарии остаются отрендеренными; после «текст» описание снова отрендерено

Scenario: Исходник у одного комментария

  • WHEN у второго комментария нажато «исходник», затем открыта связанная задача и возврат «назад»
  • THEN сразу после нажатия исходником показан только второй комментарий; после возврата все комментарии отрендерены

Scenario: Ключ задачи в тексте

  • WHEN комментарий — П. 1 ведётся в AURORA-1445; AURORA-1645 снята как дубль.
  • THEN «AURORA-1445» и «AURORA-1645» — ссылки; щелчок по первой меняет содержимое окна на AURORA-1445

Scenario: Неизвестная разметка и опасный адрес

  • WHEN описание — {color:red}важно{color}, !схема.png! и [нажми|javascript:alert(1)]
  • THEN все три показаны текстом исходника как есть, ссылки и картинки нет, скрипт не выполняется

Scenario: Списки с отступом и пустыми строками

  • WHEN описание — Шаги воспроизведения:, * Загрузить ОСВ., пустая строка, * Дождаться обработки.
  • THEN абзац «Шаги воспроизведения:» и маркированные пункты «Загрузить ОСВ.» и «Дождаться обработки.» без звёздочек

Scenario: Обычный текст не портится

  • WHEN описание — Поле data_type = null, дата 2026-10-15, оборот 18 803 903,65 - 9 351 102,89, a*b*c, UTF-8, **важно**
  • THEN текст показан как есть, без курсива, зачёркивания, жирного и ссылок