diff --git a/openspec/changes/wiki-package-d/design.md b/openspec/changes/wiki-package-d/design.md index 172308b..eb97726 100644 --- a/openspec/changes/wiki-package-d/design.md +++ b/openspec/changes/wiki-package-d/design.md @@ -124,3 +124,15 @@ T8.2: `request_fetch` отвечает `ready` по полю `state`, а `GET различая «новый/идущий». Цена ошибки — ответ PATCH называет уже идущий пакет. - 2026-10-27 — T6.1: картинка-другое-вложение внутри ссылки на вложение выводится своей ссылкой перед ссылкой внешнего вложения. Цена ошибки — порядок двух ссылок в строке. +- 2026-10-27 — R-1: в шапку плана добавлена строка «Ветка» — хук require-plan ищет имя ветки в плане. Цена ошибки — + нет (на поведение не влияет). +- 2026-10-27 — R-2: в MCP `sync` с ожиданием ошибка команды тоже выводится одной строкой (`_line`) — это тот же текст + Eva, что «ошибка синхронизации» решения 8. Цена ошибки — нет. +- 2026-10-27 — R-3: хранилище картинок живёт внутри `useWikiDownload` (третий параметр `attachments`, геттер `image`); + `components` в `PageView` пересоздаётся при смене карты, как и при смене `status`. Решение 10 допускало оба + прочтения; выбрано прежнее поведение `status`: состояние `busy`/`error` живёт в хуке и перемонтированием не + сбрасывается (тест). Цена ошибки — перемонтирование ссылок и картинок страницы при смене состояния другого вложения + (возможное мигание, потеря фокуса; финальное ревью M-3, deferred). +- 2026-10-27 — R-4: картинка выводится над кнопкой-ссылкой сохранения — по сценарию «Скачать картинку». Цена ошибки — нет. +- 2026-10-27 — R-5: у внешней ссылки края текста сохраняются, адрес вместо пустого текста — только при вынесенном + вложении (финальное ревью I-1, M-1). Цена ошибки — нет: прежнее поведение для ссылок без вложений. diff --git a/src/sdlc_hub/modules/wiki/convert.py b/src/sdlc_hub/modules/wiki/convert.py index 4abf258..8bf57ed 100644 --- a/src/sdlc_hub/modules/wiki/convert.py +++ b/src/sdlc_hub/modules/wiki/convert.py @@ -119,7 +119,12 @@ class _Converter(MarkdownConverter): if _unsafe(href): return text # исполняемая или внутренняя ссылка автора: остаётся только текст el["href"] = _href(href) - return marks + sep + super().convert_a(el, _tail(rest.strip()) or _label(href), parent_tags) + # края текста возвращаются как были: markdownify сам выносит их за ссылку, иначе слова склеиваются (ревью I-1); + # адрес вместо пустого текста — только если из ссылки вынесено вложение (M-1) + core = rest.strip() + lead, trail = rest[:len(rest) - len(rest.lstrip())], rest[len(rest.rstrip()):] + label = lead + _tail(core) + trail if core else (_label(href) if marks else rest) + return marks + sep + super().convert_a(el, label, parent_tags) def _strip_private(soup) -> None: diff --git a/tests/test_wiki_convert.py b/tests/test_wiki_convert.py index a1ab96a..cbc05de 100644 --- a/tests/test_wiki_convert.py +++ b/tests/test_wiki_convert.py @@ -335,3 +335,15 @@ def test_trailing_backslash_in_page_link_text(): def test_trailing_backslash_in_external_link_text(): md, _ = convert.to_markdown('

путь C:\\

', [], []) assert md == "[путь C:\\\\](http://ex.com/)\n" + + +def test_external_link_keeps_edge_spaces(): + """Финальное ревью I-1: пробелы на краях текста внешней ссылки выносятся наружу, слова не склеиваются.""" + md, _ = convert.to_markdown('

слово док дальше

', [], []) + assert md == "слово [док](http://ex.com/) дальше\n" + + +def test_empty_external_link_dropped(): + """Финальное ревью M-1: пустая внешняя ссылка без вложения внутри не выводится адресом.""" + md, _ = convert.to_markdown('

abc

', [], []) + assert md == "abc\n"