blog: TS6 글 마감 + 렌더링·타입·통계 개선 모음#130
Conversation
@ssgoi/react 6.4에서 SsgoiTransition이 deprecated됨. 이 컴포넌트는 내부적으로 <div data-ssgoi-transition={id} className={...}>를 렌더할 뿐이라, 페이지 경계 div에 data-ssgoi-transition을 직접 부여하는 방식으로 치환. DOM 동일 → 동작 보존.
적용: home/posts/about/privacy 페이지 + PostClient. check-types 통과(5/5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5곳에 흩어진 <div data-ssgoi-transition={id}> 경계 마크업을 재사용 컴포넌트 PageBoundary로 통합. ssgoi 결합을 한 파일로 격리해 향후 전환 동작 조정 시 한 곳만 고치면 되게 함. 순수 마크업이라 use client 불필요 → server page에서도 그대로 사용.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
글 말미에 'tsconfig 점검' 액션 + 댓글 유도 단락 추가. 파일명의 따옴표·공백을 제거해 정리(slug는 frontmatter 기반이라 URL 영향 없음). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
블로그 글 수는 getAllPostSummaries().length로 빌드타임 산출. PR 승인 수는 Han5991/Han5991 프로필 레포의 config/summary.json(.mergedPRs)을 CI가 NEXT_PUBLIC_PR_COUNT로 주입하고, 로컬·실패 시 '58' 폴백. 컨퍼런스는 수동 상수. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ComponentPropsWithRef<'div'>를 extend해 표준 div 속성/ref를 받도록 타입 확장. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vitest 4.1이 expect()의 Assertion을 @vitest/expect 모듈로 옮기고 vitest는 re-export만 하도록 바뀌면서, @testing-library/jest-dom(6.9.1)의 declare module 'vitest' 보강이 매처를 못 붙여 tsc가 깨졌다(런타임은 정상). 실제 인터페이스가 사는 @vitest/expect의 Assertion을 직접 보강하는 vitest.d.ts 추가 + pnpm isolation으로 보강 대상이 앱에서 resolve되도록 @vitest/expect를 vitest와 동일한 4.1.4로 직접 의존성에 추가. 주의: @vitest/expect는 next.js의 vitest와 버전을 맞춰 올려야 보강이 유지된다. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
언어 태그 없는 ``` 블록이라 CodeBlock이 인라인 코드로 폴백 → <pre> 안에서 줄마다 배경 박스가 쪼개져 보였다. <file-tree> 전용 렌더러로 전환. 더불어 FileTree가 루트(depth 0)에 커넥터를 붙이지 않도록 개선해 표준 트리 모양(루트 bare)으로 그린다. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
언어 없는 ``` 블록이 인라인 <code>로 폴백해, 여러 줄이면 <pre> 안에서 줄마다 배경 박스가 쪼개졌다. 내부에 줄바꿈이 있으면 블록으로 렌더하도록 수정. trim 후 판별해 raw HTML 인라인 <code>가 앞뒤 줄바꿈을 끼어도(예: 본문 줄넘김) <p> 안에 <div>가 들어가는 hydration 오류를 막는다. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jest-dom 매처 보강 제네릭을 any 대신 E=unknown(기대값 인자 위치), R=T(반환)로 명시해 no-explicit-any를 정공법으로 통과. 빈 인터페이스 확장은 module augmentation에 구조적으로 필수라 no-empty-object-type만 인라인 비활성화. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이번 PR은 TS6 관련 글을 마무리하며 블로그 전반의 렌더링 안정성, 타입 시스템 보강, 그리고 통계 데이터의 자동화 연동을 포함한 종합적인 개선을 담고 있습니다. 주요 변경 사항은 사용자 경험을 위한 UI 컴포넌트 표준화와 빌드 도구 환경의 최적화에 초점을 맞추고 있습니다. Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
이번 풀 리퀘스트는 블로그 포스트의 마크다운 파일 구조 개선, SsgoiTransition을 대체하는 PageBoundary 컴포넌트 도입, 코드 블록 렌더링 및 파일 트리 렌더링 로직 개선, 그리고 Vitest 4.1+ 대응을 위한 타입 보강 작업을 포함하고 있습니다. 리뷰어는 PageBoundary 컴포넌트의 id 프로프가 표준 HTML id 속성과 충돌할 수 있으므로 transitionId 등으로 변경할 것과, 모노레포 내 의존성 일관성을 위해 apps/next.js 패키지의 vitest 버전을 업그레이드할 것을 제안했습니다.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| interface PageBoundaryProps extends ComponentPropsWithRef<'div'> { | ||
| /** | ||
| * `data-ssgoi-transition` 값 — 라우트 전환(hero/fade)을 매칭하는 키. | ||
| * 보통 해당 페이지 경로("/posts" 등)를 쓴다. | ||
| */ | ||
| id: string; | ||
| } | ||
|
|
||
| export const PageBoundary = ({ id, ...rest }: PageBoundaryProps) => ( | ||
| <div data-ssgoi-transition={id} {...rest} /> | ||
| ); |
There was a problem hiding this comment.
현재 PageBoundary 컴포넌트는 id 프로프를 받아 data-ssgoi-transition 속성으로 매핑하고 있습니다. 하지만 id는 HTML의 표준 속성(HTML standard attribute)이므로, 이 컴포넌트를 사용하는 곳에서 실제 HTML id 속성을 div 엘리먼트에 전달하고자 할 때 해당 값이 무시되거나 의도치 않게 전환 키로만 사용되는 문제가 발생할 수 있습니다.
표준 HTML 속성과의 충돌을 방지하고 컴포넌트의 예측 가능성을 높이기 위해, 프로프 이름을 transitionId 또는 routeId와 같이 명확하게 변경하는 것을 권장합니다.
| interface PageBoundaryProps extends ComponentPropsWithRef<'div'> { | |
| /** | |
| * `data-ssgoi-transition` 값 — 라우트 전환(hero/fade)을 매칭하는 키. | |
| * 보통 해당 페이지 경로("/posts" 등)를 쓴다. | |
| */ | |
| id: string; | |
| } | |
| export const PageBoundary = ({ id, ...rest }: PageBoundaryProps) => ( | |
| <div data-ssgoi-transition={id} {...rest} /> | |
| ); | |
| interface PageBoundaryProps extends ComponentPropsWithRef<'div'> { | |
| /** | |
| * `data-ssgoi-transition` 값 — 라우트 전환(hero/fade)을 매칭하는 키. | |
| * 보통 해당 페이지 경로("/posts" 등)를 쓴다. | |
| */ | |
| transitionId: string; | |
| } | |
| export const PageBoundary = ({ transitionId, ...rest }: PageBoundaryProps) => ( | |
| <div data-ssgoi-transition={transitionId} {...rest} /> | |
| ); |
| "@vitejs/plugin-react": "^5.0.4", | ||
| "@vitest/expect": "4.1.4", | ||
| "eslint": "^9", |
There was a problem hiding this comment.
현재 apps/next.js/package.json에서 vitest 버전은 여전히 ^4.0.0-beta.15로 유지되고 있는 반면, @vitest/expect는 4.1.4 버전으로 추가되었습니다.
모노레포 내의 의존성 일관성을 유지하고 잠재적인 타입 충돌이나 런타임 문제를 방지하기 위해, vitest 패키지 버전도 @blog/web 패키지와 동일하게 ^4.1.9 (또는 최소 4.1.4 이상)로 업그레이드하는 것을 권장합니다. vitest 버전을 올리면 @vitest/expect와의 버전 불일치로 인한 fragile한 상태를 방지할 수 있습니다.
References
- 모노레포 환경에서는 패키지 간 의존성 버전의 일관성을 유지하는 것이 중요합니다. (link)
Code ReviewThis PR finalizes the TS6 migration blog post (renaming + publishing it), refactors the Findings1. // current — rest can clobber id
<div data-ssgoi-transition={id} {...rest} />
// fix — explicit attribute wins
<div {...rest} data-ssgoi-transition={id} />
2. json=$(curl -fsSL https://raw.githubusercontent.com/... || true)
count=$(printf '%s' "$json" | jq -r '.mergedPRs // empty' 2>/dev/null || true)
[[ "$count" =~ ^[1-9][0-9]*$ ]] || count=''
echo "count=$count" >> "$GITHUB_OUTPUT"When [[ "$count" =~ ^[1-9][0-9]*$ ]] || { echo "⚠️ PR count fetch failed, using fallback" >&2; count=''; }3. // TestingLibraryMatchers<E, R>: E = 매처가 받는 기대값(인자 위치라 unknown이면 어떤
// 값이든 허용), R = 매처 반환 타입. Assertion<T>는 받은 값 T를 R로 돌려 체이닝을 유지한다.CLAUDE.md: "Don't explain WHAT the code does … Only add [a comment] when the WHY is non-obvious." The type-parameter explanation above restates what the type signature already says. The first paragraph (vitest 4.1 moved 4. // current: build array with depth-0 included, then discard it
const branches = ancestorContinues
.slice(1) // skip the root column we just built
.map(c => ...)
.join('');The 5. const isBlock = Boolean(match) || content.trim().includes('\n');A fenced code block containing only blank lines (e.g. 🤖 Generated with Claude Code |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code ReviewThis PR wraps up the TS6 blog post with a CTA section, migrates five pages from the deprecated All correctness-sensitive changes verified cleanly:
Three items worth addressing before merge:
|
vitest를 4.1.9로 정렬(블로그와 동일)하니 next.js 4.1.4 vs 블로그 4.1.9 이중 버전 충돌이 사라져, 공식 @testing-library/jest-dom/vitest 보강만으로 매처 타입이 정상 머지된다. 비표준 vitest.d.ts(@vitest/expect 직접 보강)와 @vitest/expect 직접 의존을 제거. 진짜 원인은 vitest의 re-export 구조가 아니라 모노레포 내 vitest 이중 버전이었다. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR Review — blog: TS6 글 마감 + 렌더링·타입·통계 개선전반적으로 잘 정리된 PR입니다. curl에 타임아웃 없음파일:
권장 수정: -json=$(curl -fsSL https://raw.githubusercontent.com/Han5991/Han5991/main/config/summary.json || true)
+json=$(curl -fsSL --max-time 10 --connect-timeout 5 https://raw.githubusercontent.com/Han5991/Han5991/main/config/summary.json || true)PR 수 폴백 로직이 CI와 컴포넌트에 분리됨파일: count 검증 실패 시 CI 한 곳으로 통합하는 것을 권장합니다: NEXT_PUBLIC_PR_COUNT: ${{ steps.pr_count.outputs.count || '58' }}그러면 컴포넌트에서 PageBoundary JSDoc이 CLAUDE.md 규칙 위반파일: CLAUDE.md: "Don't write multi-paragraph docstrings or multi-line comment blocks — one short line max." 현재 JSDoc 4줄 중 첫 두 문장은 prop 이름과 타입에서 이미 자명한 내용입니다. 비자명한 이유(HTML /** 페이지 전환 매칭 키 — HTML id 충돌을 피해 별도 이름 사용 */
transitionId: string;참고: cron 스케줄 오프셋 (pre-existing)이 PR에서 수정된 부분은 아니지만 연관성이 있어 언급합니다. CLAUDE.md와 코드 주석에는 "KST 09:00 (UTC 00:00) cron 자동 빌드"라고 되어 있지만, 전반적으로 코드 품질이 좋습니다. Generated with Claude Code |
|
/gemini review |
There was a problem hiding this comment.
Code Review
이번 풀 리퀘스트는 블로그 포스트의 파일 트리 형식을 커스텀 태그로 전환하고, SsgoiTransition 컴포넌트를 새로운 PageBoundary 컴포넌트로 대체하여 트랜지션 처리를 개선했습니다. 또한, 소개 페이지의 통계 수치를 동적으로 계산하도록 수정하고, 의존성 패키지(Vite 플러그인, Vitest 등) 버전을 업데이트했습니다. 리뷰어는 CodeBlock 컴포넌트에서 언어 지정이 없는 싱글 라인 fenced 코드 블록이 인라인 코드로 오인되어 스타일이 깨지는 버그를 지적하며, 원본 텍스트의 줄바꿈 여부를 판별 조건에 추가하는 구체적인 해결 방안을 제시했습니다.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| // 언어가 있거나, 언어 없이도 "내부에" 줄바꿈이 있으면(= fenced 코드블록) 블록으로 렌더. | ||
| // trim 후 판별하는 이유: raw HTML 인라인 <code>가 앞뒤로 줄바꿈을 끼고 작성될 수 있는데 | ||
| // (예: 본문에서 줄을 넘긴 `<code>\ntypes</code>`), 이를 블록으로 오인하면 <p> 안에 | ||
| // <div>가 들어가 hydration 오류가 난다. 진짜 인라인 코드는 내부 줄바꿈이 없다. | ||
| const isBlock = Boolean(match) || content.trim().includes('\n'); |
There was a problem hiding this comment.
현재 구현된 isBlock 판별 로직(content.trim().includes('\n'))은 raw HTML 인라인 <code> 태그로 인한 hydration 오류를 방지하기 위해 trim()을 수행하고 있습니다.
하지만 이로 인해 언어 지정이 없는 싱글 라인 fenced 코드 블록(예: ```\nnpm install\n```)의 경우, trim() 이후 내부 줄바꿈(\n)이 사라져 isBlock이 false로 판별됩니다. 결과적으로 블록이 아닌 인라인 <code>로 렌더링되어 코드 블록 스타일(윈도우 크롬, 복사 버튼 등)이 깨지는 부작용이 발생합니다.
해결 방안:
fenced 코드 블록은 항상 줄바꿈(\n)으로 끝나지만, raw HTML 인라인 <code>와 달리 줄바꿈으로 시작하지 않는다는 특징이 있습니다. 따라서 원본 children 문자열이 \n으로 끝나되 \n으로 시작하지 않는 조건을 추가하면, 싱글 라인 fenced 코드 블록도 올바르게 블록으로 판별할 수 있습니다.
const rawContent = String(children);
// 언어가 있거나, 언어 없이도 "내부에" 줄바꿈이 있으면(= fenced 코드블록) 블록으로 렌더.
// 단, single-line fenced 코드블록(예: 'npm install')의 경우 trim()을 하면 내부 줄바꿈이 없어 인라인으로 오인됩니다.
// 따라서 원본 텍스트가 줄바꿈으로 끝나되 줄바꿈으로 시작하지 않는 경우(fenced 블록의 특징)도 블록으로 판별합니다.
const isBlock =
Boolean(match) ||
content.trim().includes('\n') ||
(rawContent.endsWith('\n') && !rawContent.startsWith('\n'));
There was a problem hiding this comment.
Code Review
이번 PR은 블로그 포스트 내 폴더 구조 표현을 컴포넌트로 표준화하고, 기존 SsgoiTransition을 대체하는 PageBoundary 컴포넌트를 도입했습니다. 또한 소개 페이지의 통계 데이터를 동적으로 렌더링하도록 개선하고, 의존성 버전을 업데이트했습니다. 리뷰에서는 CodeBlock 컴포넌트에서 children이 문자열이 아닐 때 발생할 수 있는 비정상적인 문자열 변환 문제를 방지하기 위해 안전한 타입 가드를 적용하도록 제안하는 피드백이 제공되었습니다.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const rawContent = String(children); | ||
| const content = rawContent.replace(/\n$/, ''); |
There was a problem hiding this comment.
children이 문자열이 아닌 경우(예: undefined, null 또는 React 엘리먼트 객체 등) String(children)을 호출하면 "undefined", "null", "[object Object]"와 같은 의도치 않은 문자열이 생성됩니다.
특히 children이 React 엘리먼트인 경우, rawContent가 "[object Object]"가 되어 인라인 <code> 태그 내부에 실제 엘리먼트 대신 "[object Object]" 텍스트가 렌더링될 수 있습니다.
안전한 타입 가드를 적용하여 children이 문자열일 때만 값을 사용하고, 그렇지 않은 경우 빈 문자열('')로 처리하면 이러한 문제를 방지할 수 있습니다. 이 경우에도 인라인 렌더링 시에는 실제 {children} 엘리먼트가 그대로 전달되므로 안전하게 렌더링됩니다.
| const rawContent = String(children); | |
| const content = rawContent.replace(/\n$/, ''); | |
| const rawContent = typeof children === 'string' ? children : ''; | |
| const content = rawContent.replace(/\n$/, ''); |
gemini 리뷰 반영. children이 React 엘리먼트 등 비문자열이면 String(children)이 '[object Object]'가 되어 인라인 코드에 잘못된 텍스트가 들어갈 수 있다. typeof 가드로 문자열일 때만 rawContent를 채우고, 그 외엔 ''→인라인 분기에서 실제 {children} 엘리먼트를 그대로 렌더한다.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review — blog: TS6 글 마감 + 렌더링·타입·통계 개선 모음이 PR은 TS6 블로그 글 마감과 함께 ssgoi deprecation 대응 ( 🔴 버그 1 — cron 표현식과 주석이 불일치 (스케줄드 글이 9시간 늦게 공개됨)파일: cron 표현식이 수정: - cron: '0 0 * * *' # 매일 KST 09:00 (UTC 00:00)🟡 버그 2 —
|
|
/gemini review |
There was a problem hiding this comment.
Code Review
이번 풀 리퀘스트는 블로그 포스트의 디렉토리 구조 시각화를 위해 <file-tree> 컴포넌트를 적용하고, 기존 SsgoiTransition을 대체하는 PageBoundary 컴포넌트를 도입하여 각 페이지에 반영했습니다. 또한, CodeBlock 컴포넌트에서 하이드레이션 오류를 방지하도록 블록/인라인 렌더링 로직을 개선하였으며, 소개 페이지의 통계 데이터를 동적으로 계산하도록 수정했습니다. 그 외에 vitest 등 일부 의존성 버전을 업데이트하고 포스트 내용을 수정했습니다. 이번 변경 사항에 대해 제공할 피드백은 없습니다.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
PR Review — blog: TS6 글 마감 + 렌더링·타입·통계 개선 모음이 PR은 ssgoi deprecation 대응( 🔴 버그: cron 표현식이 의도와 9시간 다릅니다파일: # 매일 KST 09:00 (UTC 00:00) 자동 빌드 — 예약 발행(scheduled) 글 공개용
schedule:
- cron: '0 9 * * *' # ← 이건 UTC 09:00 = KST 18:00
PR이 직접 이 줄을 수정하진 않았지만 파일을 건드렸고, 주석이 명시적 의도를 담고 있어 여기서 수정하는 것이 적절합니다. 🟠 렌더링 버그:
|
코드 리뷰TS6 포스트 마감과 함께 ssgoi 추상화( 🔴 Bug 1 — 비-문자열
|
| 위치 | 판별 기준 | 언어 태그 없는 fenced 블록 결과 |
|---|---|---|
markdownBlocks.ts:isBlockMarkdownChild |
language-* className 존재 |
false → <p>로 감쌈 |
CodeBlock.tsx:isBlock |
rawContent.endsWith('\n') |
true → <div> 렌더 |
언어 태그 없는 ``` 블록은 react-markdown이 className 없이 전달하므로 isBlockMarkdownChild는 인라인으로 판단해 <p>로 감쌉니다. 그런데 CodeBlock 내부에서는 trailing \n으로 블록을 감지해 <div>를 렌더합니다 — <p> 안에 <div> 가 생겨 React hydration 오류가 발생합니다.
이는 정확히 이번 변경이 방지하려는 버그와 동일한 패턴입니다.
수정 방향: isBlockMarkdownChild에도 trailing \n 체크를 추가하거나, CodeBlock에서 isBlock 여부를 props로 노출해 두 판단 로직이 동일한 기준을 공유하도록 통일이 필요합니다.
🟡 Info — rawContent.endsWith('\n') 의존성
파일: apps/blog/web/src/components/post/CodeBlock.tsx:75
trailing \n 방식은 mdast-util-to-hast가 내부적으로 fenced code block value에 \n을 붙이는 구현 세부사항에 의존합니다. 이건 공개 API 계약이 아니라 내부 동작이므로 라이브러리 업데이트 시 조용히 깨질 수 있습니다.
더 견고한 방법은 react-markdown이 이미 props로 전달하는 hast node를 활용해 부모 노드가 pre인지 확인하는 것입니다.
✅ 나머지는 깔끔합니다
PageBoundary추상화: 5군데 일관되게 적용, 옳은 방향입니다FileTree.tsxslice(1)+ depth-0 guard: 로직 검증 완료, 의도대로 동작합니다- CI PR count 주입: 검증 + fallback 패턴이 잘 설계됐습니다
useViewCount정리: 순수 cosmetic 변경, 문제없습니다- vitest 4.1.9,
@vitejs/plugin-react6.x 업그레이드: 적절합니다
🤖 Generated with Claude Code
🔴 deploy-blog.yml cron '0 9'→'0 0': UTC 09:00(=KST 18:00)으로 돼 있어 예약 발행이 9시간 늦었다. 주석·CLAUDE.md가 명시한 KST 09:00(UTC 00:00)에 맞춤.
PageBoundary: data-ssgoi-transition을 {...rest} 뒤로 옮겨 호출부가 spread로 덮어쓰지 못하게(명시 속성 우선). FileTree: ancestorContinues 루프를 d=1부터 시작해 slice(1) 제거(등가). deploy-blog: PR count fetch 실패 시 ::warning::으로 stale 폴백 가시화. about: WHAT 나열 주석을 비자명한 부분만 남기고 축약.
decline: Prism 'text' 경고(react-syntax-highlighter가 미등록 언어를 graceful 처리—probe로 재현 안 됨), summary.json URL 핀(의도된 live-fetch+regex 검증), PageBoundary 테스트(trivial wrapper—보류).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug1: typeof 가드(': '')가 중첩 엘리먼트 children(raw HTML <code><span>x</span></code> 등)의 내용을 빈 문자열로 날려 languaged 블록이 공백 렌더될 수 있었다. codeText()로 재귀 추출해 String()의 '[object Object]'도 ''의 내용 손실도 막는다.
Bug2: isBlockMarkdownChild(언어 className 기준)와 CodeBlock.isBlock(endsWith('\n') 기준)이 언어 없는 블록에서 어긋나 <p> 안 <div> hydration 위험이 있었다. isBlockMarkdownChild를 CodeBlock과 동일 기준으로 통일.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR ReviewThis PR ships the TS6 blog post, migrates 5 pages from 🔴 Bug:
|
Code Review이 PR은 TS6 포스트 마감과 함께 ssgoi deprecation 대응( 1.
|
PR Review — blog: TS6 글 마감 + 렌더링·타입·통계 개선 모음이 PR은 TS6 글 마감 및 세 가지 주요 개선을 포함합니다: 🔴 버그 (잠재적 hydration 오류)
트리거 조건: 마크다운 소스에 raw HTML <code><em>foo</em>
</code>
결과: 수정 방법: // markdownBlocks.ts line 33
// 기존:
return typeof children === 'string' && children.endsWith('\n');
// 수정:
return codeText(children as ReactNode).endsWith('\n');두 함수가 같은 추출 로직을 공유하면 divergence가 없어집니다. 🟡 보안 (잠재적)
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
수정 방법: JSON.stringify(jsonLd).replace(/</g, '\\u003c')이는 Next.js 내부에서도 사용하는 표준 패턴입니다. 🟡 유지보수성블록 판별 로직의 중복 — 향후 divergence 위험
덤으로: ✅ 잘된 부분
요약: 블록 판별 로직 불일치(hydration 버그 잠재)와 JSON-LD 이스케이프(보안 잠재)를 수정하면 merge 준비가 됩니다. 🤖 Generated with Claude Code |
codeText·isBlockCode를 markdownCode.ts로 분리하고 CodeBlock이 import해 사용하도록 변경. 블록/인라인 판별 기준을 isBlockCode 하나로 단일화해 <p> 안 <div> hydration 오류 방지 의도를 한곳에서 관리한다. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…이스케이프 🔴 hydration: isBlockMarkdownChild를 isBlockCode로 위임해 CodeBlock과 블록 판별 기준을 단일화. raw HTML <code>의 비문자열(배열/엘리먼트) children을 codeText로 추출하고 language 정규식(/\blanguage-/ vs /language-(\w+)/) divergence도 함께 해소 → <p> 안 <div> hydration mismatch 재발 차단. 🟡 security: safeJsonLd() 헬퍼 추가, JSON-LD 6개 주입부에서 <를 <로 이스케이프해 값에 </script>가 들어갈 때 태그 조기 종료/XSS 방지(about/home/ posts 목록/포스트 상세). 회귀 테스트: markdownCode.test.ts(codeText·isBlockCode, 비문자열 children), jsonLd.test.ts(이스케이프·의미 보존), markdownBlocks.test.ts(code 비문자열 children). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Review이 PR은 TS6 블로그 글 마감을 중심으로 ssgoi deprecation 대응( 🔴
|
| 함수 | number 노드 처리 |
|---|---|
extractText |
String(n) — 숫자를 문자열로 변환 |
codeText |
return '' — 조용히 빈 문자열 |
codeText에 number 처리를 추가한 뒤 FileTree.tsx가 이를 재사용하면 중복과 divergence를 모두 제거할 수 있습니다.
export function codeText(node: ReactNode): string {
if (typeof node === 'string') return node;
+ if (typeof node === 'number') return String(node);
if (Array.isArray(node)) return node.map(codeText).join('');
if (isValidElement<{ children?: ReactNode }>(node))
return codeText(node.props.children);
return '';
}✅ 잘 된 부분
safeJsonLd:<치환은 Next.js 내부와 동일한 패턴이고, 라운드트립 테스트까지 추가한 점이 좋습니다.isBlockCode단일화:CodeBlock과isBlockMarkdownChild가 같은 함수를 공유해 hydration mismatch 근본 원인을 제거한 접근이 올바릅니다.- cron
0 9 → 0 0수정: 기존0 9 * * *은 UTC 09:00(= KST 18:00)으로 주석의 "KST 09:00"과 실제로 어긋나 있었는데 이번에 바로잡혔습니다. PageBoundary:ComponentPropsWithRef<'div'>사용은 React 19에서 plain function component에서도 ref prop이 정상 동작하므로 문제없습니다.- FileTree 루트 커넥터 제거:
renderTreeLine에서 depth=0 분기 추가 + 루프d=1시작은 정확합니다.
🤖 Generated with Claude Code
🔴 deploy-blog.yml: PR count 검증 정규식 ^[1-9][0-9]*$ → ^[0-9]+$. 기존 패턴은 mergedPRs가 0이면 유효한 숫자인데도 검증 실패 → count='' → about 페이지가 '58' 폴백을 표시했다. "숫자 여부" 검증 의도에 맞게 0도 통과시킨다. 🟡 codeText ↔ FileTree.extractText 중복 제거: codeText에 number 처리(String)를 추가하고, FileTree의 private extractText를 제거해 codeText를 재사용한다. 두 함수의 유일한 divergence(number를 String vs '')도 함께 해소. 테스트: markdownCode.test.ts의 number 단언을 ''→String으로 갱신. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR ReviewThis PR wraps up the TS6 upgrade post and bundles several rendering/type/statistics improvements: the Findings1. json=$(curl -fsSL https://raw.githubusercontent.com/Han5991/Han5991/main/config/summary.json || true)
Fix: json=$(curl --connect-timeout 10 --max-time 30 -fsSL \
https://raw.githubusercontent.com/Han5991/Han5991/main/config/summary.json || true)2.
Suggested check: confirm from 3. For inline code and language-untagged fenced blocks, Fix: pass the pre-computed text into const rawContent = codeText(children);
const content = rawContent.replace(/\n$/, '');
const isBlock = isBlockCode(rawContent, className); // or: expose isBlockCodeFromText(text, className)4. Comment length — CLAUDE.md: "one short line max" (multiple files) The project CLAUDE.md says: "Don't add error handling… Only add one [comment] when the WHY is non-obvious… never write multi-paragraph docstrings or multi-line comment blocks — one short line max." Several new docblocks exceed this:
These don't affect runtime but they accumulate comment debt. Items reviewed and found correct (no action needed)
🤖 Generated with Claude Code |
요약
TS6 "업그레이드인 줄 알았는데" 글을 마감하며 곁따라 발견·수정한 렌더링/타입/통계 개선을 모았습니다.
📝 글 콘텐츠
<file-tree>로, 맺음말 정리<file-tree>로 전환🎨 렌더링 수정
SsgoiTransitiondeprecation 대응 →data-ssgoi-transition을 재사용 컴포넌트PageBoundary로 추출 (home/posts/about/privacy/PostClient 5곳)trim판별로 raw inline<code>가<p>안에서<div>가 되는 hydration 오류 방지📊 about 통계 실시간 연동
getAllPostSummaries().length로 빌드타임 산출Han5991/Han5991)의config/summary.json(머지 PR 총수)을 CI가NEXT_PUBLIC_PR_COUNT로 주입, 로컬/실패 시 폴백summary.json출력 추가는 그 레포 main에 별도 반영 완료🔧 타입 / 도구
Assertion을@vitest/expect로 옮겨declare module 'vitest'보강이 안 붙던 것을@vitest/expect직접 augment + 동일 버전 deps 추가로 해결package.jsonengines 제거, useViewCount 정리, vitest/plugin-react 버전 bump✅ 검증
pnpm check-types전체 통과 (5/5)🤖 Generated with Claude Code