Skip to content

SEOJing F3 발표 PPTX export와 TTS bridge 검증#32

Merged
seoJing merged 7 commits into
mainfrom
feature/seojing-f3-pptx-tts-bridge
Jun 8, 2026
Merged

SEOJing F3 발표 PPTX export와 TTS bridge 검증#32
seoJing merged 7 commits into
mainfrom
feature/seojing-f3-pptx-tts-bridge

Conversation

@seoJing

@seoJing seoJing commented Jun 8, 2026

Copy link
Copy Markdown
Owner

작업 배경

F3 티켓은 단순히 PPTX 생성 가능성만 확인하는 것이 아니라, 기존 article slice를 발표용 scene으로 재구성하고 slide별/scene별 TTS script bridge를 연결할 수 있는지 검증하는 spike입니다.

변경 내용

  • pptxgenjs 기반 발표 export 스크립트 추가
    • pnpm --filter @app/web run export:presentation -- --slug <slug>
    • manifest.json.pptx 생성
  • MDX H1/H2/H3를 presentation scene으로 재구성하는 manifest 유틸 추가
    • scene title/summary/bullets/speakerScript/layoutHint 생성
    • TTS artifact manifest의 section transcript/audio path와 bridge
  • TTS section 추출/정제 안정성 보강
    • backtick/tilde fenced code block 내부 heading 오인 방지
    • tilde fence도 TTS text 정제에서 제거
  • spike 문서 추가
    • PPTX export 가능성, 제약, 다음 확장 방향 기록

데모 방법

pnpm --filter @app/web run export:presentation -- --slug study/backend/day1 --out-dir /tmp/seojing-presentation-export-smoke

생성 예시:

  • manifest: /tmp/seojing-presentation-export-smoke/study/backend/day1/manifest.json
  • pptx: /tmp/seojing-presentation-export-smoke/study/backend/day1/study__backend__day1.pptx

검증

  • pnpm --filter @app/web exec vitest run src/shared/presentation/presentation-export.test.ts src/shared/tts/tts-artifacts.test.ts — 11 tests pass
  • pnpm --filter @app/web run lint — pass
  • pnpm --filter @app/web run build — pass
  • smoke export: study/backend/day1 → 43 scenes / 43 slides / 43 notes 확인
  • touched-file prettier check — pass
  • git diff --check — pass
  • Open Code Review / Notjing gate — pass, latest OCR 0 comments

메모

  • 현재는 발표용 정적 요약 deck spike입니다. 웹 deck의 DOM/애니메이션을 그대로 캡처하지 않습니다.
  • MP3를 PPTX에 직접 임베드하지 않고, speaker notes 및 transcript/audio path bridge만 연결합니다.
  • 다음 단계에서는 featured/opt-in 글만 대상으로 HTML 모션 요약/브리핑 모드와 scene별 TTS script를 더 자연스럽게 연결하는 방향이 좋습니다.

Summary by CodeRabbit

  • 새로운 기능

    • 마크다운/MDX를 기반으로 슬라이드와 스피커 노트를 생성해 PPTX로 내보내기하는 명령 추가
    • 장면별 요약·불릿·발표 스크립트 및 TTS 연동 메타 포함된 내보내기 매니페스트 생성
  • 문서

    • PPTX 내보내기 및 TTS 브리지 스파이크 문서화
  • 개선사항

    • 코드 펜스(백틱/틸드) 인식 개선으로 헤딩 분할 정확도 향상
  • 테스트

    • 프레젠테이션·TTS 관련 동작 검증용 테스트 추가

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seoJing, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 3 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a66dc050-739f-49ea-8b3c-93a5f0d1d219

📥 Commits

Reviewing files that changed from the base of the PR and between dc5a874 and a349a17.

📒 Files selected for processing (2)
  • apps/web/src/shared/lib/code-fence.test.ts
  • apps/web/src/widgets/post-qa/PostQaPanel.tsx

Walkthrough

코드펜스 상태 추적을 도입해 MDX/Markdown에서 프리젠테이션 씬을 추출하고 manifest를 빌드한 뒤 CLI 스크립트로 manifest.json과 PPTX를 생성합니다. 관련 TTS 연동, 단위 테스트, 문서와 PresentationView/PostQaPanel 관련 UI·테스트 개선이 포함되어 있습니다.

Changes

PPTX 내보내기와 프리젠테이션 매니페스트

Layer / File(s) Summary
코드펜스 상태 추적 기반
apps/web/src/shared/lib/code-fence.ts
백틱/틸드 코드펜스를 구분·추적하는 CodeFenceStatenextCodeFenceState를 추가했습니다.
TTS 추출의 코드펜스 예외 처리
apps/web/src/shared/tts/tts-artifacts.ts, apps/web/src/shared/tts/tts-artifacts.test.ts
extractTtsSections가 라인별로 코드펜스 상태를 갱신해 코드블록 내부의 헤딩을 섹션 생성에서 제외하고, MDX 펜스 제거 정규식을 백틱/틸드 변형에 대응하도록 확장했습니다. 관련 테스트가 추가되었습니다.
프리젠테이션 export 계약 및 유틸
apps/web/src/shared/presentation/presentation-export.ts (초기 타입/유틸)
Export 버전, 씬/레이아웃 힌트 타입과 슬라이드 요약·문장 분리·슬러그화 등 유틸 및 인터페이스를 도입했습니다.
마크다운 씬 추출과 파싱
apps/web/src/shared/presentation/presentation-export.ts (147-227줄), apps/web/src/shared/presentation/presentation-export.test.ts (1-73줄)
extractPresentationScenes가 H1/H2/H3 기반 씬 경계를 만들고 코드펜스 내부 헤딩을 무시하며 headingId 중복을 해소합니다. 관련 단위 테스트가 추가되었습니다.
씬 보강 및 매니페스트 조립
apps/web/src/shared/presentation/presentation-export.ts (73-145줄, 229-306줄), apps/web/src/shared/presentation/presentation-export.test.ts (75-132줄)
씬별 summary, bullets, speakerScript, layoutHint 추론과 TTS 브리지 매핑을 수행해 PresentationExportManifest를 구성합니다. PPTX 지향 매니페스트 생성 시나리오를 검증하는 테스트가 포함됩니다.
CLI 실행과 PPTX 파일 생성
apps/web/scripts/export-presentation-pptx.ts, apps/web/package.json
npm script export:presentation 추가, pptxgenjs devDependency 추가, CLI 옵션 파싱(--slug/--out-dir/--json-only/--pptx-only), manifest 원자적 저장, addSceneSlide 기반 슬라이드 렌더링, pptxgenjs로 PPTX 저장과 오류 처리를 구현했습니다.
PPTX export 스파이크 문서
docs/seojing-pptx-tts-bridge-spike.md
설계·구현 링크·smoke 결과(생성 경로 및 43개 씬)·제한과 다음 단계를 문서화했습니다.

PostQaPanel 컴포넌트 수명 주기 개선

Layer / File(s) Summary
래퍼로의 리팩토링 및 로그 사전 로드
apps/web/src/widgets/post-qa/PostQaPanel.tsx
외부 래퍼로 변경해 props를 전달하고, log를 localStorage에서 초기 로드해 현재 slug로 필터링하도록 변경했습니다.
요청 abort 및 stale-response 방지
apps/web/src/widgets/post-qa/PostQaPanel.tsx
각 제출에서 이전 요청을 AbortController로 abort하고, requestSeq로 stale 응답을 무시하며 성공 응답을 slug-연동된 resultState로 저장하도록 처리 로직을 개편했습니다.
언마운트·abort 테스트 추가
apps/web/src/widgets/post-qa/PostQaPanel.test.tsx
질문 제출 직후 unmount() 시 fetch가 abort되고 QA analytics/로컬 로그에 stale side effect가 남지 않는지 검증하는 테스트가 추가되었습니다.

PresentationView 렌더링/스타일 단순화

Layer / File(s) Summary
레이아웃/소스 결정 및 스케일 계산
apps/web/src/widgets/presentation/PresentationView.tsx
getPresentationSource 우선순위를 data-presentation-content로 변경하고, padding/available/scale/slideW 계산을 재구성하며 전체화면 분기를 제거했습니다.
렌더링 단순화와 테마 변경
apps/web/src/widgets/presentation/PresentationView.tsx
덱/전체화면 조건부 UI를 없애고 중앙 슬라이드 영역만 렌더링하도록 단순화했으며, footer·버튼의 Tailwind 색상·테마를 그레이/다크 대응으로 변경했습니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • seoJing/SEOJing#31: PresentationView 및 PostQaPanel 관련 변경(레이아웃/요청 처리)과 중복 영역이 있어 연관성이 높습니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 주요 변경사항인 PPTX export와 TTS bridge 검증을 명확하게 요약하며, 변경사항과 직접적으로 관련이 있다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/seojing-f3-pptx-tts-bridge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented Jun 8, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 88.80597% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...web/src/shared/presentation/presentation-export.ts 86.31% 2 Missing and 11 partials ⚠️
apps/web/src/shared/lib/code-fence.ts 90.90% 0 Missing and 1 partial ⚠️
apps/web/src/widgets/post-qa/PostQaPanel.tsx 96.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 8, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
seojing a349a17 Jun 08 2026, 04:13 PM

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/shared/presentation/presentation-export.ts`:
- Around line 78-81: Extract the duplicate CodeFenceState interface and
nextCodeFenceState function into a single shared utility module (e.g., a
code-fence utility) and export them; then remove the local definitions and
import the shared CodeFenceState and nextCodeFenceState in the modules that
currently duplicate them so both use the same implementation; keep the function
signature and behavior unchanged, export the interface and function from the new
module, update imports in the files that previously declared them, and run type
checks to ensure no API or typing regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 61f2d47f-fb9c-4a17-9686-791c07d4d00e

📥 Commits

Reviewing files that changed from the base of the PR and between 0f003bd and 662d35a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • apps/web/package.json
  • apps/web/scripts/export-presentation-pptx.ts
  • apps/web/src/shared/presentation/presentation-export.test.ts
  • apps/web/src/shared/presentation/presentation-export.ts
  • apps/web/src/shared/tts/tts-artifacts.test.ts
  • apps/web/src/shared/tts/tts-artifacts.ts
  • docs/seojing-pptx-tts-bridge-spike.md

Comment thread apps/web/src/shared/presentation/presentation-export.ts Outdated
@seoJing seoJing force-pushed the feature/seojing-f3-pptx-tts-bridge branch from 662d35a to 0bfe78f Compare June 8, 2026 12:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/shared/presentation/presentation-export.ts`:
- Around line 268-277: The regex in inferLayoutHint allows unintended matches
because `<pre` and `data-code-block` lack boundaries; update the pattern inside
inferLayoutHint (the branch that currently uses
/(?:`{3,}|~{3,})|<pre|data-code-block/) to require boundaries for the HTML tag
and attribute/token (e.g. use `<pre\b` to avoid matching `<prefix` and
`\bdata-code-block\b` to avoid matching `data-code-blocks`), then run tests to
ensure code, image, and other layout detections still behave as expected.
- Around line 179-189: The current extractPresentationScenes code forces
duplicateCount to 0 for headingLevel===1 and never updates seenIds, causing
multiple H1s to share the same baseId; change the logic so duplicateCount =
seenIds.get(baseId) ?? 0 for all heading levels, always increment
seenIds.set(baseId, duplicateCount + 1) after computing duplicateCount, and
compute headingId as duplicateCount === 0 ? baseId : `${baseId}-${duplicateCount
+ 1}` so the second instance becomes `-2`. Also harden findTtsBridgeForScene so
it only matches title TTS bridges for scene.kind === "title" (e.g., compare
titleId when scene.kind === "title") and only matches section bridges for
scene.kind === "section" (compare sectionId), avoiding attaching section bridges
to title scenes; update matching order/conditions accordingly for correct TTS
pairing.

In `@apps/web/src/widgets/post-qa/PostQaPanel.tsx`:
- Around line 102-105: The remount via key in PostQaPanel causes requestSeq
increments to occur only on the new instance so in-flight requests from the old
instance can still complete and trigger side effects (safeWriteLog /
seojing:qa-interaction); remove the artificial remount (avoid using key on
PostQaPanelInner) and instead cancel in-flight requests on unmount inside
PostQaPanelInner by creating an AbortController for each request (attach its
signal to fetch/async calls), call controller.abort() in the component cleanup,
and ensure requestSeq and any side-effecting handlers (requestSeq, safeWriteLog,
seojing:qa-interaction) are bound to the same instance so aborted requests never
run their completion side effects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ad52d20f-868e-4452-8979-7c37c1dd7a67

📥 Commits

Reviewing files that changed from the base of the PR and between 662d35a and d5e55b7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • apps/web/package.json
  • apps/web/scripts/export-presentation-pptx.ts
  • apps/web/src/shared/lib/code-fence.ts
  • apps/web/src/shared/presentation/presentation-export.test.ts
  • apps/web/src/shared/presentation/presentation-export.ts
  • apps/web/src/shared/tts/tts-artifacts.test.ts
  • apps/web/src/shared/tts/tts-artifacts.ts
  • apps/web/src/widgets/post-qa/PostQaPanel.tsx
  • docs/seojing-pptx-tts-bridge-spike.md
✅ Files skipped from review due to trivial changes (1)
  • docs/seojing-pptx-tts-bridge-spike.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/src/shared/tts/tts-artifacts.test.ts
  • apps/web/src/shared/presentation/presentation-export.test.ts
  • apps/web/scripts/export-presentation-pptx.ts

Comment thread apps/web/src/shared/presentation/presentation-export.ts Outdated
Comment thread apps/web/src/shared/presentation/presentation-export.ts
Comment thread apps/web/src/widgets/post-qa/PostQaPanel.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/src/shared/lib/code-fence.test.ts (1)

4-21: ⚡ Quick win

엣지 케이스 테스트를 추가하면 견고성이 향상됩니다.

현재 테스트는 핵심 동작을 잘 검증하지만, 다음 엣지 케이스를 추가하면 TTS/프리젠테이션 파싱의 정확성을 더 보장할 수 있습니다:

  1. 선행 공백: " ```ts" 같은 들여쓰기된 펜스 마커 (정규식 ^\s*가 허용)
  2. 닫는 펜스 뒤 trailing 콘텐츠: "``` comment" 같은 경우 펜스가 닫히지 않아야 함 (정규식 \s*$ 요구사항)
  3. 더 긴 닫는 펜스: 5개 백틱이 3개 백틱 펜스를 닫는 경우 (length >= current.length 조건)
🧪 추천 테스트 케이스 예시
+  it("allows leading whitespace before fence markers", () => {
+    const opened = nextCodeFenceState(null, "  ```js");
+    expect(opened).toEqual({ char: "`", length: 3 });
+    expect(nextCodeFenceState(opened, "  ```")).toBeNull();
+  });
+
+  it("does not close fence when closing marker has trailing content", () => {
+    const opened = nextCodeFenceState(null, "```");
+    const stillOpen = nextCodeFenceState(opened, "``` some comment");
+    expect(stillOpen).toEqual(opened);
+    expect(nextCodeFenceState(stillOpen, "```")).toBeNull();
+  });
+
+  it("closes fence with longer marker", () => {
+    const opened = nextCodeFenceState(null, "```ts");
+    expect(nextCodeFenceState(opened, "`````")).toBeNull();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/shared/lib/code-fence.test.ts` around lines 4 - 21, Add
edge-case tests to apps/web/src/shared/lib/code-fence.test.ts for
nextCodeFenceState: include a test that ensures indented fence markers like " 
```ts" still open/close (leading whitespace allowed), a test that verifies a
closing marker with trailing content e.g. "``` comment" does NOT close the fence
(requires closing marker to be followed only by optional whitespace), and a test
that a longer closing marker (e.g. "`````") can close a shorter opened fence
(length >= current.length should close); name the tests clearly and use
nextCodeFenceState to assert opened vs still-open vs closed states.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/widgets/post-qa/PostQaPanel.tsx`:
- Around line 232-237: The finally block currently skips setPending(false) when
requestSeq.current !== currentRequestSeq, causing pending to stay true after
slug-triggered aborts; update the finally in PostQaPanel so it always clears the
pending state (call setPending(false) unconditionally) and only reset
requestAbortController.current when requestSeq.current === currentRequestSeq to
avoid clobbering newer requests; reference requestSeq, currentRequestSeq,
requestAbortController, and setPending in your change.

---

Nitpick comments:
In `@apps/web/src/shared/lib/code-fence.test.ts`:
- Around line 4-21: Add edge-case tests to
apps/web/src/shared/lib/code-fence.test.ts for nextCodeFenceState: include a
test that ensures indented fence markers like "  ```ts" still open/close
(leading whitespace allowed), a test that verifies a closing marker with
trailing content e.g. "``` comment" does NOT close the fence (requires closing
marker to be followed only by optional whitespace), and a test that a longer
closing marker (e.g. "`````") can close a shorter opened fence (length >=
current.length should close); name the tests clearly and use nextCodeFenceState
to assert opened vs still-open vs closed states.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a4f87710-8b2a-4685-80f1-9d7878cb88f0

📥 Commits

Reviewing files that changed from the base of the PR and between d5e55b7 and dc5a874.

📒 Files selected for processing (6)
  • apps/web/src/shared/lib/code-fence.test.ts
  • apps/web/src/shared/presentation/presentation-export.test.ts
  • apps/web/src/shared/presentation/presentation-export.ts
  • apps/web/src/widgets/post-qa/PostQaPanel.test.tsx
  • apps/web/src/widgets/post-qa/PostQaPanel.tsx
  • apps/web/src/widgets/presentation/PresentationView.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/shared/presentation/presentation-export.test.ts
  • apps/web/src/shared/presentation/presentation-export.ts

Comment thread apps/web/src/widgets/post-qa/PostQaPanel.tsx
@seoJing seoJing merged commit eef746a into main Jun 8, 2026
6 checks passed
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.

2 participants