feat(editor): image captions (figcaption) with lossless markdown round-trip (#221) #233
Open
Ghost
wants to merge 11 commits from
feat/221-image-captions into develop
pull from: feat/221-image-captions
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:test/244-part-b
vvzvlad:fix/255-ws-redis-adapter-leak
vvzvlad:feat/251-intentional-clear
vvzvlad:fix/252-e2e-open-handles
vvzvlad:feat/184-autonomous-agent-runs
vvzvlad:feat/git-sync
vvzvlad:refactor/193-tool-spec-registry
vvzvlad:fix/244-dataloss-bugs
vvzvlad:fix/embeddings-reindex-progress
vvzvlad:develop
vvzvlad:feature/offline-sync
vvzvlad:feat/229-catalog-yaml
vvzvlad:feat/243-blob-sandbox
vvzvlad:feat/228-inline-footnotes
vvzvlad:fix/qa-ui-bugs-216-218
vvzvlad:feature/agent-roles-catalog
vvzvlad:fix/share-alias-rename
vvzvlad:fix/ai-chat-empty-render
vvzvlad:feat/191-chat-doc-binding
vvzvlad:feat/201-temporary-notes
vvzvlad:feat/198-interrupt-agent
vvzvlad:feat/ai-chat-full-history
vvzvlad:feat/199-ai-generate-title
vvzvlad:feat/205-share-aliases
vvzvlad:batch/issues-189-187-170
vvzvlad:feat/170-mcp-test-button
vvzvlad:feat/189-context-badge
vvzvlad:feat/198-interrupt-agent-send-now
vvzvlad:fix/issues-190-159
vvzvlad:fix/ai-chat-new-chat-during-stream
vvzvlad:fix/ai-chat-stream-perf
vvzvlad:batch/issues-2026-06-25
vvzvlad:feat/ai-chat-persistent-history
vvzvlad:fix/ai-chat-copy-chat-wysiwyg
vvzvlad:fix/ai-stream-reset-resilience
vvzvlad:fix/ai-stream-undici-timeout
vvzvlad:fix/footnote-review-1227-followup
vvzvlad:fix/ai-chat-token-counter-realtime
vvzvlad:docs/manual-qa-test-plan
No Reviewers
Labels
Clear labels
bug
documentation
duplicate
enhancement
epic
feature
good first issue
help wanted
idea
invalid
needs-human
question
refactor
review/approved
review/changes-requested
review/needs
security
status/blocked
status/done
status/in-progress
status/ready
test
wontfix
Something isn't working
Improvements or additions to documentation
This issue or pull request already exists
New feature or request
Large multi-phase effort spanning many changes
New functionality request
Good for newcomers
Extra attention is needed
Idea / proposal for discussion
This doesn't seem right
эскалация: нужно решение человека
Further information is requested
Code cleanup / refactoring
в последнем ревью нет открытых blocking-находок
последнее ревью оставило открытые blocking-находки
head не ревьюился (head != reviewed_head)
Security / hardening issue
ждёт зависимость blocked_by
закрыто и проверено
в активной работе (мягкая заявка)
специфицировано, не заблокировано, ждёт исполнителя
Test coverage / test infrastructure
This will not be worked on
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vvzvlad/gitmost#233
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/221-image-captions"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #221.
Подписи к картинкам (
<figcaption>): видимый текст под картинкой, редактируется из бабл-меню, сохраняется во всех форматах.captionна узлеimage(остаётся atom, схема не меняется → сервер round-трипит через generateHTML/JSON без правок).<figure>+<figcaption>(resize/imperative — handles/offsetHeight по картинке; React-placeholder).use-caption-control(по образцу alt-text).<div><img data-caption>, import восстанавливает черезparseHTML.Тесты: editor-ext 148 (parseHTML/renderHTML round-trip, full HTML→JSON→HTML, md round-trip), mcp 297. client tsc 0. Поиск по caption — вне скоупа по issue.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
🤖 Generated with Claude Code
Code review — image captions (
figcaption) with lossless markdown round-trip (#221)Вердикт: Request changes — по существу изменение корректное, аккуратное и готовое к мержу: проблем безопасности, регрессий и багов стабильности не найдено (8 аспектов проверены параллельно). Единственный must‑fix — механический: отсутствует запись в
CHANGELOG.md, который в проекте ведётся для каждой пользовательской фичи. После её добавления — Approve.Базовая ветка:
develop(106df7c9). Проверены все 13 файлов диффа (+427/−8).Обязательно исправить перед мержем
[documentation] Добавить запись в
CHANGELOG.md[Unreleased] → ### Addedпро подписи к картинкам (#221) —CHANGELOG.md:11-43.Проект ведёт «Keep a Changelog»: секция
## [Unreleased]документирует каждую пользовательскую фичу с номером issue (#198, #222, #226/#227). Это изменение — явно пользовательская фича (новая командаsetImageCaption, атрибутcaption, UI редактирования в бабл‑меню картинки, losslessdata-captionв markdown), ноgrep -ni "caption\|#221" CHANGELOG.mdничего не находит. Исправление: добавить запись под## [Unreleased] → ### Addedв стиле соседних (короткий жирный лид + 1–2 предложения +(#221)).[suggestion][test-coverage] Экспортировать и покрыть юнит‑тестом
sanitizeCaption(схлопывание пробелов / trim / граница 500 символов) —apps/client/src/features/editor/components/common/use-caption-control.tsx:40-42.Это единственная нетривиальная чистая логика нового хука и точка целостности данных (её результат уходит в
updateAttributes): ни одна ветка (collapse\s+, trim,slice(0,500)) не покрыта. Проект тестирует именно такие нормализаторы строк (normalizeLabelNameвnormalize-label.test.ts), так что пробел реальный. Сейчас функция приватная — её нельзя протестировать (как и сестринскийsanitizeAlt, который тоже не покрыт, поэтому не блокирующее). Исправление: экспортироватьsanitizeCaptionи добавить маленький vitest по образцуnormalize-label.test.ts.[suggestion][simplification] Удалить дублирующий CSS‑модульный класс
.imageCaption, оставив только глобальный.image-caption—apps/client/src/features/editor/components/image/image-view.module.css:10-19..imageCaption(image‑view.module.css) и.image-caption(styles/media.css) — побайтово идентичны, и React‑ImageViewвешает оба на один элемент (image-view.tsx:75). Глобальный.image-caption(скоуп.ProseMirror) уже стилизует и React‑путь, и императивныйResizableNodeView, и read‑only/share. Исправление: удалить блок.imageCaptionи убратьclasses.imageCaptionизclsxвimage-view.tsx:75, оставив один источник правды вmedia.css.[suggestion][documentation] Поправить комментарий «(like the video rule)» в turndown‑правиле картинки —
packages/editor-ext/src/lib/markdown/utils/turndown.utils.ts:271-273.Комментарий говорит, что captioned‑картинка эмитится «raw
<img>wrapped in a block<div>(like the video rule)», но в этом файле собственное правилоvideo(строки ~344‑351) эмитит markdown‑ссылку[name](src), а не<div>. Симметрия с video справедлива только в MCP‑конвертере (markdown-converter.ts), не в turndown. Исправление: убрать «(like the video rule)» из turndown‑комментария или уточнить, что аналогия относится к MCP‑конвертеру.Покрытие тестами
Покрытие новой логики хорошее. Три добавленных файла осмысленно проверяют три разных пути с точными ассертами:
image.spec.ts— контрактcaption: parseHTML восстанавливает, renderHTML эмититdata-captionтолько при наличии, caption‑less остаётся чистым, полный HTML→JSON→HTML round‑trip с энтити‑экранированием, image остаётся atom.image-markdown.test.ts— turndown/marked round‑trip: raw<img data-caption>, caption‑less остаётся, спецсимволы выживают.markdown-converter.test.mjs— MCP PM→MD:без подписи,<div><img data-caption>с подписью, экранирование&и".Сознательно НЕ покрыто (приемлемо по планке проекта): команда
setImageCaption(однострочная обёртка надupdateAttributes, симметрична уже непокрытойsetImageAlign) и императивный node‑view DOMapplyCaption/onUpdate(проект не юнит‑тестит императивный DOM node‑view). Единственный реально стоящий пробел —sanitizeCaption(см. выше).Архитектура и дизайн
5fbd655441todc14a9a540Extract the ~110 duplicated lines into one parameterized useImageTextFieldControl and make useAltTextControl/useCaptionControl thin wrappers. Behavior identical; t("...") literals stay in the wrappers so i18n extraction keeps working. sanitizeCaption still exported for its unit test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Ревью
57308bc3f— переревью ПОЛНЫМ РАЗДЕЛЬНЫМ веером (8 отдельных субагентов: security / stability / regressions / test-coverage / conventions / documentation / simplification / architecture) на полном диффе. Вердикт: approved.Все аспекты LGTM. Подтверждены закрытыми все прошлые находки (F1–F8). XSS нет (caption рендерится как textContent/JSX, экранирование атрибутов корректно), round-trip lossless на обоих путях (editor-ext turndown+marked и MCP docmost-schema), обратная совместимость изображений без подписи цела, покрытие тестами полное (вкл. подпись в колонке/imageToHtml, спецсимволы), дублирование схемы editor-ext↔mcp — задокументированный осознанный паттерн.
(Прошлый round был сделан объединённым субагентом — переревьюено раздельным веером по требованию. С этого момента ревью постит аккаунт agent_reviewer.)
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.