Skip to content

[feat/MAT-272] content-renderer 패키지 개발#268

Merged
sterdsterd merged 36 commits intodevelopfrom
feat/mat-272-content-renderer
Apr 13, 2026
Merged

[feat/MAT-272] content-renderer 패키지 개발#268
sterdsterd merged 36 commits intodevelopfrom
feat/mat-272-content-renderer

Conversation

@sterdsterd
Copy link
Copy Markdown
Collaborator

Summary

학생 앱에서 TipTap 기반 수학 콘텐츠를 렌더링하는 공용 WebView 패키지 @repo/pointer-content-renderer를 신규 추가합니다. 한 화면에 20+개까지 존재하던 WebView를 단일 WebView로 통합하며, 하나의 번들에서 document / chat / overview 3가지 렌더링 모드를 지원합니다.

기존 ProblemViewer의 수식 조판 CSS(테이블 ①②③ 보기 넘버링, blockquote 조건박스, highlight 변수 등)를 그대로 이관하면서, WebView 내부에 채팅 순차 진행·오버뷰 탭 네비게이션·북마크 상호작용까지 포함했습니다.

Linear

Changes

New package: @repo/pointer-content-renderer

  • Vite + vite-plugin-singlefile로 단일 index.html을 빌드 (JS/CSS 인라인, KaTeX/Pretendard/KoPub CDN만 외부 참조)
  • TipTap JSON 직렬화기(serializeJSONToHTML, serializeNodeToHTML)를 5개 파일로 분리 이관. 로직 변경 없음. 숫자 attr(start, colspan, rowspan)과 type 화이트리스트로 attribute injection 차단.
  • hardBreak 노드 <br> 대응

3가지 렌더링 모드

  • document: 기존 ProblemViewer 대체. ResizeObserver + debounce 기반 height 리포트, fontStyle / backgroundColor / padding prop 지원. KoPub Batang을 기본 serif로 사용.
  • chat: 포인팅 시나리오를 순차 진행 (typing indicator — 길이 기반 3단계 타이밍, 버블 등장 애니메이션, 네/아니오 버튼 버블 내 배치).
    • Resume 기능: userAnswers를 init에 전달하여 부분 완료 상태부터 이어서 진행. 완료된 phase는 static 재생(애니메이션/typing 없음), 미완료 phase부터 interactive.
    • 답변 직후 answer 이벤트(fire-and-forget) emit, 전체 완료 시 complete emit.
    • Sticky-to-bottom intent tracking으로 수식 버블의 KaTeX 렌더링 후 높이 증가에도 자동 스크롤 유지. 사용자가 위로 올리면 추적 중단.
  • overview: 카드(default/pointing/collapsible), plain, chat, divider 섹션 혼합. variant로 summary(학습 마무리) / pointing(포인팅 전체보기) 분기.
    • 탭 네비게이션: label 있는 섹션 + divider 섹션을 탭으로 자동 생성. sticky 탭 바, 탭 클릭 → 섹션 스크롤, 스크롤 → 활성 탭 동기화 (scroll 이벤트 + rAF).
    • Collapsible 카드: scrollHeight 기반 정확한 max-height transition, transition 중 클릭 차단.
    • 북마크: optimistic update + pending-lock + requestId echo로 다중 실패/out-of-order result race 차단.

Bridge 프로토콜

  • RN → WebView: init (mode별 payload + chat의 userAnswers, document의 padding), bookmarkResult
  • WebView → RN: bridgeReady (리스너 준비 완료 신호), ready, height, complete, answer, bookmark
  • bridgeReady 도입으로 기존 RN/Web 상호 대기 deadlock 해소. 매 bridgeReady마다 init 재주입 가능.
  • initMessage prop 변경 시 자동 재주입 (useEffect 추적)

Render lifecycle

  • disposeCurrentRender + currentRenderId 기반 stale render 가드. Document는 detached fragment에 렌더 후 isCurrent 확인하여 live DOM commit. Overview는 루프 내 isCurrent 체크. Chat은 AbortSignal로 pending
    delay/yes-no wait 중단.
  • KaTeX 로드 방어: <script onload/onerror>로 즉시 상태 캡처, 수식 없으면 wait skip, 실패 시 raw LaTeX fallback.

Native 컴포넌트 & 통합

  • ContentWebView: mode별 scrollEnabled / height / background 분기. htmlSource prop으로 asset 주입(consumer가 asset 소유).
  • useContentBridge hook: ref + 메시지 송수신 관리.
  • apps/native/assets/webview/content.html은 build-time generated asset으로 취급 (.gitignore 추가).
    • apps/nativesync-webview-html 스크립트가 패키지 빌드 + 복사 담당
    • prestart / preios / preandroid / preweb 훅으로 로컬 실행 시 자동 동기화
    • EAS 원격 빌드 시 eas-build-post-install 훅으로 자동 동기화
  • metro.config.js:
    • resolver.assetExtshtml 추가
    • resolveRequestreact / react-native / react-native-webview 경로를 앱 node_modules로 강제 고정 → monorepo에서 React 이중 인스턴스로 인한 "Invalid hook call" 차단

Dev 환경

  • 브라우저 dev 패널: document / chat(5종) / overview(summary, pointing L, pointing R) 모드 전환 버튼
  • Chat resume 시나리오 4종 mock data 포함

Testing

  • pnpm build --filter=@repo/pointer-content-renderer 빌드 통과, dist/index.html ~36KB 단일 파일
  • pnpm typecheck 통과
  • 브라우저 dev 서버(pnpm dev)에서 3가지 모드 + chat resume 4종 + overview 2 variant 수동 검증
  • test/content-renderer-smoke 브랜치에서 실제 RN 앱(ContentWebView) 통합 smoke test 수행:
    • SegmentedControl로 모드 전환하며 각 모드의 렌더링, typing indicator, 네/아니오 → answer/complete 이벤트, 탭 네비게이션, 북마크, collapsible 동작 확인
    • Overview pointing variant는 side-by-side 2개 WebView 동시 렌더로 확인
    • (해당 테스트 브랜치는 삭제됨)

Risk / Impact

  • 영향 범위: 신규 패키지 추가. 기존 ProblemViewer와 그 사용처는 변경하지 않아 현재 기능은 영향 없음. apps/nativeresolver.assetExtsresolveRequest 확장, 신규 sync-webview-html 스크립트가 추가됨.
  • 확인이 필요한 부분:
    • .pnpmreact@16.14.0이 남아있는 것으로 확인됨 — resolveRequest 강제 경로 고정으로 해결했으나, 장기적으로 pnpm why react@16.14.0으로 원인 제거 권장.
    • EAS 빌드 환경에서 eas-build-post-install 훅이 실제 호출되는지는 실배포 시 확인 필요.
  • 배포 시 유의사항:
    • 이 PR은 패키지 추가만 수행하며 실제 사용처 교체는 별도 PR에서 진행 예정 → 이 PR만으로는 유저 impact 없음.
    • apps/native metro.config 변경이 포함되므로 앱 Metro 캐시 초기화(pnpm start -c) 1회 필요할 수 있음.
    • 최초 체크아웃 시 pnpm install + pnpm sync-webview-html(또는 pnpm startprestart 훅) 실행 필요.

Screenshots / Video

Screen.Recording.2026-04-13.at.21.18.29.mp4

sterdsterd and others added 27 commits April 12, 2026 14:37
Create Vite + vanilla-ts package with singlefile build for WebView content rendering.
Includes serializer migration (split into 5 files), shared types, bridge protocol,
core modules (CSS, math-renderer, text-length), and base styles migrated from ProblemViewer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Document mode: serializer-based rendering with ResizeObserver height reporting
- Chat mode: sequential pointing flow with typing indicator, yes/no gates, expand content
- Overview mode: card/plain/chat/divider sections with IntersectionObserver and bookmark support
- Main entry point with mode dispatch and bridge message routing
- Dev panel with mock data for browser-based testing of all 3 modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ContentWebView: mode-aware WebView with scroll/height behavior per mode
- useContentBridge: message handling hook for RN ↔ WebView communication
- Add @repo/pointer-content-renderer workspace dependency to native app
- Create assets/webview directory for build output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ibleSection bridge messages

- Sticky tab bar with horizontal scroll, built from sections with label or divider type
- Tab click scrolls to section with sticky offset compensation
- IntersectionObserver syncs active tab on scroll
- Remove scrollToSection (RN→WebView) and visibleSection (WebView→RN) from bridge protocol
- Update native ContentWebView and useContentBridge to match new protocol

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…y when at bottom

- Overview: remove position:fixed/inset:0 from .overview-mode so normal document
  scroll works with window.scrollTo and IntersectionObserver
- Chat: scrollToBottom() now checks if user is within 100px of bottom before
  scrolling, preserving scroll position when reading earlier messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace point-in-time isNearBottom() check with persistent sticky state:
- Track user scroll intent via scroll event listener
- Programmatic scrolls are flagged to avoid falsely disabling sticky mode
- Re-scroll after renderMath() to catch KaTeX-induced height changes
- Fixes issue where math content bubbles would break auto-scroll chain

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lit mock data per pointing

- Chat controller: always show divider including first pointing
- Chat renderer renderAllBubbles: skip first divider (overview provides its own)
- Overview controller: replace IntersectionObserver with scroll-event-based active tab detection
- Mock data: split single multi-pointing chat section into per-pointing divider+chat pairs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iner

- Chat controller: showWithTypingIndicator and showFixedMessage return bubble element
- waitForYesNo appends buttons inside the target bubble
- renderAllBubbles uses renderStaticYesNo for overview (disabled + selected highlight)
- Question yes/no goes inside last question bubble, confirm yes/no inside fixed message bubble

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added new variants for overview mode: 'summary' and 'pointing'
- Updated tab bar to support sticky behavior with edge-to-edge styling
- Introduced collapsible card variant for overview sections
- Adjusted chat bubble styles for better alignment and readability
- Refactored font and color tokens for consistent theming
- Removed unused mock-data functions and improved mock section structure
Introduce `AbortSignal` support for async operations to enable better
cancellation handling. Refactor rendering logic to include cleanup
functions for improved memory management and DOM consistency.
- Added `sync-webview-html` script to generate WebView HTML asset
- Updated `.gitignore` to exclude generated WebView HTML file
- Modified `metro.config.js` to include `.html` in asset extensions
- Updated `ContentWebView` to accept dynamic HTML sources
- Adjusted `pointer-content-renderer` build script to remove direct
  asset copy
- Added global flags to detect KaTeX load success or failure.
- Implemented timeout fallback for slow/offline CDN.
- Updated `renderMath` to fallback to raw LaTeX if KaTeX is unavailable.
logic

Replace inline bookmark button state management with a shared utility
function `setBookmarkButtonState`.
prevent race condition

Add `bookmarked` field to `bookmarkResult` message for better state
tracking. Update `handleBookmarkResult` to conditionally rollback
optimistic updates only if the current state matches the failed attempt.
scenarios

Add validation to skip pointings with no questionNodes, preventing
crashes.
renderer

Ensure answers are keyed by pointingId to handle reordering, filtering,
or missing entries without causing misalignment in chat scenarios.
Ensure initMessage is re-injected when it changes, provided the
bridge is ready. Added useEffect to handle updates and improved
bridgeReady handling with refs.
Introduce `safePositiveInt` utility to validate and constrain numeric
attributes like `start`, `colspan`, and `rowspan`. Update serializers
to use this function for safer handling of input values.
Introduce `requestId` to bookmark messages for deduplication and
state management. Updated native and web implementations to handle
this new parameter, ensuring proper rollback and re-enabling of
buttons after failed attempts.
Implements per-section bookmark state handling to manage in-flight
requests, prevent stale results, and ensure UI consistency.
handling

- Introduced support for chat resume scenarios with partial user
  answers.
- Added `onAnswer` callback to handle per-step answer events.
- Updated `RNToWebViewMessage` and `WebViewToRNMessage` types for new
  payloads.
- Enhanced `runChatScenario` to support static rendering and answer
  tracking.
Workspace package @repo/pointer-content-renderer was loading a different
physical react path than the app, causing "Invalid hook call" errors when
hooks inside the package were invoked.

- metro.config.js: force resolveRequest to redirect react / react-native /
  react-native-webview (and their sub-paths) through the app's package.json
  so every consumer resolves to the same realpath.
- pointer-content-renderer/package.json: declare react / react-native /
  react-native-webview as peerDependencies to document the requirement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sterdsterd sterdsterd requested a review from Copilot April 13, 2026 12:34
@linear
Copy link
Copy Markdown

linear bot commented Apr 13, 2026

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pointer-admin Ready Ready Preview, Comment Apr 13, 2026 5:04pm

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

학생 앱에서 TipTap 기반 수학 콘텐츠를 렌더링하기 위한 신규 공용 WebView 패키지 @repo/pointer-content-renderer를 추가하고, 앱(apps/native)에서 HTML 번들 동기화 및 Metro 설정을 통해 단일 WebView 기반 렌더링을 가능하게 하는 PR입니다.

Changes:

  • 신규 패키지 packages/pointer-content-renderer 추가(Vite singlefile 빌드 + document/chat/overview 모드 + RN↔WebView 브리지)
  • apps/native에 WebView HTML 번들 sync 스크립트/훅 및 Metro resolver 설정(React/RN dedupe, html assetExts) 추가
  • TipTap JSON → HTML serializer 및 KaTeX 렌더링/스타일(CSS) 이관

Reviewed changes

Copilot reviewed 36 out of 38 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pnpm-lock.yaml 신규 패키지/의존성 추가에 따른 lockfile 갱신
packages/pointer-content-renderer/vite.config.ts Vite + singlefile 기반 index.html 단일 번들 빌드 설정
packages/pointer-content-renderer/tsconfig.native.json RN(native) 측 타입체크 설정 추가
packages/pointer-content-renderer/tsconfig.json Web/dev 포함 TS 타입체크 설정 추가
packages/pointer-content-renderer/src/web/modes/overview/overview.css overview 모드(탭/카드/북마크/디바이더) 스타일 정의
packages/pointer-content-renderer/src/web/modes/overview/overview-renderer.ts overview 섹션 렌더러(카드/플레인/채팅/디바이더) 구현
packages/pointer-content-renderer/src/web/modes/overview/overview-controller.ts overview 탭 네비게이션/스크롤 동기화 + bookmarkResult 처리
packages/pointer-content-renderer/src/web/modes/overview/bookmark-state.ts 북마크 optimistic + pending lock + requestId 기반 상태관리
packages/pointer-content-renderer/src/web/modes/overview/bookmark-icons.ts 북마크 버튼 아이콘/active 상태 토글 유틸
packages/pointer-content-renderer/src/web/modes/document/document.css document 모드 스크롤/overflow 제어 스타일
packages/pointer-content-renderer/src/web/modes/document/document-renderer.ts document 모드 렌더 + ResizeObserver 기반 높이 리포트
packages/pointer-content-renderer/src/web/modes/chat/typing-indicator.ts typing indicator 및 abortable delay 유틸
packages/pointer-content-renderer/src/web/modes/chat/scroll.ts sticky-to-bottom intent 기반 스크롤 제어 유틸
packages/pointer-content-renderer/src/web/modes/chat/chat.css chat UI(버블/선택지/typing) 스타일 정의
packages/pointer-content-renderer/src/web/modes/chat/chat-renderer.ts chat 버블/디바이더/정적 yes-no 렌더링 유틸
packages/pointer-content-renderer/src/web/modes/chat/chat-controller.ts chat 시나리오 실행(typing, resume, answer 이벤트 emit)
packages/pointer-content-renderer/src/web/main.ts init 메시지 기반 모드 분기 렌더 + stale render 가드 + bridgeReady
packages/pointer-content-renderer/src/web/index.html KaTeX/폰트 CDN 포함 WebView 엔트리 HTML
packages/pointer-content-renderer/src/web/core/text-length.ts typing 타이밍 산정을 위한 텍스트 길이/이미지 포함 여부 계산
packages/pointer-content-renderer/src/web/core/styles/base.css 공용 타이포/테이블 넘버링/blockquote 등 기본 스타일 이관
packages/pointer-content-renderer/src/web/core/serializer/utils.ts serializer 유틸(escape/safePositiveInt/marks compare)
packages/pointer-content-renderer/src/web/core/serializer/nodes.ts block 노드(table/list/blockquote/hr 등) 직렬화
packages/pointer-content-renderer/src/web/core/serializer/marks.ts mark(bold/italic/underline/highlight 등) 직렬화
packages/pointer-content-renderer/src/web/core/serializer/inline.ts inline(text/math/br/image 등) 직렬화 및 mark grouping
packages/pointer-content-renderer/src/web/core/serializer/index.ts JSON(doc/node) → HTML 직렬화 진입점
packages/pointer-content-renderer/src/web/core/math-renderer.ts KaTeX 로드 대기/실패 fallback 포함 수식 렌더러
packages/pointer-content-renderer/src/web/bridge.ts WebView 메시지 수신(onMessage) 및 RN 송신(sendToRN)
packages/pointer-content-renderer/src/types.ts 모드 payload/브리지 메시지/시나리오/섹션 타입 정의
packages/pointer-content-renderer/src/native/useContentBridge.ts RN 훅: bridgeReady 기반 init 재주입 + 이벤트 라우팅
packages/pointer-content-renderer/src/native/index.ts RN entrypoint exports 정의
packages/pointer-content-renderer/src/native/ContentWebView.tsx RN 컴포넌트: htmlSource 주입 + mode별 scroll/height 처리
packages/pointer-content-renderer/package.json 패키지 메타/스크립트/peerDeps 정의
packages/pointer-content-renderer/dev/mock-data.ts 브라우저 dev 패널용 mock 데이터(문서/채팅/오버뷰)
packages/pointer-content-renderer/dev/dev-panel.ts dev 서버에서 모드 전환/리렌더 트리거 UI
packages/pointer-content-renderer/.gitignore 패키지 로컬 산출물/IDE 파일 ignore
apps/native/package.json HTML 번들 sync 스크립트 및 pre* / EAS 훅 추가 + 패키지 의존성 추가
apps/native/metro.config.js html assetExts + React/RN/WebView dedupe resolver 설정 추가
apps/native/.gitignore 생성되는 assets/webview/content.html ignore 추가
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5 to +6
.document-mode html,
.document-mode body {
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

.document-mode html / .document-mode body selectors won’t match because the document-mode class is applied to <body> (see main.ts), not to a parent of html/body. As written, overflow is only guaranteed on the body via .document-mode { ... }, and the intended html/body rules are effectively dead. Consider targeting html, body.document-mode (or body.document-mode, body.document-mode * as needed) so the overflow lock reliably applies.

Suggested change
.document-mode html,
.document-mode body {
html,
body.document-mode {

Copilot uses AI. Check for mistakes.
markProgrammatic(smooth);
window.scrollTo({
top: document.body.scrollHeight,
behavior: smooth ? 'smooth' : 'instant',
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

window.scrollTo({ behavior: 'instant' }) uses a non-standard value; the ScrollOptions spec only allows 'auto' or 'smooth'. In WebViews/browsers that don’t support 'instant', this may be ignored or behave inconsistently. Use behavior: 'auto' for the non-smooth branch.

Suggested change
behavior: smooth ? 'smooth' : 'instant',
behavior: smooth ? 'smooth' : 'auto',

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +52
const timer = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => {
clearTimeout(timer);
reject(signal.reason);
}, { once: true });
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

delay() adds an abort event listener on the signal but never removes it when the timeout resolves normally. With many delays, this accumulates listeners until the controller is aborted (or forever if it isn’t), causing a memory leak. Please remove the listener on resolve (or use { once: true } plus explicit cleanup in both the resolve and abort paths).

Suggested change
const timer = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => {
clearTimeout(timer);
reject(signal.reason);
}, { once: true });
const cleanup = () => {
signal?.removeEventListener('abort', onAbort);
};
const timer = setTimeout(() => {
cleanup();
resolve();
}, ms);
const onAbort = () => {
clearTimeout(timer);
cleanup();
reject(signal.reason);
};
signal?.addEventListener('abort', onAbort, { once: true });

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +26

export function onMessage(handler: MessageHandler): void {
_handler = handler;

window.addEventListener('message', (event: MessageEvent) => {
try {
const data =
typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
handler(data as RNToWebViewMessage);
} catch {
// ignore non-JSON messages
}
});
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

onMessage() registers a new window.addEventListener('message', ...) every time it’s called and never unregisters the old listener. In Vite dev/HMR (or if onMessage is invoked more than once), this can lead to duplicate handlers and multiple renders per message. Consider installing the listener once (module-level) and routing to the latest _handler, or returning a cleanup function to remove the listener.

Suggested change
export function onMessage(handler: MessageHandler): void {
_handler = handler;
window.addEventListener('message', (event: MessageEvent) => {
try {
const data =
typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
handler(data as RNToWebViewMessage);
} catch {
// ignore non-JSON messages
}
});
let _listenerInstalled = false;
function handleWindowMessage(event: MessageEvent): void {
try {
const data =
typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
_handler?.(data as RNToWebViewMessage);
} catch {
// ignore non-JSON messages
}
}
export function onMessage(handler: MessageHandler): void {
_handler = handler;
if (!_listenerInstalled) {
window.addEventListener('message', handleWindowMessage);
_listenerInstalled = true;
}

Copilot uses AI. Check for mistakes.
- Adjusted indentation and line breaks for better readability
- Unified import statement formatting across files
- Corrected minor style issues in JSX and TypeScript code
- Added pointer-content-renderer to lint filter in CI
- Removed specific filters for typecheck and build in CI scripts
- Added lint script to pointer-content-renderer package
Refined `.document-mode` to use `:has()` for better specificity and
clarity.
Refactored `delay` function to remove abort event listener after
timeout resolves, ensuring proper cleanup and avoiding potential
memory leaks.
Ensure the message listener is installed only once, even during Vite
HMR.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 37 out of 39 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +44 to +75
const [height, setHeight] = useState(0);
const [isLoading, setIsLoading] = useState(true);

const { webViewRef, handleMessage } = useContentBridge({
initMessage,
onReady: (m) => {
setIsLoading(false);
onReady?.(m);
},
onHeight: isDocument ? setHeight : undefined,
onComplete,
onAnswer,
onBookmark,
});

const backgroundColor =
isDocument && initMessage.mode === 'document'
? (initMessage.backgroundColor ?? '#ffffff')
: '#f5f5f5';

return (
<View style={[isDocument ? { height, width: '100%' } : { flex: 1 }, style]}>
<WebView
ref={webViewRef}
source={htmlSource as unknown as WebViewSource}
onMessage={handleMessage}
scrollEnabled={!isDocument}
style={[
isDocument ? { height, width: '100%' } : { flex: 1 },
{ backgroundColor },
isLoading ? { opacity: 0 } : { opacity: 1 },
]}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

In document mode the wrapper and WebView height are driven by height state, which is initialized to 0. This can collapse the layout initially (and the loading overlay won't be visible because the parent height is 0) until the first height message arrives. Consider using a non-zero initial height and/or adding a minHeight prop/default so the loader and first paint are visible.

Copilot uses AI. Check for mistakes.
Comment thread package.json
Comment on lines +22 to +23
"ci:typecheck": "turbo run typecheck",
"ci:build": "turbo build"
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

ci:typecheck and ci:build were changed from filtered runs to turbo run typecheck / turbo build for the whole monorepo. This can significantly increase CI time and may run tasks for packages that weren’t previously validated in CI. If the intent is only to include @repo/pointer-content-renderer, consider keeping filters consistent (or document why full-monorepo CI is required).

Suggested change
"ci:typecheck": "turbo run typecheck",
"ci:build": "turbo build"
"ci:typecheck": "turbo run typecheck --filter=admin --filter=@repo/pointer-editor-v2 --filter=@repo/pointer-content-renderer",
"ci:build": "turbo build --filter=admin --filter=@repo/pointer-editor-v2 --filter=@repo/pointer-content-renderer"

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
"build": "tsc --noEmit && vite build --config vite.config.ts",
"typecheck": "tsc --noEmit",
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

tsconfig.native.json is added but the package scripts (typecheck/build) run tsc without a project, which will use tsconfig.json and currently excludes src/native. This means the exported native code can ship without being typechecked. Consider updating the scripts to run tsc -p tsconfig.native.json as well (or merging native + web into a single tsconfig include).

Suggested change
"build": "tsc --noEmit && vite build --config vite.config.ts",
"typecheck": "tsc --noEmit",
"build": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.native.json && vite build --config vite.config.ts",
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.native.json",

Copilot uses AI. Check for mistakes.
Updated the `typecheck` script to include both `tsconfig.json` and
`tsconfig.native.json` for comprehensive type checking. Adjusted the
`build` script to use the updated `typecheck` command.
@sterdsterd sterdsterd merged commit 382fe62 into develop Apr 13, 2026
3 checks passed
@sterdsterd sterdsterd deleted the feat/mat-272-content-renderer branch April 13, 2026 17:07
@sterdsterd sterdsterd restored the feat/mat-272-content-renderer branch April 13, 2026 18:03
@sterdsterd sterdsterd self-assigned this Apr 13, 2026
@sterdsterd sterdsterd added the ✨ Feature 기능 개발 label Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants