내용 대치를 '병합'으로 하면 정렬이 깨진다. 원인이 두 층이고 해결 레이어가 두 갈래라
담당자가 갈린다 — 어디서 깨지는지를 먼저 판정하면 물어볼 사람이 자동으로 정해진다.
그리고 이 문제의 정면 해결은 이미 MOR-2018에서 진행 중이다.
'병합'은 렌더링 장치가 아니다. 검색용 쿼리 트리를 한 단계 접는 변형이고, 접은 만큼 점수를 깎는다. 그림을 고쳐 주는 코드가 아니라 후보를 더 찾아 주는 코드다.
mergeStep병합은 시그니처 트리를 접는 연산이고, 접힌 단계 수만큼 점수에서 빠진다.
/**
* 한 단계 자식 병합. 현재 최대 leaf depth 의 형제 그룹 / 외동 leaf 를
* 모두 한 번에 병합해 하나의 새 forest 를 만든다.
* ...
* 형제 병합할 그룹이 없는 depth ≤1 트리는 null.
*/
// 병합 감점은 root 에 가까운 단계일수록 크게: 1+2+...+step = step(step+1)/2. const mergeWeight = (args.mergeStep * (args.mergeStep + 1)) / 2;
병합은 검색 단계의 연산이라 정렬을 고칠 수단이 애초에 없다. 정렬은 그 뒤 단계(박스 보정 · 재배치)의 일이고, 그 단계에 지금 무엇이 있는지가 §2다.
대치는 렌더러에 ThumbnailLoad 한 덩어리를 넘긴다.
그 안에 구조를 바꾸는 필드가 없다 — 글자(nodeTextMap)와
박스(nodeBboxMap)뿐이다.
return { type: 'COMPONENT_THUMBNAIL', sheetJson, // 후보 원본 — 구조 그대로 nodeTextMap, // 글자 주입 nodeBboxMap, // 박스 보정 (여기가 전부다) siblingGroups: buildSiblingGroups(textMap, sheetJson), ...(rlsc ? { rlsc } : {}), };
| 토글 | 하는 일 | 기본값 |
|---|---|---|
expandText | 텍스트 노드를 형제·부모와 겹치지 않는 선에서 가로 확장 | false |
relieveTopBias | 위로 쏠림 완화 — 글씨 절반만큼 아래로 이동 | true |
ensureBgMargin | 텍스트 박스를 배경 도형 경계 안쪽으로 clamp | true |
tidyLayout 공간 정돈 | force-directed 시뮬레이션으로 box 일괄 조정 — 켜지면 위 셋을 대체 | false |
근거: build-replacement.ts:54-70 ·
즉 정렬 개선 수단(tidyLayout)은 기본으로 꺼져 있다.
"왜 정렬이 안 맞나"의 첫 번째 답이 여기일 수 있다.
박스가 넘치거나 변이 어긋나는 기하 문제인지, 대상이 담을 수 없는 계층이 필요한 구조 문제인지에 따라 손댈 곳이 다르다.
기존 지도에는 ④a·④b 둘뿐이었지만, 실제로는 이 문제를 정면으로 푸는 세 번째 작업이
이미 돌고 있다 — miricanvas-iui/src/post-replace-relayout/ 의 재배치 후보 다섯 개다.
그리고 그 안에서 tidyLayout 은 후보가 아니라 대조군으로 쓰인다.
자세한 것은 §5-B.
같은 단어가 서로 다른 것을 가리켜서, 어느 쪽을 말하는지에 따라 담당이 갈린다.
아래 줄 번호는 고정 커밋 기준이라 링크가 그 시점을 가리킨다. 코드가 움직이면 어긋날 수 있으니, 엉뚱한 곳이 뜨면 커밋을 갱신할 것.
| 무엇 | 파일 · 줄 | 메모 |
|---|---|---|
| 대치 페이로드 조립 | build-replacement.ts:109 | buildContentReplacement() — 여기가 대치의 출구 |
| 박스 보정 토글 | build-replacement.ts:61-70 | tidyLayout 기본 false |
| 공간 정돈 본체 | comp-match/tidyLayout.ts | 815줄 · buildTidyLayoutBboxMap |
| 병합 트리 생성 | comp-match/matching.ts:459 | 한 단계 자식 병합 |
| 병합 감점 | comp-match/scoring.ts:456 | step(step+1)/2 |
| 재배치 후보 · 지표 | src/post-replace-relayout/ | MOR-2018의 구현 대상 · 14,836줄 |
| 컨테이너 판정 | relayout-core/metrics.ts | findTextContainer — 넘침·겹침 전부의 기반 |
| 무엇 | 파일 | 메모 |
|---|---|---|
| 대치 4단계 | buildMappedComponentSheetJson.ts:108 | 내용→폰트→스타일→이미지 |
| SmartBlock 자동 레이아웃 | AbstractSmartBlockLayoutWithChildrenStrategy.ts | Flex · Grid · V1 세 구현 |
| 캔버스 반영 | ComponentItemReplaceCommand.ts | 대치의 종착점 |
⚠ 렌더러(텍스트 흘리기 · 폰트 오토핏 · 재레이아웃)는 우리 통제 밖이다.
끄는 레버는 shrinkConfigs 하나뿐이고, 줄바꿈·재레이아웃은 그래도 일어난다.
이 문제에 직접 닿는 코드는 없다 — RLSC(structure_json)를 생산하는 쪽이라
대치·정렬 런타임에는 관여하지 않는다. 다만 대치의 판단 근거인 Component RLSC가 여기서 나오므로,
"구조가 애초에 잘못 잡혔나"를 의심할 때 거슬러 올라가는 지점이다
(RLSC 생성 여정).
담당을 가르는 질문(A)과, 그와 별개로 이미 돌고 있는 근본 해결 작업(B)을 나눠 적는다.
B는 MOR-2018 의 조사·구현 문서에서 가져왔고, 아래 ↻ 표시가 붙은 표는
원본이 바뀌면 sync-sources.py 로 다시 채워진다.
병합 · 대치 · 정돈 갈래(④a)는 홍지혜가 맡는다.
이석님 Pod 이 이미 만들어 둔 접근(tidyLayout · comp-sign 스마트블록 대치)은
「기존 접근 방법」 절로 뺐고 참고만 한다 —
전제로 삼지 않는다. 실제 해결 경로는 아래 B 다.
확인 결과 생성엔진 영역이면 AI연구팀으로 넘긴다. ailabs SmartBlock 생성기 소관일 때만.
우선순위·PRD는 내용시각화 스쿼드가 갖는다 — 김수현(스쿼드 리더) + 정원석(PM-E), 창석님은 요청자.
문제가 나는 지점(병합 · 대치 · 정돈)은 홍지혜가 맡는다. 이석님 Pod 의 기존 구현은 참고 자료이고, 구조 재배치가 필요할 때만 주영님.
"내용 대치가 끝난 컴포넌트를 ① 넘침 ② 과도 축소 ③ 여백 낭비가 없도록 재정렬한다." 정확히 이 페이지의 문제이고, 후보 다섯 개가 이미 구현되어 돌고 있다.
후보 다섯을 다 돌리고도 남는 FAIL 을 글자를 줄여 풀 수 있나, 줄인다면 어디서 멈춰야 하나. 커밋된 119건 실측과 하한 기준의 결정 (「원안 대비 배율」 vs 「페이지 대비 크기」)을 그 페이지에 따로 뒀다.
정답(expected_result.json)이 스크립트 산출물이라
알고리즘이 같은 절차를 쓰면 채점이 순환한다. 커밋 a0cb02aa 에서
손으로 만든 정답이 스크립트 산출물로 덮였다.
현재 코드는 (b)에 가깝게 이동했다 — README가 채점 기준을
original.json(대치 이전 원안)으로 바꿨다. 문서(w3 §5)와
코드의 결론이 갈리는 지점이라 확인이 필요하다.
method1 — 세로 공간 재분배. 폭 고정이라 예측 0method2 — 가로 확장. 줄 수를 줄인다 · 줄바꿈 예측이 들어간다method3 — 가로 넘침 해소. 도형을 좌우로 키우고, 막히면 텍스트를 안쪽 폭으로 좁힌다method4 — 대치가 만든 겹침 해소 (유닛 사이) · 크기 고정 · 예측 0method5 — 유닛 안의 겹침 해소. 한 배경 도형 안에서 글자끼리 포개진 것
읽는 법 — 넘침을 줄이는 일은 합본 3+1+2 에서 끝난다
(4,107,470 → 534,454px², 87%). 게이트(신규 겹침)는 +4·+5+4 가 따로 푼다.
tidy 열이 대조군이다 — 게이트 34/119로 before(35)보다도 낮다.
| 열 | 넘침 노드(H/V) | 넘침 면적 | 게이트 PASS | 폰트 배율(평균) | 정렬선(평균) |
|---|---|---|---|---|---|
original(기준) | 45 (39/11) | 1,086,451px² | 기준 | 1.000 | 1.000 |
before | 139 (48/111) | 4,107,470px² | 34/111 | 1.000 | 0.932 |
method1 | 67 (48/26) | 2,607,978px² | 40/111 | 1.000 | 0.913 |
method2 | 111 (48/84) | 3,204,852px² | 36/111 | 1.000 | 0.891 |
method3 | 117 (12/112) | 3,315,905px² | 35/111 | 1.000 | 0.927 |
합본 1+2 | 45 (43/6) | 1,816,735px² | 42/111 | 1.000 | 0.894 |
합본 3+1+2 | 14 (12/5) | 534,454px² | 43/111 | 1.000 | 0.895 |
합본 3+1+2+4 | 14 (12/5) | 534,454px² | 71/111 | 1.000 | 0.885 |
합본 3+1+2+5+4 | 14 (12/5) | 534,454px² | 83/111 | 1.000 | 0.864 |
합본 3+1+2+5+4+6 | 11 (9/4) | 368,345px² | 97/111 | 0.982 | 0.861 |
tidy | 126 (72/98) | 4,897,172px² | 34/119 | 1.000 | 0.900 |
expected(목표선) | 39 (25/24) | 699,729px² | 60/90 | 0.993 | 0.849 |
↻ post-replace-relayout 후보별 실측 — 원본에서 자동 동기화
예비 실측에서 커밋된 7건 전부가 세로 여유만으로 부족분을 덮었다 —
가로 넘침 0건. 그래서 method2 는 method1 이 실패하는 케이스를
찾은 뒤에 착수한다는 것이 원래 계획이었다.
(실제로는 method3 이 필요해지면서 계획이 한 번 갈렸다 — B-1 참고)
| # | 항목 | 어디에 | 규모 |
|---|---|---|---|
| 0 | 채점 방식 결정 (§5) | — | 먼저. 이게 없으면 비교 자체가 무의미 |
| 1 | tight box를 판정 기준으로 붙이기 | 공용 | 2일 |
| 2 | 위상 동결 — 관계 그래프 추출 | 공용 | 3일 |
| 3 | Abacus PlaceRow + 스택 추출 | 공용 | 4일 |
| 4 | 여백 대수(1D, 닫힌 형식) | method1 | 2일 |
| 5 | 최대 빈 사각형 열거 | method1 (+ 합본의 분기 기준) | 3일 |
| 6 | method1 완성 → 5건 채점 | — | |
| 7 | DocIframe이 pageJson 반환하도록 수정 | 공용 | 1일 |
| 8 | 최소구성 + 자기 캘리브레이션 | method2 — 조건부 | 4일 |
| 9 | 제약 그래프 여유 + 실현가능성 | method2 — 조건부 | 1주 |
| 10 | method2 완성 → 채점 → §4.3 판정 | — |
↻ 착수 순서 — 원본에서 자동 동기화
이 중 #1 채점 방식은 B-0이고, #6 폰트 복원·#7 행간 변경은 제품 결정이라 엔지니어링으로 못 넘긴다 — B-4.
| # | 항목 | 무엇을 막나 | 상태 |
|---|---|---|---|
| 1 | 채점 방식 결정 (§5의 a/b/c) | 정답이 스크립트 산출물이라 지금은 채점이 성립하지 않는다. 다른 모든 작업의 전제 | 최우선, 방법론 결정 |
| 2 | wrapBlock 오차 2건(78/80)의 정체 — CJK 금칙? 긴 라틴 토큰? | 4위 전체의 전제 | 미측정 |
| 3 | tight box 굽기의 프로덕션 비용 (headless Chrome, 문서당) | 5위 [전] 적용 가능 여부 | 미측정 |
| 4 | 여백 균등 분배 vs 비율 분배 | 2위의 유일한 설계 미결 | 케이스로 확인 가능 |
| 5 | 렌더러가 실제로 무엇을 바꾸나 — 특히 컨테이너를 키우면 폰트를 되돌리나 | 5위 [후]의 "뜻밖의 이득" 가설 | pageJson을 버리고 있어 아무도 안 봄 |
| 6 | 폰트 복원 권한 (original.json 사용 허용) | §6.1 — 문제유형 ②를 다룰 수 있느냐 | 제품 결정 |
| 7 | 행간(lineSpacing) 변경 허용 | §6.2 — 가장 싼 세로 회수 수단 | 제품 결정 |
| 8 | 장식 요소 필터링의 정확도 | 3위의 거짓 양수 사이클 | DECORATION_TYPES 재사용 검증 |
| 9 | 기요틴 분해율 (샘플 ≥500) | §6.4 계측기 착수 | w2에서 이월, 미측정 |
| 10 | 케이스를 7건보다 늘려 method1이 실패하는 사례 찾기 | §4.3.1 실측이 7건 전부 "세로로 해결"이라 method2를 만들 근거가 아직 없다. 반례가 없으면 만들지 않는다 | 미착수 |
| 11 | case.json의 problems 라벨 채우기 (7건 중 6건이 비어 있음) | 케이스가 어떤 증상인지 사람이 분류한 기록이 없어 교차 검증이 불가능 | 사람이 이미지를 봐야 함 |
↻ 남은 확인 필요 항목 — 원본에서 자동 동기화
⚠ 테스트가 0개다 — wrapBlock·boxHeightOf·rendererLineCount 는
method 4·5위의 기반이고 findContainer·overflowOf 는 전부의 기반이라,
여기가 조용히 깨지면 전부 깨진다. (문서 기준. 현재 코드에는
relayout-core/ 아래 테스트 파일이 다수 있어 재확인 필요)
original.json(대치 전 원안)의 글자 크기로 되돌려도 되나.
허용하지 않으면 문제유형 ②(과도 축소)를 아예 다룰 수 없다.
lineSpacing) 변경 허용 — 현행 규칙(before-after-rules.md §2)이 금지한다.
하지만 행간 −20%는 세로 20%를 회수하면서 글자 크기 손실이 0이라
가장 싼 세로 회수 수단이다 (폰트 0.894와 동일한 면적 효과).
이 둘은 A-2(김수현·정원석) 이 답할 항목이다 — 엔지니어링 선택이 아니라 품질 정책이다.
tidyLayout(force-directed)을 후보가 아니라 대조군으로 만든 이유다.tidyLayout 실측에서 5건 중 2건이 신규 겹침 게이트 FAIL,
1건은 중앙 오차 26.1 → 87.4. 소프트로 다루면 안 된다는 우리 데이터.| 날짜 | 구분 | 항목 | 내용 | 반영 위치 |
|---|---|---|---|---|
| 2026-08-20 | 확정 | Method 1 착수 승인 | w2 §3 "크기·위치 동시" 원칙과 모순 없음. 폭 동결 시 높이는 자유변수가 아니라 실측값 — 최소 섭동 목적 하에서 h_i* = h_실측,i가 조인트 최적임이 증명됨(h를 더 키우면 자기 페널티↑ + y 실현가능 영역↓). 잔여 (y, θ) 조인트 QP는 PlaceRow/여백대수가 정확해를 냄. Method 1 = 예측 0 부분공간으로 제한한 조인트 솔브이지 grow-then-move 휴리스틱이 아님. w2의 경고는 폭이 자유변수여서 피드백 루프가 있을 때의 얘기(→ Method 2 영역) | w3 §4 |
| 2026-08-20 | 확정 | 갭 분배는 델타 균등(닫힌 형식) | method1 노트 §5 장난감 예제의 완전 균등(55×4)과 w3 2위 닫힌 형식 θ_j = θ^orig + Δ/m(→ 25/45/25/125)이 불일치. 후자가 디자이너 여백 위계를 보존하므로 구현 기준. 완전 균등화는 idle-margin용 별도 플래그로 분리 채점 | w3 2위 |
| 2026-08-20 | 권고 | 착수 순서 #7을 #6 앞으로 | Method 1의 "예측 0"에 숨은 피드백 채널 = 렌더러 오토핏(박스 확대 → 폰트 복원 → 텍스트 높이 변화). h_실측은 현재 fontSizeScale에서 잰 값이라 최종 렌더와 어긋날 수 있음(w3 §8-5 "아무도 안 봄"). pageJson 회수(1일)를 m1 채점 전에 넣어 5건 채점을 render-verified로. "뜻밖의 이득"(증상 ② 간접 해결) 가설도 같은 실험에서 확인됨 — m1 vs m2 판정에 영향 | w3 §4.4, §8-5 |
| 2026-08-20 | 권고 | m1 1차 채점은 보수 모드 | 확대 + 법제화 + 델타 분배만. 미학적 채움·전면 균등화 제외 — §4.3 축 판정의 귀속(attribution) 보전 | w3 §4.3 |
| 2026-08-20 | 권고 | 배경 도형은 이동 결합이 아니라 크기 결합 | 텍스트 박스가 자라면 배경 도형도 padding 보존으로 커져야 Rule.md §0 overlap 요구 유지. 재줄바꿈 없으므로 예측 0 | w3 1위 필요조건 |
↻ Method 1 착수 전 재검토 결정 — 원본에서 자동 동기화
이 아카이브 표시가 붙은 둘은 이 사이트로 옮겨 왔다 —
사이드바에서 이 문서의 하위로 들어가 있고, 슬라이더·시뮬레이터도 원본 그대로 돈다.
나머지는 MOR-2018/ 안의 원본이다.
| 문서 | 무엇 |
|---|---|
Rule.md | Before/After 정의 규칙 · 불변 조건 · 문제 유형 ①②③ |
research/relayout-research_w2.md | 문헌 조사 정본 · 결정 로그(§10) |
research/relayout-research_w3.md | 순수 알고리즘 Top 5 · 조립 계획(§4) · 남은 항목(§8) |
post-replace-relayout_method1-method2-notes.md | Method 1/2 직관 설명 · 장난감 예제 |
logics/find-text-container.md | findTextContainer 판정 3순위 해부 |
logics/overlap-area-map.md | overlapAreaMap · 신규 겹침 게이트 |
| overlapAreaMap 겹침 게이트 이 아카이브 | 지표 ⑥ 의 2단계 알고리즘 · 제외 규칙 다섯 · 판정 시뮬레이터. 원본 overlap-gate-notes.html |
TC/ · 예시/ | 테스트 케이스 sheetJson · Before/After 예시 이미지 |
logics/ 의 로직 문서는 사내에 배포되어 있다 —
post-replace-relayout-key-func.miridih.app (VPN).
아래는 이석님 Pod 이 이미 구현해 둔 접근이다. 담당이 홍지혜로 넘어왔으므로 이번 작업의 전제로 삼지 않는다 — 무엇이 이미 있고 어디까지 커버되는지 알아 두는 용도로만 읽는다.
실제 해결 경로는 §5-B(post-replace-relayout) 다.
그 안에서 tidyLayout 은 후보가 아니라 대조군으로 쓰인다.
각 노드의 bbox 네 변을 1D Verlet 입자로 두고, 아래 힘을 합산해 240회 적분한 뒤 최종 박스만 낸다. 결정론적이지 않고, 비겹침을 하드 제약으로 다루지 않는다 — 이 성질이 §5에서 문제가 된다.
| 힘 | 내용 |
|---|---|
| 텍스트 확장 단방향 | 예측 높이가 현재 박스보다 크면 verticalAlign 방향으로 확장. 작으면 그대로 |
| 텍스트 가로 확장 단방향 | 세로로 겹치는 형제·부모 경계 안에서 horizontalAlign 방향으로 펼침 (WIDE_PADDING=24) |
| 배경 확장 단방향 | 자식이 커져 여백이 좁아지면 목표 여백(BG_MAX_MARGIN=24)까지 배경 변을 바깥으로 |
| 가이드 선 유지 | 시작 시 같은 직선(GUIDE_TOL=4)에 있던 변들을 묶어 평균으로 당김. 부모-자식은 서로 영향 없음 |
| 겹침 방지 | 형제끼리 겹치면 최소 침투축으로 밀어냄. 시작 시 이미 겹쳐 있던 쌍은 제외 |
| 여백 균형 | 배경 컨테이너의 자식 텍스트에 한해 상하 여백이 같아지도록 세로 평행이동 (크기 보존) |
| 페이지 제한 hard | 변을 페이지 안으로 clamp. 최초부터 밖이던 입자는 예외 |
ITERATIONS=240 · DAMPING=0.6 ·
comp-match/tidyLayout.ts:13-46
④a(기하적 정렬)가 tidyLayout 커버 범위인지, comp-sign '스마트블록 대치'로
④b(구조적 재배치)까지 가능한지 확인.
tidyLayout 을 켜고 있나? 기본값이 false다(§1).