refactor: 분리되어 관리하고 있던 문서 시스템을 하나로 통일

This commit is contained in:
DongHyeonka
2026-09-04 18:56:01 +09:00
parent 4b9e7148b5
commit 43bccd08a8
121 changed files with 2861 additions and 534 deletions
@@ -0,0 +1,66 @@
가드가 실제로 잡는지 — 결함을 되돌려 확인
출처: tech-log-frontend @ 2b2f443 — 2026-09-04 실행
방법: 각 가드에 대해 결함을 되돌리고(sed), 테스트를 돌리고, git checkout 으로 원복한다.
가드는 넣는 것보다 「정말 잡는가」가 중요하다. 넣기만 하고 확인하지 않으면 늘 통과하는
테스트가 하나 늘 뿐이다.
======================================================================
### [A] public-path-reachability — 라우트 없는 주소를 링크로 그리지 않는가
정상:
✓ tests/features/tech-log/public-path-reachability.test.ts (18 tests) 12ms
Tests 18 passed (18)
결함 되돌림: resolvesToPublicRoute 가 '/' 로 시작하면 무조건 true 를 주게 한다
(= 가드가 없던 상태)
tests/features/tech-log/public-path-reachability.test.ts (18 tests | 3 failed) 14ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 3 ⎯⎯⎯⎯⎯⎯⎯
AssertionError: expected true to be false // Object.is equality
tests/features/tech-log/public-path-reachability.test.ts:38:7
AssertionError: expected true to be false // Object.is equality
tests/features/tech-log/public-path-reachability.test.ts:44:43
AssertionError: expected true to be false // Object.is equality
tests/features/tech-log/public-path-reachability.test.ts:49:59
원복 완료: 0 개 변경 남음
### [B] section-heading-rank — 나란히 서는 구역 제목의 급이 갈리는가
배경: 「구조별로 알게 된 것」만 26px·굵기 400 으로 나왔다. 규칙이 없었던 게 아니라
절반만 있었다 — 그 구역들이 크기만 각자 적어 두어 굵기를 아무도 정하지 않았고,
기본값 400 으로 떨어졌다.
정본: .section-heading-row h2 (30px / 650)
공유 규칙(globals.css:2482):
.document-relations h2,
.topic-variants h2 { margin: 0 0 20px; font-size: 30px; font-weight: 650; letter-spacing: -0.045em; }
정상:
✓ tests/features/tech-log/section-heading-rank.test.ts (1 test) 5ms
Tests 1 passed (1)
결함 되돌림: 그 공유 규칙에서 font-weight: 650 만 뺀다 (= 굵기를 아무도 정하지 않는 상태)
.document-relations h2,
.topic-variants h2 { margin: 0 0 20px; font-size: 30px; letter-spacing: -0.045em; }
tests/features/tech-log/section-heading-rank.test.ts (1 test | 1 failed) 10ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
AssertionError: .document-relations h2 의 font-weight 가 .section-heading-row h2 과 다르다
tests/features/tech-log/section-heading-rank.test.ts:47:14
Tests 1 failed (1)
원복: 남은 변경 0 개
======================================================================
결론
[A] public-path-reachability 결함 되돌림 → 3건 실패 ✓
[B] section-heading-rank 결함 되돌림 → 1건 실패 ✓
[C] route-chunk-names 결함 되돌림 → 1건 실패 ✓
셋 다 되돌리면 실제로 빨개진다. 모두 git checkout 으로 원복했고 작업 트리에 변경은
남지 않았다.
메모: [C] 는 처음에 잘못된 문자열(TECH_LOG_STUDIO_TOPIC_EDIT)을 지워 통과했다. vite 설정의
표는 라우트 id 가 아니라 「화면 파일 경로 → chunk 이름」이라 그 이름이 없다. 올바른 줄
("/studio/pages/topic-edit-page.tsx")을 지우자 잡혔다. 검증을 검증해야 하는 이유다 —
"되돌렸는데 통과했다"를 "가드가 없다"로 읽을 뻔했다.
@@ -0,0 +1,72 @@
「손으로 나열한 목록」이 표로 바뀌었나 — 현재 코드
출처: tech-log-frontend @ 2b2f443 / tech-log-backend @ 8cd8ee3
조회: 2026-09-04
§3 의 주장: 종류를 나열하는 자리를 Record<Kind,_> 나 sealed switch 식으로 바꿔
새 종류가 늘면 컴파일러가 빈 자리를 잡게 했다.
======================================================================
### 프론트 — Record<Kind, _> 로 바뀐 자리
src/features/tech-log/presentation/public/components/explore-filter-form.tsx:12: 목록을 손으로 적지 않고 `Record<RecordKind, …>` 에서 뽑는다. 종류가 늘면 이 표가 비어 있는
src/features/tech-log/presentation/public/components/explore-filter-form.tsx:16:const EXPLORE_KIND_ORDER: Record<RecordKind, number> = {
src/features/tech-log/presentation/shared/document-kind-labels.ts:18: * 타입이 잡는다 — `Record<RecordKind, string>` 이므로 빠진 종류가 있으면 컴파일되지 않는다.
src/features/tech-log/presentation/shared/document-kind-labels.ts:20:export const DOCUMENT_KIND_LABELS: Record<RecordKind, string> = {
src/features/tech-log/presentation/shared/document-kind-labels.ts:35:export const DOCUMENT_KIND_STATES: Record<RecordKind, "CONFIRMED" | "SETTLED" | "OPEN"> = {
src/features/tech-log/presentation/shared/document-kind-labels.ts:53:export const EXPLORE_KIND_PATHS: Record<RecordKind, string> = {
src/features/tech-log/presentation/studio/components/document-list.tsx:15: 작업본 목록이 거를 수 있는 종류. 손으로 나열하지 않고 `Record<RecordKind, …>` 에서 뽑는다 —
src/features/tech-log/presentation/studio/components/document-list.tsx:18:const STUDIO_KIND_ORDER: Record<RecordKind, number> = {
src/features/tech-log/adapters/mock/validate-working-copy.ts:58: const BRANCH_FIELDS: Record<RecordKind, string[]> = {
src/features/tech-log/adapters/http/http-management-gateway.ts:153: 아래 표가 `Record<DeletableDocumentKind, …>` 인 것이 실제로 종류를 강제하는 지점이다.
src/features/tech-log/adapters/http/http-management-gateway.ts:155: const OPERATIONS: Record<DeletableDocumentKind, string> = {
src/features/tech-log/adapters/http/http-public-content-gateway.ts:186: const OPERATIONS: Record<RecordKind, string> = {
### 프론트 — 남아 있는 종류 삼항 사슬이 있나 (있으면 여기 나온다)
src/features/tech-log/presentation/studio/components/publication-event-preview-screen.tsx:157: renderModel.kind === "CASE" ? renderModel.bodyBlocks : [],
src/features/tech-log/adapters/mock/validate-working-copy.ts:71: const stringFields = ["title", "slug", "summary", ...(kind === "CASE" ? ["problem", "conclusion", "environment", "reproduction", "bodyMarkdown"] : []), ...(kind === "CONCEPT" ? ["bodyMarkdown", "basisVersion"] : []), ...(kind === "REFERENCE" ? ["purpose"] : []), ...(kind === "QUESTION" ? ["nextValidation"] : []), ...(kind === "PROJECT_DECISION" ? ["statement", "rationale"] : [])];
### 백엔드 — sealed switch 를 식으로 쓴 자리 (컴파일러가 빠진 가지를 요구한다)
../tech-log-backend/src/application-core/src/main/java/dev/caskeleton/application/techlog/studio/model/PublicPaths.java:25: return switch (kind) {
../tech-log-backend/src/application-core/src/main/java/dev/caskeleton/application/techlog/studio/model/PublicPaths.java-26- case CASE -> "/cases/" + slug;
../tech-log-backend/src/application-core/src/main/java/dev/caskeleton/application/techlog/studio/model/PublicPaths.java-27- case REFERENCE -> "/references/" + slug;
### 백엔드 — PublicSql.pathOf 에 CONCEPT 이 들어갔나 (13번째 사례)
static String pathOf(String resourceType, String slug, String projectSlug) {
return switch (resourceType) {
case "CASE" -> "/cases/" + slug;
case "REFERENCE" -> "/references/" + slug;
case "QUESTION" -> "/questions/" + slug;
case "CONCEPT" -> "/concepts/" + slug;
case "PROJECT" -> "/projects/" + slug;
case "PROJECT_DECISION" ->
projectSlug == null ? null : "/projects/" + projectSlug + "/decisions#" + slug;
case "RELEASE" -> "/releases/" + slug;
default -> null;
};
}
======================================================================
정직한 평가 — 이 증거가 드러내는 남은 구멍 둘
[1] PublicSql.pathOf 는 여전히 컴파일러가 강제하지 못한다
switch 의 대상이 sealed enum(RecordKind)이 아니라 String(resourceType)이다.
그래서 `default -> null` 이 남아 있고, 새 종류를 더할 때 이 자리를 빠뜨리면
컴파일은 통과하고 경로가 null 로 나간다 — §3 이 경고하는 바로 그 모양이다.
같은 파일의 PublicPaths.forKind 는 RecordKind 로 switch 하므로 강제된다.
둘의 차이는 pathOf 가 공개 투영의 resource_type 을 다루기 때문이다. 그 칸은
RecordKind 에 없는 값(PROJECT, RELEASE)도 담는다 — 그래서 String 이다.
지금은 PublicPathsTest 가 RecordKind 전수를 돌며 막고 있지만, pathOf 만 쓰는
경로(홈 focus 의 recentDecision)는 그 테스트가 닿지 않는다.
[2] validate-working-copy.ts 의 stringFields 는 아직 삼항 사슬이다
다만 모양이 다르다 — 배타적 사슬이 아니라 「종류마다 칸을 더한다」는 가산형이다.
종류를 빠뜨리면 "잘못된 분기로 떨어진다"가 아니라 "그 종류의 추가 칸을 검사하지
않는다"가 된다. 덜 위험하지만 조용하기는 마찬가지다.
같은 파일의 BRANCH_FIELDS 는 Record<RecordKind, string[]> 로 바뀌어 있다(58줄).
그쪽이 실제로 종류를 강제하는 자리다.
즉 §3 의 「표로 바꿨다」는 대부분 사실이지만 전부는 아니다. 위 둘은 남아 있다.