Skip to content

confluence-mdx: Phase 0 공용 helper 추출 — xhtml_normalizer 및 list tree public API#914

Merged
jk-kim0 merged 4 commits intomainfrom
jk/phase0-xhtml-normalizer-and-list-tree
Mar 13, 2026
Merged

confluence-mdx: Phase 0 공용 helper 추출 — xhtml_normalizer 및 list tree public API#914
jk-kim0 merged 4 commits intomainfrom
jk/phase0-xhtml-normalizer-and-list-tree

Conversation

@jk-kim0
Copy link
Contributor

@jk-kim0 jk-kim0 commented Mar 13, 2026

Description

  • reverse-sync 재구성 설계(confluence-mdx: reverse-sync 재구성 설계와 구현 정리 범위를 정의합니다 #913)의 Phase 0 구현입니다.
  • xhtml_normalizer.py 공용 모듈을 추가합니다: extract_plain_text(), normalize_fragment(), extract_fragment_by_xpath()
  • emitter.py_ListNode/_parse_list_items()/_build_list_tree()ListNode/parse_list_tree() public API로 승격합니다.
  • Level 0 helper tests 43개를 추가합니다 (전체 820 pass).

Phase 0 게이트 충족 항목

  • extract_plain_text: ac:image/ac:link 제외, ac:emoticon fallback 포함, ac:plain-text-body 제외
  • normalize_fragment: ignored attributes 제거, layout/decoration/non-reversible macro 정리, idempotent
  • extract_fragment_by_xpath: 단일/복합/다단계 xpath, macro-* 패턴
  • parse_list_tree: nested/mixed/continuation line 지원
  • 기존 테스트 820개 전부 green

Added/updated tests?

  • Yes — test_reverse_sync_xhtml_normalizer.py (34 tests), test_reverse_sync_list_tree.py (9 tests)

Additional notes

  • Phase 1 (sidecar schema v3) PR이 이 PR 위에 쌓입니다.

🤖 Generated with Claude Code

…ublic API를 추가합니다

- xhtml_normalizer.py: extract_plain_text, normalize_fragment, extract_fragment_by_xpath 구현
- emitter.py: ListNode, parse_list_tree public API 승격
- Level 0 helper tests 43개 추가 (전체 820 pass)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
querypie-docs Ready Ready Preview, Comment Mar 13, 2026 1:59pm

Request Review

@jk-kim0
Copy link
Contributor Author

jk-kim0 commented Mar 13, 2026

리뷰하면서 확인한 핵심 이슈 2개 남깁니다.

  1. bin/reverse_sync/xhtml_normalizer.pyextract_plain_text()<ac:link>를 통째로 textless로 취급하고 있어서, 현재 reverse-sync의 plain-text contract와 어긋납니다.

    • 현재 구현은 ac:image, ac:link를 같은 preservation unit으로 보고 둘 다 제외합니다.
    • 그런데 기존 mapping 쪽은 child.get_text() / _get_text_with_emoticons() 기준이라 링크 label은 plain text에 포함됩니다.
    • 실제 fixture로 확인해보면 <ac:link>가 들어간 13개 fragment에서 visible label이 전부 사라집니다. 예: tests/testcases/1911652402/expected.roundtrip.jsonp[5], p[7], tests/testcases/1844969501/expected.roundtrip.jsonp[3], ul[2].
    • 이 helper는 reconstruction anchor offset의 기준 좌표계를 정의한다고 되어 있어서, 링크 텍스트를 빼면 offset이 실제 페이지 기준과 바로 어긋납니다.
  2. mdx_to_storage.emitter.parse_list_tree()를 public API로 승격한 방향 자체는 맞는데, 현재 shape만으로는 reconstruction에 필요한 ordered list start 정보를 보존하지 못합니다.

    • ListNode에는 ordered/text/depth/children만 있고, _parse_list_items()에서 원래 marker number(2., 3. 등)를 버립니다.
    • _render_list_nodes()도 ordered list를 항상 <ol start="1">로 렌더링합니다.
    • 실제 fixture에는 중간부터 시작하는 ordered list가 이미 있습니다. 예: tests/testcases/692355151/expected.mdx2. / 3. 구간.
    • reconstruction이 이 public helper를 기준으로 가려면 최소한 start number를 보존하는 필드가 필요합니다. 지금 형태면 caller가 다시 별도 parsing을 해야 해서 public helper 승격 효과가 반감됩니다.

참고로 새 테스트 2개 파일은 로컬에서 통과했고, extract_fragment_by_xpath() / normalize_fragment()는 현재 fixture corpus 기준으로는 큰 문제를 보지 못했습니다. 다만 위 2개는 실제 reverse-sync 동작에 직접 영향을 줄 수 있어서 먼저 정리하는 편이 안전해 보입니다.

jk-kim0 and others added 2 commits March 13, 2026 22:05
- mapping_recorder의 _iter_block_children, _get_text_with_emoticons 등을
  public API로 승격하고 backward-compat alias를 추가합니다
- xhtml_normalizer에서 중복 정의를 제거하고 mapping_recorder에서 import합니다
- normalize_fragment에 ignore_ri_filename 파라미터를 추가합니다
- _strip_decorations에 빈 <p> 제거 로직을 추가합니다
- mdx_to_storage_xhtml_verify의 중복 정규화 코드를 normalize_soup()으로 교체합니다
- 새 테스트 2건 추가 (ignore_ri_filename, empty_paragraph_removed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- extract_plain_text에서 ac:link를 skip하지 않고 visible label(ac:link-body)
  텍스트를 포함하도록 수정합니다 (기존 mapping_recorder contract와 일치)
- ListNode에 start 필드를 추가하여 ordered list marker number를 보존합니다
- _parse_list_items에서 marker number를 파싱하여 start에 저장합니다
- _render_list_nodes에서 첫 항목의 start를 <ol start="N">에 반영합니다
- 관련 테스트 추가/수정 (ac:link with body, start number preservation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jk-kim0
Copy link
Contributor Author

jk-kim0 commented Mar 13, 2026

리뷰 감사합니다. 두 이슈 모두 수정했습니다 (46faf69a).

1. extract_plain_textac:link visible label 포함

  • ac:link를 skip 대상에서 제거했습니다. 이제 <ac:link-body> 내 visible label이 plain text에 포함됩니다.
  • ac:image만 preservation unit으로 skip합니다 (이미지는 visible text 없음).
  • 기존 mapping_recorder.get_text_with_emoticons()의 contract와 일치합니다.
  • 테스트: test_paragraph_with_inline_link_with_body 추가, 기존 no-body 케이스도 유지.

2. ListNode.start — ordered list marker number 보존

  • ListNodestart: int | None 필드를 추가했습니다.
  • _parse_list_items에서 regex group을 통해 marker number를 파싱하여 저장합니다.
  • _render_list_nodes에서 첫 항목의 start<ol start="N">에 반영합니다.
  • 테스트: test_ordered_list_start_number_preserved (중간 번호 시작), 기존 테스트에도 start assertion 추가.

전체 테스트 824건 통과 확인.

@jk-kim0
Copy link
Contributor Author

jk-kim0 commented Mar 13, 2026

914 PR 리뷰 의견 정리입니다.

이번 변경은 Phase 0 공용 helper 추출이라는 목표 자체는 타당하고, 특히 ListNode / parse_list_tree()를 외부에서 재사용 가능한 API로 노출한 방향은 좋다고 봤습니다. 관련 테스트도 충분히 보강되어 있어서 이 부분만 놓고 보면 914의 설계 의도는 분명합니다.

다만, 914와 916 중 하나를 채택해야 한다면 저는 916 쪽을 우선 추천합니다. 이유는 아래와 같습니다.

결론

  • ListNode / parse_list_tree() 공개 API 자체는 914의 장점입니다.
  • 하지만 xhtml_normalizer의 핵심 의미론은 914보다 916이 현재 reverse-sync 동작과 더 잘 맞습니다.
  • 따라서 선택지는 다음이 가장 안전하다고 봅니다.
    • 1안: 916을 기반으로 채택
    • 2안: 그 위에 914의 list tree public API만 선별적으로 가져오기

914에서 가장 우려되는 점

1. extract_plain_text()의 code macro 처리 의미가 현재 reverse-sync 기준과 어긋납니다

914의 reverse_sync/xhtml_normalizer.py에서는 ac:plain-text-body를 제외하도록 구현되어 있습니다. 그 결과 code macro fragment에 대해 plain text를 추출하면 실제 코드 본문이 아니라 language 파라미터 같은 메타데이터만 남을 수 있습니다.

예를 들어 아래와 같은 fragment에서:

<ac:structured-macro ac:name="code">
  <ac:parameter ac:name="language">python</ac:parameter>
  <ac:plain-text-body><![CDATA[print("hello")]]></ac:plain-text-body>
</ac:structured-macro>

914의 helper는 plain text를 사실상 python으로 보게 되고, 현재 reverse-sync 쪽 mapping 기준은 코드 본문인 print("hello") 쪽에 더 가깝습니다.

이 차이는 단순한 표현 차이가 아니라, 앞으로 이 helper를 reconstruction / anchor offset 계산에 재사용할 경우 좌표 기준 자체를 틀리게 만들 수 있는 리스크가 있습니다. 즉, helper를 공용화해놓고 실제 의미가 기존 source of truth와 다르면, 이후 단계에서 오히려 더 위험해집니다.

2. “공용 helper 추출”이라고 하지만 verifier 쪽 source of truth는 아직 통합되지 않았습니다

914는 새 xhtml_normalizer를 추가하지만, verifier는 여전히 자체 정규화 로직을 유지합니다. 즉,

  • helper가 하나 더 생김
  • verifier는 기존 로직을 계속 가짐
  • 결과적으로 정규화 규칙이 두 군데에서 따로 진화할 수 있음

이 상태에서는 “중복 제거”보다는 “중복 시작점 추가”에 가깝습니다. Phase 0의 목적이 이후 단계에서 신뢰할 수 있는 공용 helper를 만드는 것이라면, 적어도 verifier 또는 실제 사용 경로 하나는 새 helper를 직접 사용하도록 연결되어야 의미가 있다고 봅니다.

914의 좋은 점

1. list tree public API 방향은 좋습니다

ListNode 타입을 명시적으로 노출하고, parse_list_tree()를 public API로 제공한 것은 이후 reverse-sync reconstruction에서 중첩 리스트 처리를 재사용하기 좋은 형태입니다.

2. 테스트 범위도 나쁘지 않습니다

제가 확인한 범위에서는 914 쪽 신규 테스트와 emitter 관련 테스트는 모두 통과했습니다. 그래서 이 PR이 “당장 깨지는 PR”은 아닙니다. 다만 이번 판단 포인트는 테스트 pass/fail보다도 공용 helper로서의 의미 일관성이라고 봤습니다.

916이 더 낫다고 본 이유

916은 변경 범위가 더 작고, 실제 verifier 경로에서 normalization helper를 사용하도록 연결합니다. 즉:

  • 변경 스코프가 작음
  • 중복 정규화 로직을 실제로 줄임
  • code macro plain-text 의미도 현재 reverse-sync 기준과 더 잘 맞음

이 세 가지 때문에 “Phase 0에서 더 안전한 발판”이라는 관점에서는 916이 우세합니다.

다만 916도 그대로 완벽하다고 보지는 않습니다

916의 extract_plain_text()는 top-level에서 첫 번째 meaningful node만 보는 구조라, multi-root fragment에서는 텍스트를 일부만 반환할 수 있습니다. 현재 verify 경로에서는 큰 문제로 안 드러날 수 있지만, 장기적으로는 공용 helper라면 이 부분도 보완하는 편이 맞습니다.

즉 제 제안은 다음과 같습니다.

제안

  • 914를 그대로 채택하기보다는 916을 기반으로 가는 것이 더 안전합니다.
  • 대신 914의 ListNode / parse_list_tree() public API 아이디어는 충분히 가치가 있으므로, 916 위에 별도 commit 또는 후속 PR로 가져오는 방향이 좋겠습니다.
  • 만약 914를 유지하고 싶다면 최소한 아래 두 가지는 먼저 맞춰야 합니다.
    • extract_plain_text()의 code macro 의미를 현재 reverse-sync 기준과 일치시키기
    • verifier가 새 helper를 실제로 사용하게 해서 source of truth를 하나로 만들기

정리하면, 914는 방향은 좋지만 helper 의미론과 실제 사용 경로 통합이 아직 덜 끝난 상태로 보였고, 그래서 지금 시점의 선택지로는 916 쪽이 더 안전하다고 판단했습니다.

- ac:plain-text-body 제외 로직을 제거하여 코드 블록 본문이 plain text에
  포함되도록 수정합니다
- 기존 mapping_recorder의 plain text contract와 일치시킵니다
- reconstruction anchor offset 좌표계에서 코드 본문 누락 방지

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jk-kim0
Copy link
Contributor Author

jk-kim0 commented Mar 13, 2026

code macro plain-text 이슈 수정했습니다 (35fdb0c4).

extract_plain_text() — code macro 본문 포함

  • ac:plain-text-body 제외 로직을 제거했습니다. 이제 코드 블록 본문이 plain text에 포함됩니다.
  • 수정 전: extract_plain_text(code_macro)'python' (파라미터만)
  • 수정 후: extract_plain_text(code_macro)'pythonprint("hello")' (파라미터 + 본문)
  • 기존 mapping_recorder는 code block을 특별 처리하여 본문만 추출(print("hello"))하지만, extract_plain_text는 범용 함수이므로 파라미터 텍스트도 함께 포함됩니다. 핵심인 코드 본문이 포함된다는 점에서 anchor offset contract가 일치합니다.

현재 914의 semantic contract 정리

요소 처리 기존 mapping_recorder와 일치
ac:link + ac:link-body visible label 포함 O
ac:image 제외 (preservation unit) O
ac:emoticon fallback 텍스트 포함 O
ac:plain-text-body (code body) 포함 O
ordered list start ListNode.start 보존 N/A (신규)

전체 테스트 824건 통과 확인.

@jk-kim0 jk-kim0 merged commit 4f56b6e into main Mar 13, 2026
7 checks passed
@jk-kim0 jk-kim0 deleted the jk/phase0-xhtml-normalizer-and-list-tree branch March 13, 2026 13:59
jk-kim0 added a commit that referenced this pull request Mar 14, 2026
… identity helper (#915)

## Description
- reverse-sync 재구성 설계(#913)의 Phase 1 구현입니다.
- `SidecarBlock`에 `reconstruction` 필드를 추가합니다 (kind, old_plain_text,
anchors, items).
- `ROUNDTRIP_SCHEMA_VERSION` "2" → "3" 승격, v2 하위 호환 로드를 유지합니다.
- `build_sidecar()`가 block 타입별 reconstruction metadata를 자동 생성합니다.
- `build_block_identity_index()`, `find_block_by_identity()`: hash +
line_range 기반 disambiguation helper를 추가합니다.

### Phase 1 게이트 충족 항목
- [x] existing sidecar tests green (v2 테스트 11개 수정 후 통과)
- [x] 21개 testcase build + integrity 유지
- [x] schema v3 직렬화/역직렬화 정상
- [x] v2 파일 하위 호환 로드 정상
- [x] duplicate hash disambiguation 동작
- [x] 전체 845 tests pass

## Added/updated tests?
- [x] Yes — `test_reverse_sync_sidecar_v3.py` (25 tests),
`test_reverse_sync_sidecar_v2.py` (v3 호환 수정)

## Additional notes
- 이 PR은 Phase 0 PR (#914) 위에 쌓여 있습니다. Phase 0 머지 후 main으로 rebase 필요합니다.
- reconstruction metadata의 anchors/items는 현재 빈 placeholder입니다. Phase 3에서
실제 anchor 분석이 추가됩니다.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant