문서 / docs/task-reports/technical-label-typography.md

.technical-label 자간 규칙 분리 보고서

라벨 자간을 한글과 라틴으로 나눈 작업 보고 — 구현·검토·수정 세 역할이 실제로 지나간 자취.

작업 범위: .technical-label의 자간·대문자 규칙을 한글용 기본과 라틴 전용 수식자로 분리
기준일: 2026-08-18 UTC
manual-impact: none (공개 매뉴얼 문안 변경 없음, 표기만 조정)

배경

서체를 자가호스팅으로 적재한 직후(8908275) .technical-label의 자간이 드러났다. letter-spacing: 0.04em + text-transform: uppercase는 라틴 라벨(On this page, Working Guide / v1, 01 / Concept)에는 맞지만 한글 라벨(통과 조건, 읽는 것, 남기는 것, 권장 문서 · 사용자가 씁니다)에는 과해 성기게 보였다. 한글은 자간을 벌리지 않는 것이 기본이다.

RED

구현 전에 tests/browser/technical-label.spec.ts를 먼저 쓰고 실행했다.

  1) [chromium] › tests/browser/technical-label.spec.ts:11:5 › 한글 라벨은 자간을 벌리지 않고, 라틴 라벨은 계속 벌린다

    Error: expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 0.4736

      31 |   // 한글: 벌어지지 않는다. 대문자 변환도 한글에는 뜻이 없으므로 걸지 않는다.
    > 32 |   expect(spacingPx(koreanStyle.letterSpacing)).toBe(0);
         |                                                ^

  2) [chromium] › tests/browser/technical-label.spec.ts:43:5 › 한글이 섞인 라벨은 라틴 수식자를 받지 않는다

    Error: expect(received).toBe(expected) // Object.is equality

    Expected: 0
    Received: 0.4736

      51 |   const mixedSpacing = await mixed.evaluate((el) => getComputedStyle(el).letterSpacing);
    > 52 |   expect(spacingPx(mixedSpacing)).toBe(0);
         |                                   ^

  2 failed

첫 시도에서는 한글 라벨이 SM 갈래 패널에 있어 toBeVisible()이 hidden으로 실패했다 (규칙이 아니라 탭 상태 때문). 탭 링크 .track-tab--sm을 눌러 패널을 편 뒤 위의 자간 값 불일치라는 진짜 RED를 확인했다.

GREEN

명령 결과
npx playwright test PASS — 119 passed (기존 117 + 신규 2)
npm run verify:app PASS — verify:docs, test(unit/integration), typecheck, lint, build, verify:bundle 전부 통과

구현 결과

  • app/globals.css: .technical-label 기본 규칙에서 letter-spacingtext-transform을 제거해 한글용으로 만들고, .technical-label--latin 수식자에 기존 표기(0.04em + uppercase)를 옮겼다. 색·서체·크기·굵기는 그대로다.
  • 라틴 전용 라벨 30곳에 technical-label--latin을 붙였다: app/page.tsx(9), app/guide/page.tsx(7), app/roadmap/page.tsx(5), app/5240lab/page.tsx(4), app/docs/page.tsx(1), app/not-found.tsx(1), components/MaturityLevels.tsx, components/MaturityScore.tsx, components/EvidenceExplorer.tsx.
  • 기본 규칙에 남긴 라벨 17곳은 전부 한글이 주인이다: 통과 조건, 읽는 것, 남기는 것, 목차, {n}편, {n}단계, 권장 문서 · {authorship}, 남기는 문서 · {authorship}, HOW / {steps}(steps가 한글), 문서 / {sourcePath}(앞머리가 한글).
  • 판별 기준은 "내용이 순수 라틴·숫자·기호인가"이며, AI가 채웁니다처럼 라틴이 섞였을 뿐 한글이 주인인 것은 기본 규칙 쪽이다. 이 기준을 CSS 주석으로 남겼다.

테스트가 진짜인지

  • 한글·라틴 두 값을 같은 테스트에서 읽어 koreanPx < latinPx로 비교한다. 규칙을 통째로 지우면 두 값이 같아져 실패한다.
  • .technical-label--latin 수식자를 지우면 라틴 쪽 > 0.3 단언과 uppercase 단언이 실패한다. 기본 규칙에 자간을 되돌리면 한글 쪽 toBe(0)이 실패한다.
  • 선택자가 아니라 화면에 보이는 문구(통과 조건, On this page, 권장 문서)로 요소를 잡아, 클래스 이름만 바꾸는 리팩터링에는 견디고 표기가 되돌아가면 깨진다.

브리프와 어긋난 지점

없다. 브리프의 참조 내용(app/globals.css의 두 선언, 라벨 목록)은 현재 소스와 일치했다. 라틴 판별은 브리프 지시대로 소스를 직접 읽어 수행했다.

남은 우려

  • 이 보고서는 content/docs-manifest.mjs에 등록하지 않았다. 공개 여부는 사람이 정한다.

독립 검토 지적 반영 (2026-08-18)

Critical 0건, 명세 준수 충족. Important 1건과 Minor 1건을 닫았다.

I-1 회귀 테스트가 판정 자체를 보호하지 못했다

지적: 기존 두 테스트는 통과 조건·On this page·권장 문서 세 요소만 검사한다. 규칙(CSS)이 통째로 사라지는 것은 막지만, 이 커밋의 본체인 47곳의 갈래 판정 — 한글 라벨에 --latin을 붙이거나 라틴 라벨에서 떼는 오분류 — 은 전혀 잡지 못했다.

RED: app/guide/page.tsx:86의 한글 라벨 읽는 것에 일부러 technical-label--latin을 붙이고 기존 테스트를 돌렸다. 통과했다 — 그것이 결함이다.

Running 2 tests using 2 workers

  ✓  1 [chromium] › tests/browser/technical-label.spec.ts:43:5 › 한글이 섞인 라벨은 라틴 수식자를 받지 않는다 (1.4s)
  ✓  2 [chromium] › tests/browser/technical-label.spec.ts:11:5 › 한글 라벨은 자간을 벌리지 않고, 라틴 라벨은 계속 벌린다 (1.4s)

  2 passed (3.6s)

수정: tests/browser/technical-label.spec.ts에 라벨을 열거하지 않고 규칙 자체를 못 박는 전수 불변식 테스트를 더했다. /·/guide·/docs·/5240lab· /roadmap 다섯 경로의 .technical-label$$eval로 전부 읽어(오늘 143개), 한글이 든 라벨은 letter-spacing: 0 + text-transform: none, 한글이 없는 라틴 라벨은 > 0.3px + uppercase를 단언한다. 갈래 패널이 접혀 있어도 요소는 DOM에 있으므로 탭 클릭 없이 SM 라벨까지 덮는다. 같은 주입을 유지한 채 다시 돌리니 이번엔 실패했다.

  ✘  3 [chromium] › tests/browser/technical-label.spec.ts:63:5 › 모든 라벨이 갈래 규칙을 지킨다 — 한글은 자간 0, 라틴은 자간을 벌린다 (2.2s)

    Error: /guide :: "읽는 것" — 한글 라벨인데 자간이 벌어져 있다

    Expected: 0
    Received: 0.4736

반대 방향도 확인했다. 진짜 라틴 라벨(app/5240lab/page.tsx:46 Reading key)에서 --latin을 떼는 반대 오분류를 주입하니 같은 테스트가 잡았다.

    Error: /5240lab :: "Reading key" — 라틴 라벨인데 자간이 벌어지지 않았다

    Expected: > 0.3
    Received:   0

멀쩡한 입력을 막지 않는지도 확인했다. 다섯 경로의 라벨 143개를 전수로 덤프해 본 결과 텍스트가 비었거나 한글도 라틴 글자도 없는(숫자·기호 ·만인) 라벨은 한 곳도 없다. 숫자·중점이 섞인 라벨(01 · 브레인스토밍, 4편 · 모든 단계에 적용, HOW / 작업 분해 · 격리 구현)은 모두 한글을 포함해 기본 규칙 쪽으로 정확히 판정된다.

다만 "한글 없음 → 라틴"이라는 이분법은 그런 라벨이 생기면 자동으로 라틴 취급해 판정을 사람 대신 정해버린다. 단언을 느슨하게 푸는 대신 정확히 좁혔다 — 분기를 셋으로 나눠 한글도 라틴 글자도 없는 라벨은 명시적으로 실패시키고, 갈래를 사람이 정해 테스트를 함께 고치도록 메시지를 남겼다. 오늘 해당 라벨이 0개이므로 멀쩡한 입력을 막지 않는다. 이 분기가 실제로 발화하는지는 라벨 텍스트를 · 01 ·로 잠깐 바꿔 확인한 뒤 원복했다.

기존 두 테스트는 남겼다. 전수 불변식이 자간·대문자 판정은 포함하지만, 기존 테스트는 그 세 라벨이 여전히 존재하고 탭을 편 뒤 실제로 visible인지까지 함께 본다 — 전수 검사가 덮지 않는 축이다.

M-1 "남은 우려"가 실제보다 과했다

{layer.id}에 한글이 들어가면 판정이 어긋난다는 우려를 적었으나 소스를 확인하니 과했다. lib/evidence-schema.tsHARNESS_LAYER_IDSas const 튜플이고 HarnessLayerId는 그 유니온이며, content/layers.tsHarnessLayer.id가 그 타입이다. 임의 데이터가 흘러드는 자리가 아니라 타입으로 닫혀 있고, 런타임에도 parseLayer가 집합 밖 값을 ContentValidationError로 거부한다. 한글 id가 들어오려면 튜플 자체를 고쳐야 하고, 그렇게 고쳐도 위의 전수 불변식이 /5240lab 경로에서 잡는다. 해당 항목을 "남은 우려"에서 지웠다.

검증

명령 결과
npx playwright test PASS — 120 passed (기준선 119 + 신규 1)
npm run verify:app PASS

주입은 전부 원복했고 git statustests/browser/technical-label.spec.ts 외에 변경이 없음을 확인했다. next-env.d.ts는 이 커밋 이전부터 dirty였으므로 손대지 않고 스테이징에서도 제외했다.