chore: establish TechLog migration baseline

This commit is contained in:
DongHyeonka
2026-08-15 17:49:40 +09:00
parent 05e3d50ba0
commit 034b702e8e
6 changed files with 951 additions and 0 deletions
@@ -0,0 +1,82 @@
# TechLog UI migration baseline
## Source provenance
- Source path: `/home/donghyeon/workspace/techlog-studio-frontend`
- Inspected: 2026-08-15 (Asia/Seoul)
- Git revision/status: unavailable. The supplied source directory is an exported
checkout with no `.git` metadata; both `git rev-parse HEAD` and
`git status --short` report that it is not a Git repository. The source path
and the byte checksums below are the reproducible provenance available for
this baseline.
## Approved copied assets
| Source and target-relative path | SHA-256 |
| --- | --- |
| `public/favicon.svg` | `e6d2e59b7b5bbb0342e0fb496dfc262decbfe4426bbb7b047aec8d467d1dc6f7` |
| `public/media/fetch-strategy-boundary.svg` | `b07926823ed77fc200f962a1f64a440e130cefa6bed31144b611612af9b02606` |
Only these two SVGs are approved for this baseline. They must remain exact
byte-for-byte copies of the source assets. The evidence key
`fetch-strategy-boundary` resolves to `/media/fetch-strategy-boundary.svg`
with dimensions `1080 × 420`, trigger label `Fetch Join과 Batch Fetch 비교
다이어그램 크게 보기`, and dialog label `Fetch Join과 Batch Fetch의 페이징 경계
확대`.
## Required dependency pins
| Package | Exact version |
| --- | --- |
| `pretendard` | `1.3.9` |
| `@fontsource/ibm-plex-mono` | `5.3.0` |
| `unified` | `11.0.5` |
| `remark-parse` | `11.0.0` |
| `remark-gfm` | `4.0.1` |
| `remark-directive` | `4.0.0` |
No Next.js, Vinext, or Cloudflare package is part of the migration baseline.
## Expected route inventory
| Layout | Route ID | Path |
| --- | --- | --- |
| PUBLIC | `TECH_LOG_HOME` | `/` |
| PUBLIC | `TECH_LOG_EXPLORE` | `/explore` |
| PUBLIC | `TECH_LOG_EXPLORE_KIND` | `/explore/:kind` |
| PUBLIC | `TECH_LOG_CASE` | `/cases/:slug` |
| PUBLIC | `TECH_LOG_REFERENCE` | `/references/:slug` |
| PUBLIC | `TECH_LOG_QUESTION` | `/questions/:slug` |
| PUBLIC | `TECH_LOG_TOPIC` | `/topics/:slug` |
| PUBLIC | `TECH_LOG_PROJECTS` | `/projects` |
| PUBLIC | `TECH_LOG_PROJECT` | `/projects/:slug` |
| PUBLIC | `TECH_LOG_PROJECT_RECORDS` | `/projects/:slug/records` |
| PUBLIC | `TECH_LOG_PROJECT_DECISIONS` | `/projects/:slug/decisions` |
| PUBLIC | `TECH_LOG_PROJECT_ACTIVITY` | `/projects/:slug/activity` |
| PUBLIC | `TECH_LOG_RELEASES` | `/releases` |
| PUBLIC | `TECH_LOG_RELEASE` | `/releases/:version` |
| PUBLIC | `TECH_LOG_PROFILE` | `/profile` |
| PUBLIC | `TECH_LOG_SEARCH` | `/search` |
| STUDIO | `TECH_LOG_STUDIO_HOME` | `/studio` |
| STUDIO | `TECH_LOG_STUDIO_DOCUMENTS` | `/studio/documents` |
| STUDIO | `TECH_LOG_STUDIO_DOCUMENT_NEW` | `/studio/documents/new` |
| STUDIO | `TECH_LOG_STUDIO_DOCUMENT_EDIT` | `/studio/documents/:id/edit` |
| STUDIO | `TECH_LOG_STUDIO_DOCUMENT_VALIDATION` | `/studio/documents/:id/validation` |
| STUDIO | `TECH_LOG_STUDIO_DOCUMENT_PREVIEW` | `/studio/documents/:id/preview` |
| STUDIO | `TECH_LOG_STUDIO_DOCUMENT_PUBLISH` | `/studio/documents/:id/publish` |
| STUDIO | `TECH_LOG_STUDIO_PUBLICATIONS` | `/studio/publications` |
| STUDIO | `TECH_LOG_STUDIO_PUBLICATION_PREVIEW` | `/studio/publications/:publicationEventId/preview` |
| STUDIO | `TECH_LOG_STUDIO_NOT_FOUND` | `/studio/*` |
| PUBLIC | `NOT_FOUND` | `*` |
## Source stylesheet inventory
- `app/globals.css`
- `app/studio.css`
- `app/studio-editor.css`
- `components/studio/workflow.module.css`
- `components/studio/publication-flow.module.css`
The inventories in this document are human review baselines. Automated
coverage is intentionally limited to the two SVG byte contracts, evidence
asset lookup behavior, and the package manager's frozen-lockfile verification.
+6
View File
@@ -123,11 +123,17 @@
"check:types:service-worker": "tsc --project tsconfig.service-worker.json"
},
"dependencies": {
"@fontsource/ibm-plex-mono": "5.3.0",
"@tanstack/react-query": "5.101.4",
"lucide-react": "1.25.0",
"pretendard": "1.3.9",
"react": "19.2.8",
"react-dom": "19.2.8",
"react-router-dom": "7.18.1",
"remark-directive": "4.0.0",
"remark-gfm": "4.0.1",
"remark-parse": "11.0.0",
"unified": "11.0.5",
"zod": "4.4.3"
},
"devDependencies": {
+729
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 19.2727C22 20.779 20.779 22 19.2727 22H14.7273C13.221 22 12 20.779 12 19.2727V12H19.2727C20.779 12 22 13.221 22 14.7273V19.2727Z" fill="#68C4FF"/>
<path d="M20 2C21.1046 2 22 2.89543 22 4V7C22 8.10457 21.1046 9 20 9H17C15.8954 9 15 8.10457 15 7V4C15 2.89543 15.8954 2 17 2H20Z" fill="#0C79D8"/>
<path d="M7 15C8.10457 15 9 15.8954 9 17V20C9 21.1046 8.10457 22 7 22H4C2.89543 22 2 21.1046 2 20V17C2 15.8954 2.89543 15 4 15H7Z" fill="#0C79D8"/>
<path d="M12 12H4.72727C3.22104 12 2 10.779 2 9.27273V4.72727C2 3.22104 3.22104 2 4.72727 2H9.27273C10.779 2 12 3.22104 12 4.72727V12Z" fill="#2E9EFF"/>
</svg>

After

Width:  |  Height:  |  Size: 712 B

+57
View File
@@ -0,0 +1,57 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1080" height="420" viewBox="0 0 1080 420">
<title>Fetch Join과 Batch Fetch의 페이징 경계</title>
<desc>Fetch Join은 전체 조인 결과를 읽은 뒤 메모리에서 20개를 고른다. Batch Fetch는 부모 20개를 먼저 고른 뒤 해당 ID의 컬렉션만 조회한다.</desc>
<rect width="1080" height="420" rx="18" fill="#FCFCFB"/>
<style>
.label { font: 600 18px Pretendard, sans-serif; fill: #17181B; }
.text { font: 500 15px Pretendard, sans-serif; fill: #3F4249; }
.muted { font: 500 13px Pretendard, sans-serif; fill: #686B72; }
.box { fill: #F7F7F5; stroke: #D9DBDE; stroke-width: 1.5; }
.bad { fill: #FFF4E8; stroke: #D99A4E; stroke-width: 1.5; }
.good { fill: #ECF6F2; stroke: #4F927F; stroke-width: 1.5; }
.arrow { stroke: #A5A8AE; stroke-width: 2; fill: none; marker-end: url(#arrow); }
</style>
<defs>
<marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
<path d="M0 0 L8 4 L0 8 Z" fill="#A5A8AE"/>
</marker>
</defs>
<text class="label" x="40" y="54">Collection Fetch Join</text>
<rect class="box" x="40" y="80" width="190" height="76" rx="10"/>
<text class="text" x="66" y="112">목록 + 컬렉션 JOIN</text>
<text class="muted" x="66" y="136">부모 × 자식 행</text>
<path class="arrow" d="M230 118 H286"/>
<rect class="bad" x="286" y="80" width="230" height="76" rx="10"/>
<text class="text" x="312" y="112">전체 Join 결과 로드</text>
<text class="muted" x="312" y="136">DB LIMIT 없음 · 1,961행</text>
<path class="arrow" d="M516 118 H572"/>
<rect class="box" x="572" y="80" width="220" height="76" rx="10"/>
<text class="text" x="598" y="112">부모 엔티티 복원</text>
<text class="muted" x="598" y="136">중복 부모 정리</text>
<path class="arrow" d="M792 118 H848"/>
<rect class="bad" x="848" y="80" width="192" height="76" rx="10"/>
<text class="text" x="874" y="112">메모리에서 20개</text>
<text class="muted" x="874" y="136">페이지 경계가 뒤에 있음</text>
<line x1="40" y1="210" x2="1040" y2="210" stroke="#E2E3E1"/>
<text class="label" x="40" y="258">Parent Paging + Batch Fetch</text>
<rect class="good" x="40" y="284" width="230" height="76" rx="10"/>
<text class="text" x="66" y="316">부모 목록 LIMIT 20</text>
<text class="muted" x="66" y="340">정렬과 페이지 경계 확정</text>
<path class="arrow" d="M270 322 H342"/>
<rect class="box" x="342" y="284" width="210" height="76" rx="10"/>
<text class="text" x="368" y="316">부모 ID 20개</text>
<text class="muted" x="368" y="340">현재 페이지 집합</text>
<path class="arrow" d="M552 322 H624"/>
<rect class="good" x="624" y="284" width="220" height="76" rx="10"/>
<text class="text" x="650" y="316">컬렉션 IN 조회</text>
<text class="muted" x="650" y="340">현재 부모만 로드</text>
<path class="arrow" d="M844 322 H900"/>
<rect class="box" x="900" y="284" width="140" height="76" rx="10"/>
<text class="text" x="926" y="316">화면 조립</text>
<text class="muted" x="926" y="340">비용 상한 설명 가능</text>
<text class="muted" x="40" y="396">관찰: 페이징이 적용되는 지점이 부모 조회 앞으로 이동한다.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,71 @@
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
const publicDirectory = resolve(process.cwd(), "public");
const approvedSvgAssets = [
{
relativePath: "favicon.svg",
sha256: "e6d2e59b7b5bbb0342e0fb496dfc262decbfe4426bbb7b047aec8d467d1dc6f7",
},
{
relativePath: "media/fetch-strategy-boundary.svg",
sha256: "b07926823ed77fc200f962a1f64a440e130cefa6bed31144b611612af9b02606",
},
] as const;
const localEvidenceAssets = {
"fetch-strategy-boundary": {
src: "/media/fetch-strategy-boundary.svg",
width: 1080,
height: 420,
triggerLabel: "Fetch Join과 Batch Fetch 비교 다이어그램 크게 보기",
dialogLabel: "Fetch Join과 Batch Fetch의 페이징 경계 확대",
},
} as const;
function getLocalEvidenceAsset(key: string) {
const asset = localEvidenceAssets[key as keyof typeof localEvidenceAssets];
if (!asset) {
throw new Error(`Unknown local evidence asset: ${key}`);
}
return asset;
}
function sha256(filePath: string) {
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
}
describe("TechLog migration baseline", () => {
it("preserves each approved SVG asset byte-for-byte", () => {
for (const asset of approvedSvgAssets) {
const assetPath = resolve(publicDirectory, asset.relativePath);
expect(existsSync(assetPath), asset.relativePath).toBe(true);
expect(sha256(assetPath), asset.relativePath).toBe(asset.sha256);
}
});
it("resolves the fetch strategy evidence to its published asset contract", () => {
expect(getLocalEvidenceAsset("fetch-strategy-boundary")).toEqual({
src: "/media/fetch-strategy-boundary.svg",
width: 1080,
height: 420,
triggerLabel: "Fetch Join과 Batch Fetch 비교 다이어그램 크게 보기",
dialogLabel: "Fetch Join과 Batch Fetch의 페이징 경계 확대",
});
const evidencePath = resolve(
publicDirectory,
getLocalEvidenceAsset("fetch-strategy-boundary").src.slice(1),
);
expect(existsSync(evidencePath)).toBe(true);
expect(sha256(evidencePath)).toBe(
"b07926823ed77fc200f962a1f64a440e130cefa6bed31144b611612af9b02606",
);
});
});