@@ -192,6 +192,24 @@ def _mapping_block_family(mapping: BlockMapping) -> str:
192192 return _xpath_block_family (mapping .xhtml_xpath )
193193
194194
195+ def _strategy_for_resolved_mapping (
196+ change : BlockChange ,
197+ mapping : BlockMapping ,
198+ ) -> str :
199+ """이미 찾은 mapping에 대해 처리 전략을 다시 계산한다."""
200+ if change .old_block .type == 'callout' :
201+ return 'containing'
202+ if mapping .children :
203+ if change .old_block .type == 'list' :
204+ return 'list'
205+ return 'containing'
206+ if change .old_block .type == 'list' :
207+ return 'list'
208+ if is_markdown_table (change .old_block .content ):
209+ return 'table'
210+ return 'direct'
211+
212+
195213def _flush_containing_changes (
196214 containing_changes : dict ,
197215 used_ids : 'set | None' = None ,
@@ -330,7 +348,9 @@ def _mark_used(block_id: str, m: BlockMapping):
330348 idx , mdx_to_sidecar , xpath_to_mapping )
331349 if mapping is None :
332350 continue
333- sidecar_block = xpath_to_sidecar_block .get (mapping .xhtml_xpath )
351+ sidecar_block = _find_roundtrip_sidecar_block (
352+ del_change , mapping , roundtrip_sidecar , xpath_to_sidecar_block ,
353+ )
334354 if _is_clean_block (
335355 add_change .new_block .type ,
336356 mapping ,
@@ -346,6 +366,18 @@ def _mark_used(block_id: str, m: BlockMapping):
346366 _paired_indices .add (idx )
347367 _mark_used (mapping .block_id , mapping )
348368 continue
369+ if sidecar_block_requires_reconstruction (sidecar_block ):
370+ patches .append (
371+ _build_replace_fragment_patch (
372+ mapping ,
373+ add_change .new_block ,
374+ sidecar_block = sidecar_block ,
375+ mapping_lost_info = mapping_lost_info ,
376+ )
377+ )
378+ _paired_indices .add (idx )
379+ _mark_used (mapping .block_id , mapping )
380+ continue
349381 old_plain = normalize_mdx_to_plain (
350382 del_change .old_block .content , del_change .old_block .type )
351383 new_plain = normalize_mdx_to_plain (
@@ -391,13 +423,37 @@ def _mark_used(block_id: str, m: BlockMapping):
391423 change , old_plain , mappings , used_ids ,
392424 mdx_to_sidecar , xpath_to_mapping )
393425
426+ if strategy == 'skip' and roundtrip_sidecar is not None and change .old_block .content :
427+ identity_sidecar = find_sidecar_block_by_identity (
428+ roundtrip_sidecar .blocks ,
429+ sha256_text (change .old_block .content ),
430+ (change .old_block .line_start , change .old_block .line_end ),
431+ )
432+ if identity_sidecar is not None :
433+ mapping = xpath_to_mapping .get (identity_sidecar .xhtml_xpath )
434+ if mapping is not None :
435+ strategy = _strategy_for_resolved_mapping (change , mapping )
436+
437+ if strategy == 'list' and mapping is None and roundtrip_sidecar is not None and change .old_block .content :
438+ identity_sidecar = find_sidecar_block_by_identity (
439+ roundtrip_sidecar .blocks ,
440+ sha256_text (change .old_block .content ),
441+ (change .old_block .line_start , change .old_block .line_end ),
442+ )
443+ if identity_sidecar is not None :
444+ mapping = xpath_to_mapping .get (identity_sidecar .xhtml_xpath )
445+
394446 if strategy == 'skip' :
395447 continue
396448
397- if strategy == 'list' :
398- list_sidecar = _find_roundtrip_sidecar_block (
449+ sidecar_block = None
450+ if mapping is not None :
451+ sidecar_block = _find_roundtrip_sidecar_block (
399452 change , mapping , roundtrip_sidecar , xpath_to_sidecar_block ,
400453 )
454+
455+ if strategy == 'list' :
456+ list_sidecar = sidecar_block
401457 # roundtrip sidecar가 있지만 이 list에 매칭되는 block이 없을 때
402458 # (cross-type 거부 또는 mapping drift) clean list는 whole-fragment 재생성으로 처리
403459 should_replace_clean_list = (
@@ -409,7 +465,8 @@ def _mark_used(block_id: str, m: BlockMapping):
409465 if (mapping is not None
410466 and not _contains_preserved_anchor_markup (mapping .xhtml_text )
411467 and (
412- sidecar_block_requires_reconstruction (list_sidecar )
468+ list_sidecar is not None
469+ or sidecar_block_requires_reconstruction (list_sidecar )
413470 or should_replace_clean_list
414471 )):
415472 _mark_used (mapping .block_id , mapping )
@@ -446,6 +503,18 @@ def _mark_used(block_id: str, m: BlockMapping):
446503 mdx_to_sidecar , xpath_to_mapping ))
447504 continue
448505
506+ if strategy == 'containing' and sidecar_block_requires_reconstruction (sidecar_block ):
507+ _mark_used (mapping .block_id , mapping )
508+ patches .append (
509+ _build_replace_fragment_patch (
510+ mapping ,
511+ change .new_block ,
512+ sidecar_block = sidecar_block ,
513+ mapping_lost_info = mapping_lost_info ,
514+ )
515+ )
516+ continue
517+
449518 new_plain = normalize_mdx_to_plain (
450519 change .new_block .content , change .new_block .type )
451520
@@ -465,9 +534,6 @@ def _mark_used(block_id: str, m: BlockMapping):
465534 and collapse_ws (new_plain ) == collapse_ws (mapping .xhtml_plain_text )):
466535 continue
467536
468- sidecar_block = _find_roundtrip_sidecar_block (
469- change , mapping , roundtrip_sidecar , xpath_to_sidecar_block ,
470- )
471537 if _can_replace_table_fragment (change , mapping , roundtrip_sidecar ):
472538 patches .append (
473539 _build_replace_fragment_patch (
0 commit comments