Отчет редтима, написать тесты, потенциально баги #206
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
attach-1 — Дублирование страницы ломает картинки, общие для нескольких страниц · high / high / moderate
(attachmentId остаётся X). Дублировать корень A. Открыть копии A' и B'.
attachmentMap(одна Map на всё поддерево) ключуется поattachmentId. Для BattachmentMap.set(X, {oldPageId:B,…})затирает запись A. Дальше при копировании блоба строкаif (attachment.pageId(A) !== pageAttachment.oldPageId(B)) continue→ копия пропускается,новый attachments-row не создаётся,
storageService.copyне зовётся. Обе копии картинок 404.duplicatePageс деревом из 2 страниц, ссылающихся на один attachmentId:ожидать 2 новых attachment-row и резолвящиеся src; сейчас — 0 строк, copy не вызывается.
page.service.ts:604(общая Map),:620(setпо attachmentId),:826-828(guard пропускает копию).attachment.pageId !== oldPageIdна 826.persist-1 — Сбой БД при автосейве проглатывается, правка теряется молча · high / medium / cheap
updatePage/транзакции при store (дедлок, разрыв коннекта,serialization failure). Затем последний клиент отключается, документ выгружается.
executeTxобёрнут вtry/catch, который толькоlogger.error. Функциявозвращается «успешно», Hocuspocus считает, что сохранил, выгружает (destroy) in-memory Y.Doc —
единственную копию новой правки. При следующей загрузке читается старый ydoc. Ретрая нет.
updatePage, кидающим один раз; ассертить, что onStoreDocument НЕ пробрасываетошибку и контент не записан (Hocuspocus примет это за успех). Целевой контракт — rethrow/повторный enqueue.
persistence.extension.ts:184-261(catch+log),@hocuspocus/server …:2520-2538(на resolve выгружает),
:2600-2601(destroy). Среда, не действие пользователя, но реальная потеря данных.ui-state-races-1 — Вне-очередное moveTreeNode роняет поддерево (нет cycle-guard в reducer) · high / medium / cheap
parentIdоказывается внутри перемещаемогоподдерева (сервер двинул X под Y, затем Y под X; на приёмнике Y ещё внутри X). Достижимо при двух
пользователях или быстром reorder + серверном эхо.
treeModel.move(drag-drop) имеетisDescendant-guard, аplaceByPosition(через который идёт socket-reducer
applyMoveTreeNode) — нет.removeудаляет узел со ВСЕМподдеревом (включая будущего родителя),
insertс отсутствующим parentId возвращает дерево безизменений → перемещаемый узел и все потомки молча исчезают из локального дерева, без ошибки/refetch.
tree-socket-reducers.test.ts: положить родителя под собственного ребёнка,ассертить, что
find(next,'a')иfind(next,'b')обаnull(текущее поведение).tree-model.ts:289-302(placeByPosition без guard),:100-101(insert no-op приотсутствии parent),
:314(move ИМЕЕТ guard — пути разошлись),tree-socket-reducers.ts:84-94.placeByPositionпроверяет толькоfind(source)/find(parent), безisDescendant.mdrt-2 — Экспорт в Markdown молча выкидывает узлы без turndown-правила · high / high / cheap
transclusionReference,mention,status,pageBreak→ Export to Markdown /copy-as-markdown. transclusionReference и pageBreak исчезают полностью; mention/status теряют
идентичность (data-id/color).
math, iframe, htmlEmbed, image, video, footnote). Остальные кастом-узлы попадают в дефолтную обработку:
пустой div = «blank» и удаляется, обёртка отдаёт только текст. Нарушен инвариант «никогда молча не терять блок».
htmlToMarkdown('<div data-type="transclusionReference" data-id="abc"></div>')→ ''.Round-trip mention: PM→HTML→MD→HTML→JSON, ассертить выживание узла.
turndown.utils.ts:55-72(фикс-набор правил),transclusion-reference.ts:61-68,mention.ts:273-285.persist-6 — Пустой live-документ перезаписывает непустой контент · high / medium / cheap
merge, опустошающая транклюзия) на странице с контентом; срабатывает debounce-store.
onStoreDocumentНЕТ empty-guard передupdatePage(в отличие отonLoadDocument).Единственные стопы —
!pageиisDeepStrictEqual.isEmptyParagraphDocиспользуется только длярешения о boundary-снимке, а не для блокировки записи. Пустой контент проходит и затирает страницу;
boundary-снимок защищает только переход user→agent.
actor='user'; ассертить, что updatePage вызван с пустым контентом (страница затёрта).
persistence.extension.ts:192-255(нет guard),:234(isEmptyParagraphDoc только для снимка).editor-pm-7 — Коллизии unique-id при copy/paste/duplicate блока · medium / medium / moderate
bulk-JSON, сохранив
attrs.id. Получаются два блока с одинаковым id; адресная правка (MCP patch_node/delete_node «before/after id») бьёт не по тому/по обоим узлам.
addUniqueIdsToDocтолько ДОБАВЛЯет id там, где их нет, и не дедуплицирует существующие. Узлы вненабора (callout, column, ячейки таблицы) вообще без стабильного id.
addUniqueIdsToDoc,ассертить, что дедупликации НЕ происходит; затем MCP patch_node по этому id.
extensions.ts:192-195,collaboration.util.ts:131-138,unique-id.ts:4-11.mdrt-5 — Сноски при импорте: порядок и осиротевшие определения · medium / high / cheap
без ссылки (
[^z]).extractFootnoteDefinitionsвыдаёт div’ы определений в порядке СТРОК, а ссылки остаютсяв порядке появления → нумерация разъезжается; осиротевшие определения выводятся безусловно.
Плагин-синхронизатор сносок работает только на локальных правках и пропускает remote/import →
несогласованное состояние персистится, а при первой правке пользователя осиротевшая сноска внезапно
исчезает (отложенное «само изменилось»).
extractFootnoteDefinitions('First[^b] then[^a].\n\n[^a]:…\n[^b]:…')→порядок def ['a','b'] vs ref ['b','a']; ассертить совпадение (упадёт). Отдельно — orphan
[^z]не должен попадать.footnote.marked.ts:99-125,footnote-sync.ts:210-224.vhs-1 / vhs-3 / vhs-11 — Снимок истории мис-тегается human↔agent из-за re-read контента · high / medium / moderate
воркера прилетает агентская правка (delay-0 снимок + перезапись строки страницы). Когда отложенный
«человеческий» джоб срабатывает, воркер
findByIdчитает ТЕКУЩИЙ (агентский) контент и сохраняет егос provenance из строки = 'agent'. Промежуточная человеческая версия не снимается вообще.
lastUpdatedSourceв момент запуска, без привязкик контенту на момент enqueue. Окно человеческого debounce (до 5 мин) сильно больше интервала правок,
так что «человеческий» джоб рутинно снимает то, что в строке на момент срабатывания.
updatePageнаC_agent/'agent'; запустить воркер; ассертить контент/provenance снимка (получится C_agent/'agent').
history.processor.ts:44-46,page-history.repo.ts:81,constants.ts:1,persistence.extension.ts:60-72(комментарий сам признаёт окно).test-pipeline-1 — Ветка «сбой БД при store» недостижима в тестах
executeTxв споке мокнут так, чтоtransaction().execute(fn)=>fn(trxStub)никогда не падает, аlogger.errorзастаблен → молчаливая потеря правки (persist-1) не ассертится ничем.persistence-store.spec.ts:57-62,119-122.test-pipeline-2 — Гонка delay-0 снимка проверена только как арифметика
compute-history-job.spec.tsпроверяетdelay===0и строку jobId; реальное переплетение(agent enqueue → human updatePage → worker findById) не воспроизводится.
:22-30,history.processor.spec.ts:57,121-143.test-pipeline-3 — Многонодовый redis-sync (378 строк) без единого теста; CI одно-нодовый
collab-узла.
redis-sync.extension.ts:35-56,.github/workflows/test.yml:41-49,71-79.test-pipeline-4 — Конверсия Yjs/PM тестируется только на одном документе
collaboration.util.spec.ts:182-243.test-pipeline-5 — Сбой jsonToText молча обнуляет textContent (поиск), не ассертится
onStoreDocumenttextContentдефолт null, ставится в try/catch; на throw остаётся null и пишется в БД,затирая полнотекст.
grep jsonToText … *.spec.tsпусто.persistence.extension.ts:166-172,245.test-pipeline-6 — Нет ни одного e2e/RTL живого редактора; reconciliation дерева не тестится
vi.mock('@tiptap/react')→ живой ProseMirror/Yjs нигде не запускается;jest-e2e.jsonесть, но CI его не зовёт; reducers дерева тестятся изолированно, без сценария «оптимистично + авторитетное
событие».
footnote-views.structure.test.tsx:33-39,apps/server/package.json:27,32,171.test-pipeline-7 — Нет порога покрытия; round-trip сносок проверяет подстроки, а не структуру
footnote-markdown.test.tsассертитtoContain(...), а не структурнуюэквивалентность/порядок/отсутствие дублей.
.github/workflows/test.yml:71-79,vitest.config.ts:11-15.test-pipeline-8 — Интеграционный суит maxWorkers:1 на одной БД → contention/row-lock не вскрыть
FOR UPDATEвonStoreDocumentникогда не проверяется на конкурентность.jest-integration.json:13-15,persistence.extension.ts:186-190.Закрываю как выполненное по сути. Большинство находок редтима исправлено и покрыто тестами в PR #212; остаток покрыт регрессионными тестами в PR #230. Два реально подтверждённых бага потери данных (mdrt-2 — экспорт MD молча теряет ноды; persist-6 — пустой live-документ затирает контент) осознанно оставлены неисправленными (их фикс — изменение поведения) и задокументированы характеризующими тестами (it.fails/it.failing). Чтобы этот долг не потерялся, он вынесен в отдельный issue: #244.