# Visual Summary: 내용이 디자인에 들어가기까지

<!-- DOC:AUTHOR -->
갱신 2026-10-18 · 저자: 홍지혜(jhhong)
<!-- /DOC:AUTHOR -->

> **이 문서의 역할** Visual Summary를 처음 배우는 사람을 위한 강의자료다. 고등학생 수준의 독해를 전제로 용어, 명세, comp-frame 코드를 연결한다.
> **오너** 미지정 · **상태** DRAFT · **최종 갱신** 2026-10-17
> **SSoT는 여기가 아니다.** 규칙은 [스키마](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md), [capability](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md), [기계 판독 표](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema/capability.json)를 따른다.
> **같이 쓰는 자료** [퀴즈 50문항과 해설](quiz.md) · [학습 페이지](learning.html)

## 0. 먼저 질문에 답한다

| 질문 | 답 |
| --- | --- |
| tier는 YAML의 타입으로 찾는 것인가? | **tier 1**이 그 역할이다. tier 전체는 검색할 카테고리를 우선순위로 묶은 것이다. 현재 코드는 tier 2에 정해진 fallback 카테고리, tier 3에 마지막 대안을 둔다 |
| alternatives도 tier에 포함되는가? | 명세에는 사용자 내용에 따른 대안으로 존재한다. **현재 comp-frame 검색은 무시한다.** 나중에 어느 tier로 넣을지는 현재 구현으로 단정하지 않는다 |
| fallback은 검색 결과가 없으면 비슷한 종류를 찾는 것인가? | 대체로 맞다. 다만 비슷해 보이는 종류를 임의로 고르지 않고 정해진 순서를 따른다. 성립 조건 미충족이나 대치 가능한 후보 부족도 진행 이유가 된다 |
| projection은 무엇인가? | **다른 표현으로 바뀌어도 내용을 담을 수 있게 데이터 모양이나 대응을 바꾸는 작업**이다. 카테고리를 바꿀 때 하는 structure projection과 표현 속성을 맞추는 property projection을 구분한다 |
| IIS boundary와 property는 어떻게 연결되는가? | 경계가 먼저 '어느 묶음이 무슨 종류인가'를 정한다. 그 경계를 대상으로 I.I.S. (Semantic Component Property)가 표현 속성과 구조 대응을 구한다 |
| IIS property가 없으면 YAML이 안 들어가는가? | 무엇이 없는지에 따라 다르다. 표현 속성 `properties`가 없는 것과 글상자 대응표 `structure`가 없는 것은 다르다. 현행 코드에는 대응표가 없을 때 규칙으로 Frame을 만드는 보완 경로도 있다 |

출처: [코드 C1](#c1), [C3](#c3), [C5](#c5), [C7](#c7), 명세 5절과 6.1절.

### 읽는 순서

1. 1~3강에서 YAML과 디자인의 차이를 이해한다.
2. 4~6강에서 tier, fallback, projection을 구분한다.
3. 7~9강에서 I.I.S.와 내용 대치를 연결한다.
4. 10~12강에서 실제 코드와 예외를 확인한다.
5. 퀴즈를 풀고 틀린 문항의 복습 절로 돌아온다.

예제의 학교 행사와 노드 번호는 **설명용 가상 데이터**다. 실제 검색 결과나 추출 정확도를 뜻하지 않는다.

## 1. Visual Summary는 무엇을 만드는가

사용자가 다음과 같이 입력했다고 하자.

> 과학 발표회는 3월에 주제를 정하고, 4월에 실험하며, 5월에 결과를 발표한다.

이 글에는 내용과 표현 선택이 함께 필요하다. 내용은 '주제 선정, 실험, 발표'와 각 시기다. 표현은 시간의 흐름이 보이는 `Timeline`으로 할 수 있다.

**Visual Summary는 이 내용을 검색과 대치가 읽을 수 있는 구조로 적은 결과물이다.** 완성된 이미지나 디자인 파일은 아니다. 글상자의 좌표와 글꼴을 전부 생성하는 문서도 아니다.

```text
사용자 글
   ↓ 내용과 표현 선택을 구조화한다
Visual Summary YAML
   ↓ 내용을 받을 수 있는 디자인 후보를 찾는다
후보 디자인 + 어느 글을 어디에 넣을지 정한 대응
   ↓ 텍스트와 데이터를 대치하고 렌더링한다
화면에 보이는 결과
```

여기서 **검색 성공과 대치 성공은 다르다.** 'Timeline'이라는 이름이 맞아도 글상자가 두 개뿐이거나 글이 너무 길면 원하는 결과를 만들지 못한다. 그래서 실제 검색 코드는 후보에 내용을 맞춰 본 결과까지 검사한다. [S1](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md), [C3](#c3), [C6](#c6).

## 2. YAML을 한 줄씩 읽는다

```yaml
version: "0.5.6"
title: 과학 발표회 준비
components:
  - type: TimelineSchedule.Timeline
    params:
      orientation: horizontal
    structure:
      entities:
        - label: 주제 선정
          period: 3월
          items: [조별로 주제를 정한다]
        - label: 실험
          period: 4월
          items: [결과를 기록한다]
        - label: 발표
          period: 5월
          items: [포스터를 발표한다]
```

YAML은 사람이 읽기 쉬운 데이터 표기법이다. 들여쓰기는 포함 관계를, `-`는 목록의 항목을 나타낸다. `components`는 여러 컴포넌트 블록의 목록이고, 이 예제에는 하나만 있다.

| 필드 | 읽는 말 | 예제에서의 뜻 |
| --- | --- | --- |
| `version` | 어떤 규약으로 썼나 | 예제는 0.5.6 표기를 쓴다 |
| `title` | 이 문서의 제목은 무엇인가 | 과학 발표회 준비 |
| `type` | 어떤 종류로 표현하고 싶은가 | Timeline |
| `params` | 그 종류의 표현 방식을 어떻게 할까 | 가로 방향 |
| `structure` | 실제로 넣을 내용은 무엇인가 | 세 항목과 날짜, 설명 |
| `entities` | 항목들이 무엇인가 | 주제 선정, 실험, 발표 |
| `label` | 항목 이름 | 실험 |
| `items` | 항목의 상세 내용 | 결과를 기록한다 |
| `period` | 시기 | 4월 |

**'4월'은 내용이고, '가로 방향'은 표현 선택이다.** 날짜를 빼면 전달할 사실이 줄어든다. 가로 방향을 세로 방향으로 바꿔도 세 사건과 날짜는 그대로 적을 수 있다. 출처: [스키마 2절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md), [Timeline capability](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md).

### type은 필수이지만 최종 결과를 강제하지 않는다

명세는 생성기가 `type`을 반드시 적도록 한다. 다만 검색에서 성립하지 않거나 쓸 후보가 없으면 fallback으로 바꿀 수 있다. '반드시 적는다'와 '반드시 그대로 쓴다'는 다른 규칙이다.

`Hierarchy.Pyramid`처럼 대분류와 중분류를 합친 값을 적는다. 명세상 `Hierarchy`만 적는 것은 올바른 type이 아니다. 코드 내부의 옛 호환 처리가 더 넓은 입력을 받아도 명세가 허용한다는 뜻은 아니다. [스키마 6절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md).

### 글 목록 외에 무엇을 적을 수 있나

| 구조 | 무엇을 표현하나 | 작은 예 |
| --- | --- | --- |
| `entities` | 항목별 내용 | 주제 선정, 실험, 발표 |
| `entity.children` | 같은 내용 구조 안의 하위 항목 | 과학부 아래 물리팀과 화학팀 |
| `entity.components` | 항목 안에 별도 종류의 컴포넌트 | 팀별 카드 안에 각 팀의 Steps |
| `dataset.shape: measure` | 단일 수치와 단위, 목표 등 | 참여율 80% |
| `dataset.shape: matrix` | 열 정의와 행으로 된 데이터 | 반별 참여 인원 표 |
| `relations` | 항목 사이의 추가 관계 | 두 집합의 교집합 설명 |
| `columns` | entities로 표를 표현할 때의 열 정보 | 항목, 담당자, 기한 |

`measure`와 `matrix`는 수치 데이터의 서로 다른 형태다. '80%' 하나를 표현하는 것과 '1반 20명, 2반 25명, 3반 18명'을 비교하는 것은 다르다. `relations`는 항목 사이의 관계이며, 단순한 순서는 보통 배열 순서로 나타낸다. 모든 관계를 params에 적는 것이 아니다.

`decoration`은 내용과 별개로 꾸밈 선호를 나타낸다. `ornament`와 `background`에 `prefer`, `indifferent`, `avoid`를 쓸 수 있다. 배경을 피하고 싶다는 뜻과 기존 디자인에 실제 배경이 있다는 사실도 구분한다. [스키마 2절과 2.7절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md).

### 카테고리와 그림의 겉모양도 구분한다

같은 Steps라도 가로로 놓거나 세로로 놓을 수 있다. RLSC의 `HStack`과 `VStack`은 물리 배치이고, 의미 카테고리와 같지 않다. 또한 차트처럼 보이는 도형 묶음과 실제 데이터가 연결된 차트 요소는 다르다. 기존 그림의 글을 바꿀 수 있어도 수치에 맞춰 막대 길이를 바꿀 수 있다는 뜻은 아니다.

그래서 **스키마 어휘에 있는 카테고리와 생성기에 허용한 카테고리는 다를 수 있다.** 예를 들어 capability의 `generatable` 제약은 현재 대치로 지원할 수 있는 범위를 고려한다. 전체 어휘를 외우기보다 해당 카테고리의 성립 조건과 생성 허용 여부를 확인한다. [capability 10절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md).

## 3. 사용자 내용과 기존 디자인은 서로 다른 데이터다

검색에는 두 쪽의 정보가 필요하다.

| 사용자 내용 쪽 | 기존 디자인 쪽 |
| --- | --- |
| 넣고 싶은 글과 숫자 | 실제 글상자와 표, 도형 |
| `type`: 원하는 종류 | `categories`: 추출한 종류 |
| `params`: 원하는 표현 속성 | `properties`: 디자인에서 확인한 표현 속성 |
| `structure`: 글과 수치 자체 | I.I.S.의 `structure`: 어느 노드가 어떤 필드인지 나타내는 구조 대응 |
| 예: `label: 실험` | 예: `label: text-21` |

**같은 `structure`라는 이름이라도 오른쪽은 내용이 아니라 내용을 넣을 위치에 대한 정보다.**

`text-21`은 기존 디자인의 글상자를 구별하는 식별자다. 실제 시스템에서는 RLSC 노드 ID와 디자인의 sheet 요소 ID를 구분하기도 한다. RLSC는 기존 디자인의 배치와 역할을 트리로 나타낸 데이터다. 화면에서 사각형으로 보이는 글상자 하나를 프로그램이 정확히 지정하려면 이 식별자가 필요하다.

현재 comp-frame은 두 쪽을 공통 형식인 **Frame**으로 바꾼다. 질의 Frame은 슬롯에 넣을 글을, 컴포넌트 Frame은 슬롯의 노드 위치와 글 용량을 담는다. '슬롯'은 여기서 특정 종류의 내용을 받을 자리다. [용어 SSoT](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/README.md), [C2](#c2), [C5](#c5).

## 4. tier는 검색할 카테고리의 우선순위 묶음이다

현행 Rust `build_tiers_for`를 기준으로 보면 다음과 같다.

| 군 | 어떤 카테고리를 넣나 | 주의할 점 |
| --- | --- | --- |
| tier 1 | YAML의 `type` 중 성립하는 것 | 현재 `alternatives`는 넣지 않는다 |
| tier 2 | 원래 type의 fallback 행에 적힌 카테고리 중 변환 후 성립하는 것 | 원래 행의 순서를 유지한다 |
| tier 3 | 내용 모양으로 고른 마지막 대안 하나 | 앞 군에 있으면 중복으로 추가하지 않는다 |

마지막 대안은 `List`, `LongFormTextLayout`, `HighlightedMessage` 중 내용에 맞는 하나다. 보통 항목이 둘 이상이면 List, 하나이고 여러 문장이면 LongFormTextLayout, 하나이고 한 문장이면 HighlightedMessage를 고르는 규칙이다. dataset만 있는 입력 등의 세부 처리는 `terminal_category`와 성립 검사를 함께 봐야 한다.

### 학교 행사 예제

원래 type은 Timeline이고 항목은 세 개다. capability에 있는 Timeline의 fallback 행은 다음과 같다.

```text
Timeline → Steps → Staircase → List
```

각 후보가 성립한다면 다음 검색 군이 만들어진다.

```text
tier 1: Timeline
tier 2: Steps, Staircase, List
tier 3: 없음 (마지막 대안 List를 tier 2에서 이미 포함했다)
```

따라서 **tier 2가 fallback 한 번, tier 3가 fallback 두 번이라는 뜻은 아니다.** tier 2 안에도 여러 카테고리가 있다. 어떤 입력에서는 tier 1 자체가 없을 수 있다. [C1](#c1), [capability 3.2절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md).

### alternatives의 현재 위치

명세 6.1절의 `alternatives`는 생성기가 **이번 내용**을 보고 고른 대안이다. capability fallback은 카테고리별로 미리 정해 놓은 순서다. 명세는 alternatives를 정적 fallback보다 먼저 참고하도록 설명한다.

반면 현재 `comp-frame`은 alternatives를 파싱할 수 있지만 검색 군을 만들 때 읽지 않는다. 해당 코드에는 2026-09-10 사용하지 않기로 한 지시가 기록돼 있다. **문서에 필드가 있다는 것, 코드가 읽는다는 것, 나중에 넣을 예정이라는 것은 별개의 주장이다.** 이번 자료는 미래 구현 순서를 확정하지 않는다. [스키마 6.1절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md), [C1](#c1).

## 5. fallback은 왜 필요한가

fallback은 원하는 표현을 사용할 수 없을 때 정해진 다른 표현을 시도하는 과정이다. 이유를 세 가지로 나누면 이해하기 쉽다.

| 상황 | 예 | 어디에서 판단하나 |
| --- | --- | --- |
| 내용이 그 종류의 성립 조건을 못 채운다 | 항목 하나로 Steps를 요청한다. Steps는 둘 이상이다 | capability 성립 검사 |
| 조건은 맞지만 검색 풀에 쓸 디자인이 없다 | 세 단계 요청은 맞지만 대응하는 후보 공급이 없다 | 후보 검색 |
| 후보는 있지만 실제 대치가 부적합하다 | 세 항목은 맞지만 글이 넘치거나 내용이 손실된다 | align, 용량 검사, 속성 정책 |

현행 코드는 **실제 검사를 통과한 후보 수**로 다음 tier까지 갈지 판단한다. 검색 스냅샷에서 100개를 읽었어도 전부 탈락하면 충분한 후보를 확보한 것이 아니다. 반대로 tier 1 후보가 몇 개 있더라도 중단 기준에 모자라면 tier 2를 찾아볼 수 있다.

기본값은 `min_candidates: 500`, `tier_targets: [50, 50]`, `tier1_max_candidates: 0`이다. **첫 번째로 실제 존재하는 tier는 기본적으로 하드 필터 통과 행을 전량 검사한다.** 여기서 0은 승인 상한 없음이다. `min_candidates`와 카테고리 ×4 스캔 예산은 그 다음 tier부터 적용한다. 이름은 tier1 옵션이지만 tier 1이 비어 있으면 첫 존재 tier가 이 취급을 받는다. 이 값은 명세의 의미 규칙이 아니라 현재 코드의 검색 설정이다. 예를 들어 tier 1 처리를 마친 시점에 통과 후보가 20개라면 기본 하강 기준 50개에 못 미친다. 다음 tier를 시도할 수 있다. **정확히 50개만 조회한다는 뜻은 아니다.** 한 tier 안의 조회가 끝나거나 다른 중단 조건에 걸린 뒤 하강 여부를 판단한다. [C3](#c3), [C8](#c8).

### '유사 카테고리'는 느낌으로 고르지 않는다

원래 type의 fallback 행을 왼쪽부터 소비한다. Timeline에서 Steps를 시도했다고 해서 Steps 행으로 갈아타는 식으로 계속 확장하지 않는다. '얼핏 비슷함'만으로 표를 게이지로 바꾸지도 않는다. 예를 들어 `measure`와 `matrix` 사이에는 임의 변환 규칙을 두지 않기로 했다. 여러 수치를 하나로 줄이면 정보를 잃기 때문이다. [C1](#c1), [capability 3~4절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md).

## 6. projection은 표현이 바뀔 때 내용을 어디로 옮길지 정한다

Timeline을 Steps로 바꾼다고 하자. type 이름만 바꾸면 끝날까?

Timeline은 `period`를 읽지만 Steps는 날짜 전용 필드를 읽는다고 선언하지 않았다. 그래서 아래처럼 이름만 바꾸면 날짜가 사용되지 않을 수 있다.

```yaml
# 바꾸기 전, Timeline의 내용
label: 실험
period: 4월
items: [결과를 기록한다]
```

structure projection은 날짜를 읽을 수 있는 자리로 옮긴다.

```yaml
# Steps로 가는 projection 뒤의 내용, 설명용 예시
label: 실험
items:
  - 결과를 기록한다
  - "period: 4월"
```

날짜 전용 배치는 포기했지만 **'4월'이라는 사실은 남았다.** fallback은 'Steps도 시도하자'라는 선택이고, projection은 'Steps가 받을 수 있게 날짜를 items에 옮기자'라는 변환이다. [capability 3.4절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md), [C4](#c4).

### structure projection 규칙

| 규칙 | 쉬운 설명 | 보존하는 것 또는 포기하는 것 |
| --- | --- | --- |
| P1 | 항목과 그 순서를 기본적으로 유지한다 | 기본 내용 구조 |
| P2 | 도착 카테고리가 읽지 않는 params를 제거한다 | 사용할 수 없는 표현 선택을 포기한다 |
| P3 | 읽지 못하는 내용 필드를 items의 글로 옮긴다 | 날짜, 작성자, 하위 내용 등의 사실 |
| P4 | 표 데이터를 행별 항목과 설명으로 바꾼다 | 행과 열의 내용을 텍스트로 남긴다 |
| P5 | 단일 수치 데이터를 entity의 수치와 단위로 옮긴다 | 값과 단위 |
| P6 | 항목과 설명을 표의 행과 열로 바꾼다 | 항목별 내용 |
| P7 | 그릴 수 없는 관계의 설명을 관련 항목으로 옮긴다 | 관계가 가진 텍스트 정보 |

규칙들은 적용 조건에 따라 함께 작동한다. P1이 있다고 해서 'children은 어떤 도착지에서도 절대 변환하지 않는다'로 읽으면 안 된다. 읽지 못하는 필드에 대한 P3 등이 함께 적용된다.

**P8은 폐기됐다.** 카테고리의 개수 상한을 넘는 항목을 마지막 항목에 억지로 접어 그 카테고리를 성립시키는 규칙이다. 현재 명세는 그 카테고리를 탈락시키고 다른 후보를 시도한다. 후단 align에서 상세 글을 한 슬롯에 합치는 것까지 전부 금지했다는 뜻은 아니다. [capability 3.4절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md), [C6](#c6).

### property projection은 무엇이 다른가

structure projection이 내용 형태를 바꾸는 동안, property projection은 요청한 표현 속성과 후보의 표현 속성을 맞추는 문제를 다룬다.

예를 들어 요청한 Staircase의 `direction`이 `ascending`이고 후보가 `descending`이면, 그 종류에서 정의한 순서 의미에 맞춰 채움 순서를 뒤집는 방안을 검토할 수 있다. 반대로 가로 배치를 세로 배치로 만드는 것은 순서만 뒤집어서 해결되지 않는다. 모든 속성 차이에 같은 처리를 할 수 없다.

현행 코드의 속성 판정부에는 다음 정책이 있다.

| 정책 | 판정부의 동작 |
| --- | --- |
| `exclude` | 명시적인 속성 충돌이 있으면 후보를 제외한다 |
| `reorder` | 정의된 반대 순서 쌍이면 `reverse` 표시를 만들고, 나머지 충돌은 결함으로 센다 |
| `ignore` | 후보를 유지하면서 충돌을 결함으로 센다 |

**판단과 실제 대치 실행은 별도 단계다.** 최신 Rust의 `cost_of`는 `reverse`를 계산하고 결과에 싣는다. `align`은 그 전에 binding을 만들며 현재 클라이언트 대치는 그 binding을 사용한다. 따라서 `reverse: true`만으로 화면의 텍스트 순서까지 뒤집혔다고 판단하지 않는다. 코드의 표시와 소비자를 함께 확인한다. [C6](#c6), [C7](#c7), [C9](#c9).

### 같은 단어의 다른 사용도 있다

`projectForBoundaryExtraction`의 projection은 LLM이 경계를 판단할 때 필요한 RLSC 정보만 남기는 입력 정리다. fallback 변환과 다르다. 함수 이름에 projection이 있으면 **입력, 출력, 호출 위치**부터 확인한다. [C7](#c7).

## 7. I.I.S. boundary가 알아내는 것

기존 디자인에 다음 글상자가 있다고 하자.

```text
group-A
 ├─ text-11: 준비
 ├─ text-12: 계획을 세운다
 ├─ text-21: 실행
 ├─ text-22: 행동한다
 ├─ text-31: 점검
 └─ text-32: 결과를 본다
```

I.I.S. (Semantic Component Boundary)는 이 중 **어느 노드 묶음이 하나의 의미 컴포넌트이며 어떤 카테고리에 해당하는지** 판정한다. 예를 들어 'group-A가 Steps 경계'라는 결과를 낼 수 있다.

경계는 화면의 사각형 좌표만 뜻하지 않는다. 이 문맥에서는 RLSC의 어느 노드 또는 묶음까지 한 컴포넌트로 볼지에 대한 정보다. 하나의 디자인 안에 부분 경계가 여러 개 있을 수도 있다.

하지만 이것만으로는 `text-11`이 항목 이름인지 설명인지 확정되지 않는다. **경계가 정해졌다고 내부 모든 글상자의 의미 역할까지 정해진 것은 아니다.** [용어 SSoT](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/README.md), [C7](#c7).

## 8. I.I.S. (Semantic Component Property)가 더 알아내는 것

이 국면의 이름 'Property'는 넓은 뜻이다. 결과는 두 종류다.

| 산출물 | 질문 | 내용 |
| --- | --- | --- |
| `properties` | 이 디자인은 어떤 표현 속성을 갖는가? | 예: marker가 number인가 |
| `structure` | 각 항목의 label과 items는 어느 노드인가? | 예: 첫 항목 label은 text-11, items는 text-12 |

학습용으로 단순화한 구조 대응은 다음과 같다. 실제 DB 직렬화 전체를 그대로 복사한 예시는 아니다.

```yaml
node_id: group-A
categories: [ProcessFlow_Steps]
properties:
  marker: number
structure:
  entities:
    - id: group-step-1
      fields:
        label: text-11
        items: [text-12]
    - id: group-step-2
      fields:
        label: text-21
        items: [text-22]
    - id: group-step-3
      fields:
        label: text-31
        items: [text-32]
```

이제 새 내용의 첫 label '주제 선정'을 `text-11`에, 첫 설명을 `text-12`에 넣을 수 있다.

```text
Boundary: group-A는 Steps다
       ↓ 그 경계의 내부를 해석한다
Property 국면: 표현 속성 + 노드별 구조 대응을 구한다
       ↓ 대응표와 용량을 정리한다
Component Frame: label 자리, items 자리, 각 노드 ID와 용량
       ↓ 새 내용과 맞춘다
binding: text-11 ← 주제 선정
```

이 연결에서 내용 대치에 직접 필요한 것은 **구조 대응과 실제 슬롯**이다. `properties.marker`만으로는 '실험'을 어느 글상자에 넣어야 하는지 알 수 없다. [C2](#c2), [C5](#c5).

## 9. 'property가 없으면 안 들어간다'를 다섯 경우로 나눈다

### 9.1 카테고리가 그 내용 필드를 읽는다고 선언하지 않았다

예를 들어 Steps는 `period` 전용 필드를 읽지 않는다. 이는 I.I.S. 추출이 빠졌다는 말과 다르다. 카테고리의 규칙 문제다.

처음부터 Steps를 생성한다면 날짜가 필요한 경우 '3월에 주제를 정한다'처럼 `items`에 적는다. Timeline에서 Steps로 fallback한다면 P3이 period를 items로 옮긴다. **처음부터 Steps로 쓰는 경로에는 fallback projection이 자동으로 구제해 준다는 보장이 없다.** 생성기가 읽지 않는 필드에 내용을 넣지 않아야 하는 이유다. [capability 1절 B-33](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md).

### 9.2 표현 속성 properties만 없다

구조 대응이 있고 `properties`만 비었다면 글상자 위치는 알 수 있다. 코드에서 속성 누락은 `missingOnComponent`로 구분하고, 값이 다르다는 명시적 충돌로 취급하지 않는다. **properties가 비었다는 사실만으로 YAML 내용 대치 전체를 막지 않는다.** [C7](#c7).

### 9.3 IIS 구조 대응이 없다

명시적인 노드별 역할 정보가 없어서 어디에 넣을지 판단하기 어렵다. 하지만 현재 적재 코드는 IIS Frame을 만들 수 없으면 `buildRuleFrame`으로 보완한다. RLSC의 반복 단위와 역할로 슬롯을 만들 수 있기 때문이다. 모든 의미를 정확히 복원한다는 보장은 없고, Frame의 출처를 남긴다. [C5](#c5).

### 9.4 일부 글상자만 슬롯에 잡히지 않았다

그 글상자는 사용자 글과의 대응이 정해지지 않은 자리다. 글이 보인다는 이유만으로 임의의 사용자 문장을 넣으면 제목, 날짜, 설명의 역할이 뒤섞일 수 있다.

현재 화면과 제품 클라이언트는 공유 `buildNodeTextMap`을 쓴다.

| 상태 | 현재 처리 |
| --- | --- |
| `binding` | 사용자 글 또는 표의 `matrix`를 넣는다. 가장 먼저 적용한다 |
| `blank` | 채울 글이 없는 슬롯. 아래 공통 판단으로 원문 또는 임시 문구를 결정한다 |
| `preserve` | align이 남긴 보존 후보다. **이 이름만으로 원문 보존이 확정되지 않는다** |
| `inside_unbound_text` | 경계 안 미매핑 글. 공통 판단을 적용한다 |
| `outside_text` | 경계 밖 글. 제목으로 선택된 한 곳 외에는 같은 공통 판단을 적용한다 |

판단 순서는 **Marker와 비내용 역할(Decoration·Separator·Annotation)은 원문 → blank/preserve의 unit 필드는 임시 문구 → 구조 토큰은 원문 → 나머지는 임시 문구**다. 일반 숫자·숫자+단위도 원문 보존의 이유가 아니다. `vs`, TAM/SAM/SOM, 숫자가 아닌 한 글자, 빈 글은 구조 토큰에 해당한다.

문서 제목은 안쪽 binding에 `field: title`이 없을 때 경계 밖 한 곳으로 구제할 수 있다. Title 후보를 우선하고, 없으면 Marker·비내용 역할을 뺀 후보를 위치·글꼴 크기로 비교한다. 선택되지 않은 Subtitle·Description·추가 Title도 일반 글이면 임시 문구가 된다. 원본 크기 고정 및 threading 결과에서도 이 제목 구제는 동작한다. [C9](#c9).

### 9.5 새 내용에 맞는 전용 슬롯이 없다

새 내용의 날짜 전용 슬롯은 없지만 `items` 슬롯은 있을 수 있다. align은 필드를 items 등 받을 수 있는 슬롯으로 옮기는 처리를 한다. 반면 정말 담을 곳이 없어 `contentLoss > 0`이 되면 현재 랭커는 그 후보를 제외한다. '매핑을 못 했으니 그 글만 조용히 버린다'가 의도된 성공 경로는 아니다. [C6](#c6).

**핵심 이유는 전달할 내용의 역할과 넣을 위치를 정확히 연결해야 하기 때문이다.** property라는 단어 때문에 막는 것이 아니라, 프로그램이 어떤 내용을 어느 노드로 보낼 수 있는지 판단할 근거가 필요하다.

## 10. comp-frame 코드를 실제 순서로 읽는다

### 10.1 미리 준비하는 색인 경로

```text
기존 디자인 + RLSC + I.I.S. 결과 + 글 용량
  → buildIisFrame
  → 실패하거나 구조 대응이 없으면 buildRuleFrame
  → Frame과 검색용 요약 필드, 잔여 텍스트 메타데이터를 저장
```

`buildIisFrame`은 node_id로 경계를 찾고, 카테고리에서 허용한 필드를 대상으로 노드 참조를 슬롯으로 바꾼다. `properties`는 Frame의 params 쪽에 담긴다. 일부 누락된 반복 단위를 규칙으로 보완하는 처리도 있다. 이것은 'YAML을 받아 그때마다 IIS를 새로 수행한다'는 흐름과 다르다. [C5](#c5).

### 10.2 요청마다 실행하는 검색 경로

2026-10-12에 TypeScript 검색 구현을 제거했다. **검색·랭킹·중첩 사다리·Content Threading은 Rust 서버가 소유한다.** 화면은 문서 전체를 한 요청으로 보내고 결과를 표시한다.

| 단계 | 현재 코드 | 역할 |
| --- | --- | --- |
| 입력·요청 | `parseVisualSummaryInput.ts`, `useUnifiedSearch.ts`, `backend/remoteBackend.ts` | 입력을 읽고 문서 단위 검색을 요청한다 |
| 공개 클라이언트 | `ComponentSearchClient.search` | 원문 또는 Visual Summary를 서버에 보낸다. 원문은 서버 생성까지 한 요청에 포함된다 |
| 문서·중첩 | Rust `document_search.rs`, `nested_search.rs` | L1/L2 문서와 N1~N5의 탐색을 조율한다 |
| 질의·투영 | Rust `query_frame.rs`, `visual_summary/summary_projection.rs` | tier와 도착지별 Query Frame을 만든다 |
| 조회·배정 | Rust pool `search.rs`, core `align.rs` | 메모리 스냅샷의 후보를 조회하고 실제 글을 배정한다 |
| 검사·순위 | Rust `rank.rs`, `cost.rs` | 용량·내용 손실·표 확장·속성 등을 검사하고 중복 제거·정렬한다 |
| 실제 대치 | `comp-frame-client/replacement.ts`, 공유 `nodeTextPolicy.ts` | 응답과 sheetJson으로 텍스트·표·박스 대치 데이터를 만든다 |
| 표시 | `FrameThumbnail` 및 제품 렌더러 | 대치 데이터를 실제 디자인에 적용한다 |

`src/comp-frame/query-frame.ts`에 현재 남은 것은 표 표시를 위한 보조 함수다. 거기서 `buildQueryFrames`를 찾으면 현행 검색을 놓친다. [C1](#c1)~[C10](#c10).

### 10.3 코드에서 구분해야 할 숫자 셋

| 이름 | 의미 | 예 |
| --- | --- | --- |
| tier 1~3 | 검색할 카테고리의 우선순위 군 | type, fallback, 마지막 대안 |
| stage 0~2 | 같은 카테고리 안에서 Frame 모양 조건을 넓히는 단계 | shape bits 차이 0, 1, 2 |
| N1~N5 | 중첩된 컴포넌트 구조를 어떤 순서로 변환할지 | 구조 유지, 부모 벗기기, 평탄화 |

shape bits는 'label이 있나, items가 있나' 같은 특징의 존재 여부를 압축한 검색용 값이다. **stage 0은 모든 글과 개수, 시각 배치가 완벽히 동일하다는 보장이 아니다.** 후속 검사를 따로 한다.

**stage를 내려도 안 풀리는 비트가 있다.** 소프트닝 대상은 앞쪽 17개뿐이라, `dataset`(measure·matrix)과 `extraGrids` 비트는 **하드 필터로 끝까지 남는다.** 표·차트 데이터를 요구하는 질의가 stage 2까지 내려가도 데이터가 없는 후보는 절대 올라오지 않는다는 뜻이다.

stage 2는 '2비트 이하 차이'가 아니라 **정확히 2비트 차이**이고, stage 1의 패턴을 빼고 만든다. 안 빼면 같은 후보를 stage 1과 2에서 두 번 센다. [C3](#c3), [C10](#c10).

### 10.4 결과는 무조건 tier 1부터 나오나

현재 기본값은 `rank_mode: tier_pool`과 `clean_first: above_tier`다. 코드가 계산한 `unauthored_count`가 0인 결과를 tier보다 먼저 고려한다. 그래서 이미 조회된 후보끼리 비교할 때, 잔여가 없는 tier 2 결과가 잔여가 있는 tier 1 결과보다 먼저 나올 수 있다.

기본 비교 순서는 대략 다음과 같다.

```text
unauthored_count가 0인가
→ tier
→ fallback 행에서의 순서
→ 꾸밈 선호 일치
→ 감점 cost
→ 동률 해소 규칙
```

여기서 '온전함'은 이 코드가 센 잔여 기준이지 사람이 보기에 무조건 아름답고 의미가 정확하다는 증명은 아니다. 또한 정렬은 **이미 조회한 후보**를 대상으로 한다. tier 1에서 검색이 멈췄다면 아직 조회하지 않은 tier 2 디자인을 정렬이 새로 찾지는 않는다.

**같은 Frame의 형제 중에서는 온전한 쪽을 대표로 세운다.** 지문이 같은 후보 묶음에서 먼저 만난 것이 아니라 잔여가 0인 것을 고르고, 대표가 바뀌면 다시 정렬한다. 이 처리가 없으면 자리는 값싼 형제가 벌어 놓고 화면에는 오염된 형제가 뜬다.

### 왜 잔여를 tier보다 먼저 보나 — 실측 하나

2026-09 평가에서 잰 값이 근거다. 대치 후에도 사용자 글이 아닌 자리가 남는 것이 **judge 판정에는 영향을 주지 않는데**(오즈비 1.012, p = 0.878) **사용자 선택에는 영향을 준다**(오즈비 0.688, p = 0.022 — 선택 오즈가 약 31% 낮다). 컴포넌트가 글에 안 맞아서 생긴 착시인지 의심해 보정해도 0.663으로 남았다.

이 과거 분석은 남은 자리를 줄일 때 **카드 채택률도 살펴야 할 근거**다. 관찰 자료의 연관성을 모든 구현의 인과효과로 일반화하지 않는다. 이 수치는 규약이 아니라 도입 판단용 실측이고, 새 경로 수치는 자리째 비는 버그가 있는 상태에서 잰 것이라 고치면 달라질 수 있다. [평가 자료](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/evaluation/placeholder-effect.md), [C6](#c6), [C8](#c8).

### 10.5 결과 하나가 페이지 하나라는 전제가 깨졌다

**Content Threading**은 내용을 여러 장에 이어서 담는다. 현재 프로파일은 **List·Steps·QnACard·TableOfContents 네 종**이다. 요청에 threading 옵션을 보내면 서버가 직접 배치 → 탐색용 질의 → 나눠 담기 → 장별 검사·재정렬을 수행한다. 화면 기본값은 켬, 장수 기본 상한은 64이지만 서버 능력과 요청 설정을 확인해야 한다.

상위 `binding`·`blank`는 첫 장을 나타낸다. 다른 장은 `threadingPage`로 꺼내야 한다. threading 결과는 원본 크기로 배치하되 **경계 밖 제목 구제를 끄지 않는다.** 임시 문구는 `node_assets[id].orig`의 원본 셀 용량으로 크기 등급을 고르며, 서버가 검사한 것과 같은 언어별 문구 표로 그려야 맞음 보장이 유지된다. [C11](#c11), [C9](#c9).

### 10.6 코드의 책임이 다섯 곳에 나뉜다

| 자리 | 책임 |
| --- | --- |
| `rust/comp-pass/` | 검색·랭킹·중첩·threading의 실행 코어와 서버 |
| `src/comp-frame/` | 입력, 서버 요청, 결과 카드와 렌더 연결 |
| `packages/comp-frame-shared/` | Frame v5 계약, 공통 대치 정책, 기본 옵션, `forked/` |
| `packages/comp-frame-index/` | IIS/RLSC에서 검색 색인과 용량 데이터를 만드는 배치 |
| `packages/comp-frame-client/` | 제품용 검색 API와 대치 페이로드 |

옛 `packages/comp-frame`은 없다. `forked/`는 실행 경로가 comp-sign·comp-match를 직접 import하지 않도록 분리한 사본이다. 적재 측은 용량 모델의 정본을 호출하는 예외가 있으므로 “모든 패키지가 comp-sign과 완전히 무관하다”로 확대하지 않는다. [C12](#c12).

### 10.7 표와 차트도 같은 글상자로 보면 안 된다

native 표·차트의 grid binding은 `SlotMatrix`를 싣는다. 실제 TableItem에는 표 확장 한계 검사가 있고, 측정 불가 표는 `tableUnmeasured` 벌점으로 구분한다. 반면 글상자로 만든 표는 IIS 셀 지도(`dataset.cells`, `header`, `detectedFrom: iis`)를 통해 셀별로 맞출 수 있다. 모든 matrix가 native chart라는 뜻은 아니다. 단순 텍스트 예제만으로 표의 행·열 확장이나 렌더 품질을 검증할 수 없다. [C5](#c5), [C6](#c6).

### 10.8 지정한 종류와 추천 결과의 0군·1군

`generationTypes`로 종류를 지정하면 서버는 지정 종류 문서의 **0군**을 `tier1_only`로 먼저 검색하고, 반환 목표를 못 채울 때 추천 문서의 **1군**을 검색한다. 결과의 `group`은 tier와 별개다. 타입 지정이 없으면 추천 검색이며 1군으로 표시한다. [C10](#c10).

## 11. 여러 블록과 중첩: L1, L2, L3

| 레벨 | 허용하는 구조 | 예 |
| --- | --- | --- |
| L1 | 단일 컴포넌트 | Timeline 하나 |
| L2 | 최상위 컴포넌트 여러 개 | Timeline 옆에 List |
| L3 | entity 안에 다른 컴포넌트도 중첩 | 비교표의 한 항목 안에 Steps |

이는 생성기에 허용한 복잡도 범위다. L3를 허용해도 모든 생성물이 꼭 중첩이어야 하는 것은 아니다. `entity.children`은 항목의 계층이고 `entity.components`는 별도 컴포넌트의 중첩이므로 구분한다.

**세 레벨은 누적이다.** L1 ⊂ L2 ⊂ L3이고, 산출물 한 건의 모양이 아니라 **생성기에 허용된 범위의 이름**이다. 가르는 축은 하나다 — 컴포넌트 경계를 넘는 계층 `entity.components[]`를 쓰느냐. `entity.children[]`은 어느 레벨에서나 쓴다.

**규약의 허용 범위와 각 생성기의 지원 범위는 별도로 확인한다.** 위키는 실서비스 L2·PoC L3를 구분한다. 현행 제품용 서버 검색은 L1/L2 문서 묶음을 받으며, Rust 중첩 알고리즘도 존재한다. 위키의 I-11·D-7 과거 미완 기록을 현재 iui 코드 전체의 상태로 옮기지 않는다. [스키마 8.8절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md#L974), [C10](#c10).

최상위 `components`가 둘인 L2는 이미 경계가 나뉜 상태다. 각각 검색한다. L3 중첩에서 원하는 전체 디자인을 못 찾으면 다음을 시도한다.

| 순서 | 동작 |
| --- | --- |
| N1 | 중첩 구조와 카테고리를 그대로 찾는다 |
| N2 | 구조를 유지하면서 노드의 카테고리를 fallback한다 |
| N3 | 가장 바깥 부모를 벗기고 자식을 위로 올려 다시 찾는다 |
| N4 | 필요한 만큼 부모 벗기기를 반복한다 |
| N5 | 중첩 내용을 글로 평탄화한다 |

각 단계에는 이름이 붙어 있다 — N1 `ExactMatch: Structure&Semantic`, N2 `ExactStructure-SemanticFallback`, N3 `Root-Peeling`, N4 `Re-Peeling`, N5 `Content Flattening`. 아래로 갈수록 잃는 것이 는다. N2는 노드마다의 카테고리 표현을, N3·N4는 벗긴 부모의 표현과 형제 인접성을, N5는 자식 전부의 카테고리 표현을 잃는다.

**반복 상한은 두지 않는다.** N3·N4를 되풀이해 최상위 컴포넌트가 늘어도 배치 엔진이 페이지를 나눈다.

이 사다리를 실제로 쓰는 것은 **L3뿐이다.** L2는 최상위 원소마다 검색이 독립으로 돌 뿐 이 절을 쓰지 않는다.

> **N1의 실제 후보 수는 현재 풀을 조회해야 안다.** 위키 D-7은 당시 이종중첩 공급 제외를 기록한다. 현행 Rust는 N1 exact 검색을 실제 호출하고 결과가 있으면 반환한다. 이번 갱신에서 운영 풀을 재측정하지 않았으므로 “언제나 0”으로 단정하지 않는다.

N2에서 조합은N2에서 조합은 강등 칸수 합이 작은 것부터 시도한다. 동률이면 더 깊은 노드부터 강등한다. 부모 표현을 바꾸는 영향이 더 클 수 있기 때문이다. 형제는 큰 연속 묶음부터 찾고 순서를 유지한다.

**Hierarchy 부모는 N3를 건너뛰고 N5로 간다.** 부모를 벗기면 회사, 부서, 직원의 소속 관계가 사라질 수 있기 때문이다. 평탄화는 들여쓴 글로 그 관계를 남긴다. 현재 iui에는 Rust `nested_search.rs`가 있으므로 과거 위키의 '오케스트레이션 미구현'을 그대로 현재 설명으로 쓰면 안 된다. [capability 3.6절](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md), [C10](#c10).

## 12. 한 사례를 처음부터 끝까지 설명한다

### 상황

사용자는 2강의 Timeline YAML을 냈다. 설명을 단순하게 하려고, Timeline과 Steps에는 대치 가능한 후보가 없고 Staircase도 없으며 List에서 쓸 후보를 찾았다고 가정한다. 실제 DB 조회 결과가 아니다.

| 순서 | 일어난 일 | 지켜야 할 점 |
| --- | --- | --- |
| 1 | type Timeline과 세 항목을 읽는다 | type과 내용은 별개다 |
| 2 | tier 1 Timeline, tier 2 Steps, Staircase, List를 준비한다 | 각 도착지는 성립 검사를 통과해야 한다 |
| 3 | 후보 카테고리별 projection을 만든다 | 이름만 바꾸지 않는다 |
| 4 | Timeline을 조회하고 대치 적합성을 확인한다 | 조회 건수와 통과 후보 수는 다르다 |
| 5 | 다음 카테고리를 정해진 순서로 시도한다 | Steps 행으로 갈아타지 않는다 |
| 6 | List용 내용에서 날짜를 items로 보존한다 | 세 사건, 세 날짜가 모두 남아야 한다 |
| 7 | List 후보의 Frame 슬롯에 새 내용을 맞춘다 | 실제 노드 ID가 필요하다 |
| 8 | 글 용량과 내용 손실 등을 확인한다 | 같은 List라도 전부 통과하지 않는다 |
| 9 | binding에 따라 디자인의 글을 바꾼다 | 슬롯 밖 글은 별도 정책으로 처리한다 |

완료 후에는 세 가지를 따로 확인한다. **내용이 보존됐는가, 표현이 요청과 얼마나 달라졌는가, 실제 글상자에 잘 들어갔는가.** 첫 번째가 맞다고 두 번째와 세 번째도 맞는 것은 아니다.

## 13. 명세와 현재 코드가 다른 곳

| 주제 | 구분해야 할 사실 |
| --- | --- |
| 규약과 생성 범위 | 규약 v0.5.6의 인식 어휘는 45종, 생성 허용은 36종이다. enum을 36종으로 줄인 것이 아니다 |
| alternatives | 명세는 fallback보다 앞선 대안을 허용하지만 현재 Rust tier 계산은 읽지 않는다 |
| P8 | 개수 상한을 접어 맞추는 P8은 폐기됐다. 오래된 예시·제목의 P1~P8 표기는 현재 규칙을 뜻하지 않는다 |
| 실행 위치 | 옛 TS 검색 함수들은 삭제됐다. Rust가 검색을 하고 TS는 색인·공통 계약·클라이언트·화면을 맡는다 |
| 원문 유지 | `preserve`라는 배열 이름이나 경계 밖이라는 위치만으로 원문 유지가 결정되지 않는다 |
| N1 | 단계는 구현됐다. 위키의 과거 풀 제외 정책을 최신 풀의 0건 실측으로 읽지 않는다 |
| threading | 네 카테고리를 지원하며 제목 구제도 살아 있다. 결과 하나가 여러 장일 수 있다 |
| 점수 | 기본 가중치 JSON에는 23개 비용 키와 threading 전용 2개 가중치가 있다. 값의 크기가 알고리즘 우열의 실험 증거는 아니다 |
| 공급 버전 | Frame 스키마 v5, IIS 발행분, 제품 componentVersion, 배포 서버 버전은 서로 다른 축이다 |
| 도형 속 글자 | aippt의 `isTextBearingNode`는 글 있는 SVG도 읽는다. iui 벤더 `extractTextSlots`는 아직 Text만 읽는다. 한쪽 변경을 다른 쪽 완료로 쓰지 않는다 |

2026-09-27 위키 평가는 운영 markdown BAD 32.0%, Visual Summary 30.9%를 보고했다. 같은 입력 비교의 신뢰구간은 0을 포함하므로 개선을 확정할 수 없다. 묶음 결과는 여러 컴포넌트를 한 판정으로 셌고 재배치는 껐다. 이 수치는 **그때 실행한 평가**이며 10월 코드나 현재 운영 성능의 측정값이 아니다. [C13](#c13).

최신 origin/main의 코드가 있다는 것과 운영 서버·DB가 모두 같은 판이라는 것은 별개다. 이번 갱신은 고정 소스 검토와 정적 문서 검증이며 운영 풀·배포 상태를 재측정하지 않았다.

## 14. 코드와 문서 출처

2026-10-17에 fetch한 각 `origin/main`의 고정 커밋 파일을 읽었다. 스냅샷은 해당 커밋의 `git archive`로 만들어 워킹트리의 미커밋 변경을 섞지 않았다.

| 저장소 | 고정 커밋 |
| --- | --- |
| mordor-wiki | `a0f8b01961db9396206b1ddf097c66cb44bde137` |
| miricanvas-iui | `bcd1d0218766cbba3a174d1819ab218d613e626f` |
| aippt-prisonbreak | `e6ffa4bdf65ec76f3d3031dc566f3fc7c19eade1` |

위키 `spec/`·`design/`·`reference/`는 2026-09-20 학습판의 기준 커밋 이후 변경이 없었다. 개념 설명은 유지하고, 실행 위치·후보 수집·대치 정책·threading·표와 차트·코드 링크를 새로 확인했다. aippt `README.md`와 `_SIGNATURE.md`도 함께 읽었으며, 오래된 구현 요약 문구는 실행문과 구분했다.

### 명세

- [S1. 스키마](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/schema-v0.5.md#L59)
- [S2. capability](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/spec/capability-table.md#L31)
- [S3. 용어](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/README.md#L1)
- [S4. 내용 대치 contract](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/design/content-binding-contract.md#L1)

<a id="c1"></a>
### C1. 검색 군과 질의 Frame

[rust/comp-pass/crates/comp-frame-core/src/query_frame.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/query_frame.rs#L515)

<a id="c2"></a>
### C2. Frame 공통 형식

[packages/comp-frame-shared/src/frame.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-shared/src/frame.ts#L31) · [rust/comp-pass/crates/comp-frame-core/src/frame.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/frame.rs#L32)

<a id="c3"></a>
### C3. 후보 조회와 중단

[rust/comp-pass/crates/comp-frame-pool/src/search.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-pool/src/search.rs#L604) · [rust/comp-pass/crates/comp-frame-core/src/filter.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/filter.rs#L185)

<a id="c4"></a>
### C4. 내용 projection

[rust/comp-pass/crates/comp-frame-core/src/visual_summary/summary_projection.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/visual_summary/summary_projection.rs#L27) · [rust/comp-pass/crates/comp-frame-core/src/visual_summary/summary_content.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/visual_summary/summary_content.rs#L18)

<a id="c5"></a>
### C5. IIS 결과에서 실제 Frame으로

[packages/comp-frame-index/src/frame/component-frame.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-index/src/frame/component-frame.ts#L926) · [packages/comp-frame-index/src/frame/cellMapFrame.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-index/src/frame/cellMapFrame.ts#L1) · [packages/comp-frame-index/src/bin/build-comp-frames.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-index/src/bin/build-comp-frames.ts#L1)

<a id="c6"></a>
### C6. 대응과 점수

[rust/comp-pass/crates/comp-frame-core/src/align.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/align.rs#L1313) · [rust/comp-pass/crates/comp-frame-core/src/rank.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/rank.rs#L1066) · [rust/comp-pass/crates/comp-frame-core/src/cost.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/cost.rs#L889) · [rust/comp-pass/crates/comp-frame-core/src/table_fit.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/table_fit.rs#L1)

<a id="c7"></a>
### C7. IIS 용어와 속성 비교

[packages/comp-frame-shared/src/eregion/visual-summary/iisSemanticComponent.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-shared/src/eregion/visual-summary/iisSemanticComponent.ts#L1) · [rust/comp-pass/crates/comp-frame-core/src/visual_summary/property_match.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/visual_summary/property_match.rs#L1)

<a id="c8"></a>
### C8. 기본 옵션

[packages/comp-frame-shared/search-defaults/options.json](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-shared/search-defaults/options.json#L1) · [packages/comp-frame-shared/search-defaults/weights.json](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-shared/search-defaults/weights.json#L1)

<a id="c9"></a>
### C9. 실제 노드에 넣을 글

[packages/comp-frame-shared/src/nodeTextPolicy.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-shared/src/nodeTextPolicy.ts#L11) · [packages/comp-frame-shared/src/forked/titleCandidate.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-shared/src/forked/titleCandidate.ts#L1) · [packages/comp-frame-client/src/replacement.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-client/src/replacement.ts#L604) · [src/comp-frame/resultMaps.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/src/comp-frame/resultMaps.ts#L178)

<a id="c10"></a>
### C10. 문서 검색과 화면 진입점

[rust/comp-pass/crates/comp-frame-core/src/nested_search.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/nested_search.rs#L1) · [rust/comp-pass/crates/comp-pass-logic/src/document_search.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-pass-logic/src/document_search.rs#L1) · [src/comp-frame/backend/remoteBackend.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/src/comp-frame/backend/remoteBackend.ts#L1) · [src/comp-frame/useUnifiedSearch.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/src/comp-frame/useUnifiedSearch.ts#L1)

<a id="c11"></a>
### C11. Content Threading

[rust/comp-pass/crates/comp-frame-core/src/threading/profile.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-frame-core/src/threading/profile.rs#L62) · [rust/comp-pass/crates/comp-pass-logic/src/threading_search.rs](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/rust/comp-pass/crates/comp-pass-logic/src/threading_search.rs#L1088) · [packages/comp-frame-client/src/threading.ts](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-client/src/threading.ts#L1)

<a id="c12"></a>
### C12. 코드 책임과 fork 경계

[src/comp-frame/AGENTS.md](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/src/comp-frame/AGENTS.md#L46) · [packages/comp-frame-index/README.md](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-index/README.md#L41)

<a id="c13"></a>
### C13. 평가 자료와 두 구현의 차이

[2026-09-27 상세 평가](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/evaluation/judge-rerun-0927.md#L1) · [2026-09 잔여·채택 분석](https://github.com/miridih/mordor-wiki/blob/a0f8b01961db9396206b1ddf097c66cb44bde137/projects/mordor/visual-summary/evaluation/placeholder-effect.md#L1) · [aippt 구현 설명](https://github.com/miridih/aippt-prisonbreak/blob/e6ffa4bdf65ec76f3d3031dc566f3fc7c19eade1/src/lib/visual-summary/README.md#L1) · [aippt 함수 목록](https://github.com/miridih/aippt-prisonbreak/blob/e6ffa4bdf65ec76f3d3031dc566f3fc7c19eade1/src/lib/visual-summary/_SIGNATURE.md#L1) · [aippt 글 있는 SVG 판정](https://github.com/miridih/aippt-prisonbreak/blob/e6ffa4bdf65ec76f3d3031dc566f3fc7c19eade1/src/lib/visual-summary/rlsc.ts#L78) · [iui 벤더 Text 판정](https://github.com/miridih/miricanvas-iui/blob/bcd1d0218766cbba3a174d1819ab218d613e626f/packages/comp-frame-shared/src/eregion/visual-summary/rlsc.ts#L67)
