Skip to content

fix: set_field 후 저장/재오픈 시 필드 값 유실 수정#446

Closed
oksure wants to merge 1 commit intoedwardkim:develfrom
oksure:contrib/fix-set-field-persistence
Closed

fix: set_field 후 저장/재오픈 시 필드 값 유실 수정#446
oksure wants to merge 1 commit intoedwardkim:develfrom
oksure:contrib/fix-set-field-persistence

Conversation

@oksure
Copy link
Copy Markdown
Contributor

@oksure oksure commented Apr 29, 2026

Closes #270

문제

set_field("필드명", "값")exportHwp() → 재오픈하면 필드 값이 빈 문자열로 유실됩니다. In-memory에���는 정상 동작합니다.

근본 원인

rebuild_char_offsets()가 FIELD_END(0x04) 마커 갭만 생성하고 FIELD_BEGIN(0x03) 컨트롤 갭을 누락합니다.

중간 위치(start_char_idx > 0) 필드의 경우:

  1. rebuild_char_offsets가 FIELD_BEGIN 위치에 8바이트 갭을 생성하지 않음
  2. serialize_para_text가 갭이 없어 FIELD_BEGIN을 텍스트 뒤로 밀어냄
  3. 재오픈 시 FIELD_BEGIN~FIELD_END 사이에 텍스트가 없으므로 빈 필드로 파싱
// 변경 ��� (잘못된 직렬화)
TEXT("라벨: PERSIST_TEST") | FIELD_BEGIN | FIELD_END | PARA_END

// 변경 후 (올바른 직렬화)
TEXT("라벨: ") | FIELD_BEGIN | TEXT("PERSIST_TEST") | FIELD_END | PARA_END

수정 내용

field_begin_at 배열을 추가하여 start_char_idx 위치에 8바이트 갭을 생성합니다. 이중 계산 방지를 위해 control_idx >= ctrls_before_text && start_char_idx > 0 조���으로 필터링합니다.

테스트

회귀 테스트 3건 추가:

  • rebuild_preserves_mid_text_field_begin_gap — 중간 위치 필드 갭 보존
  • rebuild_field_at_start_no_double_count — 문단 시작 필드 이중 계산 방지
  • rebuild_after_set_field_creates_serializable_gap — set_field 후 직렬화 가능한 갭 생성
cargo test --lib field_query::tests  # 3 passed
cargo test                           # 1,120 passed, 0 failed
cargo clippy --lib -- -D warnings    # 0 warnings

rebuild_char_offsets()가 FIELD_END 갭만 생성하고 FIELD_BEGIN 갭을 누락하여,
중간 위치 필드의 FIELD_BEGIN 컨트롤이 직���화 시 텍스트 뒤로 밀림.
재오픈 시 빈 필드로 파싱되어 값이 유실됨.

field_begin_at 배열을 추가하여 start_char_idx 위치에 8바��트 갭을 생성.
����� 테스트 3건 ��가.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 29, 2026 05:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a persistence bug where set_field() updates were visible in-memory but lost after exportHwp() → reopen by ensuring rebuild_char_offsets() recreates the missing gap for mid-text FIELD_BEGIN markers, so serialization preserves the correct FIELD_BEGIN … text … FIELD_END ordering.

Changes:

  • Add field_begin_at tracking in rebuild_char_offsets() to insert 8-code-unit gaps at start_char_idx for mid-text fields.
  • Keep existing FIELD_END gap logic and update the rebuild loop to apply both begin/end gaps.
  • Add 3 regression tests covering mid-text begin-gap preservation, start-of-paragraph double-count avoidance, and post-set_field serializability.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +713 to +715
// FIELD_BEGIN: control_idx >= ctrls_before_text이고 start > 0인 필드의 시작 위치에 갭 필요
let mut field_begin_at: Vec<usize> = vec![0; text_len + 1];
for fr in &para.field_ranges {
Comment on lines +795 to +796
// Stream: [ColumnDef 8B] A(1) B(1) C(1) [FIELD_BEGIN 8B] X(1) Y(1) [FIELD_END 8B]
let mut para = Paragraph {
Comment on lines +859 to +864
// 라=8(+1) 벨=9(+1) :=10(+1) ' '=11(+1) → field_begin gap +8 → N=20(+1) E=21(+1) W=22
assert_eq!(para.char_offsets[0], 8); // 라
assert_eq!(para.char_offsets[3], 11); // ' '
assert_eq!(para.char_offsets[4], 20); // N — 8-byte gap after ' ' for FIELD_BEGIN
let gap = para.char_offsets[4] as i64 - (para.char_offsets[3] as i64 + 1);
assert_eq!(gap, 8); // serializer needs exactly 8 code units for FIELD_BEGIN
edwardkim added a commit that referenced this pull request Apr 29, 2026
- mydocs/pr/pr_446_review.md (cherry-pick 검토, 옵션 C→A→머지 변천)
- mydocs/pr/pr_446_report.md (cherry-pick 머지 결정 + 한컴 2010/2020 양 환경 검증 통과)
- mydocs/orders/20260429.md (PR #444 + PR #446 완료 행 추가)
- rhwp-studio/e2e/issue-270-set-field-persist.test.mjs (메인테이너 후속 e2e, 155 lines)
- rhwp-studio/public/samples/field-01.hwp (Vite /samples/ 라우팅 정합)

검증: 1069 passed + svg_snapshot 6/6 + issue_418 1/1 + clippy 0 + WASM
+ 작성자 단위 테스트 3건 + e2e 12 assertions 모두 통과
+ 한컴 2010 + 한컴 2020 편집기 직접 검증 통과 (회사명 PERSIST_TEST 정상 표시)

본 검증 사이클은 메모리 feedback_self_verification_not_hancom 의 양 게이트 분리
실증 — 자기 라운드트립 (e2e) + 한컴 편집기 직접 검증. 본 e2e 패턴은 향후
컨트리뷰터들의 저장 경로 PR 검증 표준.
@edwardkim
Copy link
Copy Markdown
Owner

@oksure 님 PR 감사드립니다. 메인테이너가 cherry-pick 으로 devel 에 적용 완료했습니다.

본 PR 검증 사이클이 본 저장소의 메모리 원칙 (feedback_self_verification_not_hancom) 을 정확히 실증하는 사례가 됐습니다.

처리

작성자 attribution 보존 + 메인테이너 후속 정정 (e2e 검증 게이트):

devel 머지 commit: 839015e

검증 — 양 게이트 분리

본 PR 검증은 두 게이트 분리 통과 정황:

1. 자기 라운드트립 (rhwp e2e)

rhwp-studio/e2e/issue-270-set-field-persist.test.mjs:

  • field-01.hwp 로드 → setFieldValueByName("회사명", "PERSIST_TEST") → exportHwp → 재 loadDocument → getFieldValueByName 검증
  • 12 assertions 모두 통과
  • 산출물: output/hwp/issue_270_persist_test.hwp (473,600 bytes)

2. 한컴 편집기 직접 검증 (작업지시자 직접)

한컴 환경 "회사명" 필드 결과
한컴 2010 "PERSIST_TEST" 정상 표시 ✅ 성공
한컴 2020 "PERSIST_TEST" 정상 표시 (밑줄 강조) ✅ 성공
다른 필드 (작성자, 부서명, Tel, E-Mail) "여기에 입력" placeholder 정상 ✅ 회귀 없음

검증 게이트 통과 정황

검증 사이클의 의미 — 메인테이너 e2e 검증 표준 안내

본 PR 머지 과정에서 메인테이너 결정이 옵션 C → 옵션 A → 머지 로 3차례 변경됐습니다. 이는 검증 사이클의 본질을 보여줍니다:

  1. 단위 테스트 3건 (작성자 추가) → 통과 + 사용자 영향 큼 → 옵션 C (메인테이너 직접 통합 테스트 추가) 결정
  2. e2e 자기 라운드트립 (메인테이너 추가) → 통과
  3. 한컴 편집기 검증 1차 (1페이지만 확인) → 누름틀 사라짐 정황 → 옵션 A (작성자 보강 요청) 로 전환 시도
  4. 한컴 편집기 검증 2차 (1페이지 회사명 영역 확인) → 한컴 2010 + 2020 양 환경 정상 표시 확인 → 머지 결정

이 변천 자체가 메모리 feedback_self_verification_not_hancom 의 정확한 실증입니다 — 자기 라운드트립 통과해도 한컴 호환은 별도 게이트.

e2e 패턴 향후 활용

본 PR 의 메인테이너 후속 e2e (rhwp-studio/e2e/issue-270-set-field-persist.test.mjs) 가 저장 경로 PR 의 표준 검증 패턴 으로 정립되었습니다:

cd rhwp-studio
CHROME_CDP=http://localhost:19222 node e2e/issue-270-set-field-persist.test.mjs --mode=host

향후 set_field / FIELD / 직렬화 영역 PR 작성 시 같은 패턴 (rhwp-studio e2e + Chrome CDP + 한컴 편집기 직접 검증용 산출물) 활용 환영합니다. 매뉴얼: mydocs/manual/e2e-cdp.md.

본 PR 의 좋은 점

  1. 정밀 진단: FIELD_END 갭만 생성하고 FIELD_BEGIN 갭 누락이라는 비대칭 정확 식별
  2. 단위 테스트 3건: rebuild_char_offsets 함수 단위 검증
  3. 이중 계산 방지 가드: control_idx >= ctrls_before_text && start_char_idx > 0
  4. 다운스트림 사용자 시나리오 직접 해결: LLM agent / MCP / CLI 양식 자동 채움
  5. 변경 범위 한정: 단일 파일 +107/-3
  6. 보너스: 이슈 [bug] set_field value is lost after save → reopen (in-memory OK, not persisted) #270 작성자 (@hyoseop1231) 의 사용 시나리오 + 본 e2e 결합으로 향후 회귀 게이트 확보

이슈 #270 도 함께 close 됩니다. 감사합니다.

@edwardkim edwardkim closed this Apr 29, 2026
jangster77 added a commit to jangster77/rhwp that referenced this pull request Apr 30, 2026
…Canvas→LayerTree 반영 (Task edwardkim#460 기반)

- upstream/devel 46 commits 병합 (PR edwardkim#446 set_field fix ~ PR edwardkim#456 Canvas→LayerTree)
- 소스 파일 자동 병합 성공 (layout.rs, paragraph_layout.rs, engine.rs)
- orders/20260430.md add/add 충돌만 수동 해소 (Task edwardkim#460 섹션 + PR edwardkim#450 섹션 통합)

Co-Authored-By: Claude Sonnet 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.

3 participants