Skip to content

[MAT-240] 필기 시스템 packages로 분리 작업#262

Open
b0nsu wants to merge 43 commits intodevelopfrom
refactor/mat-240-drawing-extract
Open

[MAT-240] 필기 시스템 packages로 분리 작업#262
b0nsu wants to merge 43 commits intodevelopfrom
refactor/mat-240-drawing-extract

Conversation

@b0nsu
Copy link
Copy Markdown
Collaborator

@b0nsu b0nsu commented Apr 7, 2026

Summary

필기 캔버스(pointer-native-drawing)를 별도 패키지로 분리하고, 성능 최적화 포함.

Linear

Changes

  • 패키지 분리: SCRAP 내부 skia/drawing → @repo/pointer-native-drawing 패키지 추출, metro 싱글톤 resolve
  • ProblemScreen, ScrapDetailScreen: DrawingCanvas 및 DrawingToolbar 연결
  • 버전 통일(reanimated, typescript, gesture-handler), podspec team-ppointer, TextBoxData 마이그레이션
  • EraseStrokesEntry에 cachedPathsBefore → erase undo 시 path rebuild 생략 (~100ms → ~1ms)
  • C++ NativeDrawingSession (터치 축적, 속도 계산, frozen prefix), StylusInputView 터치 인터셉트, JSI getNativeLivePath/setSessionConfig
  • Stroke.points/samples Float64Array dual type, finalizeStroke에서 Float64Array 생성 (메모리 60-70% 절감)
  • autosave 복구: markAsUnsaved 액션 추가, onChange 연결, unmount 시 저장, lastSavedDataRef 비교로 중복 로드 방지
  • lint/prettier: 패키지 전체 eslint/prettier 수정, AGENTS.md 추적 제거

Testing

  • 필기 → 지우기 → undo: path 즉시 복원 확인
  • 필기 → 저장 → 앱 재시작 → 데이터 유지 확인
  • 필기 → 뒤로가기 → 재진입: 데이터 유지 확인
  • 기존 JSON 데이터 정상 로드 (하위 호환)
  • ProblemScreen / ScrapDetailScreen 모두 필기 정상 동작

Risk / Impact

  • 영향 범위: pointer-native-drawing 패키지 전체, ScrapDetailScreen 저장 로직
  • 확인이 필요한 부분: Phase 3 native path 품질 (JS path와 일치 여부), Float64Array freeze 동작
  • Phase 3D (CallInvoker push): New Arch에서 비활성 → 별도 PR
  • Phase 4D (binary 직렬화): JSON으로 원복 → 검증 후 별도 PR로 재도입

b0nsu and others added 3 commits April 7, 2026 18:03
Draw/erase-only React Native Skia canvas module extracted as a shared monorepo package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rops

Replace local DrawingCanvas and smoothing utilities with @repo/pointer-native-drawing.
Remove undo/redo and textMode props not yet supported by the shared package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevent duplicate bundling of react, react-native, reanimated, and gesture-handler in pnpm monorepo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@b0nsu b0nsu requested review from Copilot and sterdsterd April 7, 2026 09:41
@linear
Copy link
Copy Markdown

linear bot commented Apr 7, 2026

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 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 17, 2026 6:51am

@b0nsu b0nsu changed the title MAT-240 [MAT-240] 필기-시스템-packages-로-분리-작업 Apr 7, 2026
@b0nsu b0nsu changed the title [MAT-240] 필기-시스템-packages-로-분리-작업 [MAT-240] 필기 시스템 packages로 분리 작업 Apr 7, 2026
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

Extracts the Scrap drawing/Skia logic into a new shared workspace package (@repo/pointer-native-drawing) and migrates the native app to consume it, while temporarily removing unsupported features.

Changes:

  • Added new @repo/pointer-native-drawing package (tsup build, TS config, Skia drawing canvas + path smoothing).
  • Migrated apps/native Scrap Skia utilities to re-export/use the shared package and removed legacy local drawing/smoothing modules.
  • Updated native UI/state wiring to drop undo/redo history props and adjusted Metro config to try to prevent singleton module double-bundling.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/pointer-native-drawing/package.json Declares the new shared drawing package entrypoints/peers.
packages/pointer-native-drawing/tsup.config.ts Adds build config for ESM/CJS + externals.
packages/pointer-native-drawing/tsconfig.json TypeScript config for the shared package.
packages/pointer-native-drawing/src/index.ts Public exports for the shared package.
packages/pointer-native-drawing/src/DrawingCanvas.tsx New shared Skia drawing canvas implementation.
packages/pointer-native-drawing/src/smoothing.ts New shared path smoothing implementation.
apps/native/src/features/student/scrap/utils/skia/index.ts Re-exports drawing utilities from the shared package.
apps/native/src/features/student/scrap/utils/handwritingEncoder.ts Updates type imports to use the new re-export surface.
apps/native/src/features/student/scrap/hooks/useHandwritingManager.ts Updates ref type import path.
apps/native/src/features/student/scrap/screens/ScrapDetailScreen.tsx Swaps to shared DrawingCanvas import and removes history/text props.
apps/native/src/features/student/scrap/hooks/useDrawingState.ts Removes undo/redo history state plumbing.
apps/native/src/features/student/scrap/components/scrap/DrawingToolbar.tsx Removes undo/redo UI and props.
apps/native/src/features/student/problem/screens/ProblemScreen.tsx Removes history props wiring to the drawing toolbar/canvas.
apps/native/src/features/student/problem/components/ProblemDrawingToolbar.tsx Removes undo/redo UI and props.
apps/native/package.json Adds workspace dependency on @repo/pointer-native-drawing and bumps reanimated patch.
apps/native/metro.config.js Adds custom resolve logic intended to pin singleton modules to a single physical path.

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

Comment thread packages/pointer-native-drawing/src/DrawingCanvas.tsx Outdated
Comment thread packages/pointer-native-drawing/src/DrawingCanvas.tsx Outdated
Comment thread packages/pointer-native-drawing/src/DrawingCanvas.tsx Outdated
Comment thread packages/pointer-native-drawing/src/smoothing.ts
Comment thread apps/native/metro.config.js Outdated
b0nsu and others added 3 commits April 8, 2026 10:54
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
git-subtree-dir: packages/pointer-native-drawing
git-subtree-split: ac29ede5c13e2f9f769c47c846bb2d03d262720f
@b0nsu b0nsu closed this Apr 8, 2026
b0nsu and others added 2 commits April 9, 2026 19:56
- Rename to @repo/pointer-native-drawing, set private:true
- Fix package.json exports (./package.json) and codegenConfig for Fabric codegen
- Fix StylusInputView native event name and ivar access
- Fix codegen spec (remove interfaceOnly, use Double[])
- Set iOS deployment target to 15.1
- Add backgroundColor/children props to DrawingCanvas for overlay pattern
- Migrate ProblemScreen to DrawingCanvas children API
- Remove deleted API references (setTexts/getTexts, TextItem, DrawingViewportTransform)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ering

- Overhaul WritingFeelConfig: pressure gamma, velocity thinning, tilt, EMA smoothing
- Add isFixedWidthConfig() detection for centerline vs polygon envelope rendering
- Add buildVariableWidthPath with frozen prefix optimization for long strokes
- Add arc-length resampling and velocity recomputation pipeline
- Switch live path scheduling from rAF to queueMicrotask (1-frame latency reduction)
- Convert live path from ref-based to state-based (skia v2 reconciliation requirement)
- Add deferred SkPath dispose to prevent use-after-dispose in Skia Canvas
- Add pencilOnly support in RNGH adapter with SharedValue for worklet access
- Skip velocity computation and tilt calculation in fixed-width mode
- Add eraser cursor visualization and React.memo on SkiaDrawingCanvasSurface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@b0nsu b0nsu reopened this Apr 9, 2026
b0nsu and others added 3 commits April 12, 2026 16:25
…3a9a7f

503a9a7f docs: update README with current API, props table, and architecture
a51f5597 refactor: move height buffer logic from document to viewport controller
5d98e76e refactor: unify eraser phase state machine with draw lifecycle
3c4ef476 fix: correct eraser onEnd comment to match actual phaseShared behavior
105bfbf5 fix: correct API names in feature-spec-check doc
584cfe71 fix: address review findings — dep array, gesture finalize, pinch dead commit
ede72be6 docs: add feature spec check with implementation status
46477517 fix: canvas height expansion buffer uses viewport height
a6c8e492 feat: update example app with tool selector, undo/redo, zoom toggle
f1b338a5 refactor: pinch/pan gesture coordination with worklet SharedValue
02331abc fix: onEdit prop missing in CanvasOverlayLayer + dependency array corrections
5e5e41c0 Merge branch 'refactor/drawingcanvas-decomposition-v2' into main
5e4acd93 refactor: decompose DrawingCanvas into responsibility-based modules
d644d3f8 Merge branch 'fix/canvas-input-clamp' into main
0a447308 fix: address 8 bugs from pre-merge code review
db7d072d fix: clamp draw/erase input coordinates to canvas bounds
102fc879 fix: textbox move boundary clamping and new textbox selection overlay
6e2689eb fix: add body tap gesture to TextBoxSelectionOverlay for edit re-entry
36bb1739 fix: auto-commit on tool switch, suppress draw in textbox mode, dismiss commits editing
118678f1 refactor: rewrite TextBox selection overlay with all-RNGH gestures
c67517a7 fix: resolve TextBox stale closure, blur race, and canvas bounds (B1/B2/G1-G3)
d263a5fe fix: adversarial review — endSession, textBoxesRef, Paragraph dispose
97b346a8 fix: address code review findings (CRITICAL 1 + HIGH 3)
a8b94c5a fix: address production readiness feedback (4 items)
e42cf07d feat: add TextBox edit, resize, and move (1-4, 1-5, 1-6)
f5111a26 feat: add undo/redo HistoryManager and TextBox MVP (create + delete)
72898ed9 fix: pinch zoom focal-point anchoring, finger-lift jump, and rendering bugs
82738bff feat: add zoom/pan support with enableZoomPan prop
160a5012 Merge branch 'feature/native-path-builder'
5cd9f75c refactor: extract smoothing factor constant, remove unused velocity recomputation, fix SkPath dispose
45f2b26f Merge pull request #1 from team-ppointer/feature/native-path-builder
0bf4eae2 docs: add rendering pipeline analysis and native technology guide
559df493 fix: strokeWidth prop now correctly applied in fixed-width mode
718b2019 refactor: remove TuningPanel, update pen sizes to 0.2/0.75/1.45mm
3b0d3a1f refactor: 1€ filter to RNGH only + trim tuning panel
cb62aeb9 refactor: move 1€ filter to RNGH adapter only (finger/60Hz input)
d4ad482e fix: migrate C++ to SkPathBuilder API + fix podspec header resolution
c7a4b9c2 fix: thread targetSpacing through native buildCenterlinePath
9ee97bb3 feat: add 1€ filter for stylus input smoothing
735aebfc fix: reduce prefix/tail overlap to 1 sample to minimize AA seam
c9ac6ac8 feat: add DPI-aware buildCenterlinePath for fixed-width rendering
6d7cd561 feat: add iOS predicted touches for lower-latency live rendering
66d69d12 refactor: deprecate variable-width rendering, use fixed-width only
8fa22881 docs: add JSDoc for nativeBuildCenterlinePath and packPoints
1aaceab2 fix: address third-round review findings
7b4c7e8f fix: address second-round review findings
98b739b7 fix: address remaining review findings for native path builder
17a95829 feat: add native C++ JSI path builder with Skia integration
1a2c93ff feat: add fixed-width mode, eraser cursor, and children overlay pattern
5c4c4429 Perf/memory optimization and fix live stroke end-taper artifact
ca80947f Stabilize sample pipeline: recompute velocities, smooth centerline, EMA continuity
8ee16e35 Add example app for testing drawing features on iOS
05dc0708 Fix RN 0.85 + Skia 2.6.x compatibility and add pencilOnly prop
19d2f959 Fix codegen event array types: ReadonlyArray<Double> → Double[]

git-subtree-dir: packages/pointer-native-drawing
git-subtree-split: 503a9a7f746cb4fc56475b7dd3ddece0de2330eb
…/mat-240-drawing-extract

# Conflicts:
#	packages/pointer-native-drawing/ios/StylusInputView.mm
#	packages/pointer-native-drawing/src/DrawingCanvas.tsx
#	packages/pointer-native-drawing/src/specs/StylusInputViewNativeComponent.ts
…indicator

- Update pointer-native-drawing package to latest (subtree pull)
- Add pencilOnly prop to both ScrapDetailScreen and ProblemScreen
- Enable enableZoomPan on both screens
- Add scroll indicator with black style to DrawingCanvas
- Fix ProblemScreen minCanvasHeight to screenHeight * 2
- Rename text mode props (isTextMode -> isTextBoxMode, activeTool)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add undo/redo buttons to DrawingToolbar and ProblemDrawingToolbar
- Add strokeColor state to useDrawingState with lastColor persistence
- Save/load textBoxes in useHandwritingManager (getTextBoxes/setTextBoxes)
- Add lastColor field to handwritingEncoder for color restoration
- Export TextBoxData type from skia index
- Wire onUndoStateChange callback to both screens

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread packages/pointer-native-drawing/src/DrawingCanvas.tsx Outdated
Comment thread packages/pointer-native-drawing/src/DrawingCanvas.tsx Outdated
Comment thread apps/native/src/features/student/scrap/utils/handwritingEncoder.ts
Comment thread packages/pointer-native-drawing/src/render/skia/useSkiaDrawingRenderer.ts Outdated
Comment thread apps/native/package.json
Comment thread packages/pointer-native-drawing/package.json Outdated
Comment thread packages/pointer-native-drawing/package.json Outdated
Comment thread packages/pointer-native-drawing/pointer-native-drawing.podspec Outdated
Comment thread packages/pointer-native-drawing/pointer-native-drawing.podspec Outdated
b0nsu and others added 9 commits April 17, 2026 14:40
- 버전 맞추기: reanimated ~4.1.6, gesture-handler ~2.28.0, typescript ~5.9.2
- podspec author/homepage/source를 team-ppointer로 변경
- useImperativeHandle stale ref 수정 (textBoxesRef 도입)
- useTextBoxManager 인라인 객체 useMemo 감싸기
- TextBoxData 마이그레이션: width/height 없는 기존 데이터 fallback
- committed paths dispose를 queueMicrotask로 지연 (live path과 동일 패턴)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EraseStrokesEntry에 cachedPathsBefore 필드 추가.
erase 시작 시 committedPaths 스냅샷을 history entry에 저장하고,
undo 시 캐시된 paths를 직접 복원하여 O(ΣP) path rebuild 생략.

- HistoryManager: cachedPathsBefore, onEntryEvicted 콜백, push/clear/discard 시 dispose
- rendererTypes: replaceCommittedStrokes에 prebuiltPaths 파라미터, getCommittedPaths
- useDrawingDocumentController: beginEraseTransaction/commitEraseTransaction 래퍼
- useDrawingInteractionController: docController 래퍼 호출로 변경

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
터치 이벤트를 Fabric EventEmitter 전에 native에서 인터셉트하여
path를 미리 빌드. JS는 getNativeLivePath()로 미리 빌드된 path를 사용.

Native C++:
- NativeDrawingSession: 포인트 축적, 속도 계산, frozen prefix path 빌드
- NativeDrawingLivePathBridge: thread-safe 정적 브릿지 (mutex + CallInvoker push)
- StylusInputView: 터치 인터셉트, tilt 변환, config 동기화 (static activeView)

JSI:
- getNativeLivePath(): 최신 native path 복사
- setSessionConfig(): JS config → native session 동기화
- registerLivePathCallback(): CallInvoker push (Phase 3D, New Arch 미활성)

TODO: Phase 3D New Arch CallInvoker 접근 방식 수정 (별도 PR)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stroke.points/samples가 Point[]|Float64Array 둘 다 지원.
새 stroke는 finalizeStroke에서 Float64Array로 생성 (메모리 60-70% 절감, GC 0).

- drawingTypes: packed stride 상수, pack/unpack 헬퍼, 접근 함수
- DrawingEngine: finalizeStroke에서 packPoints/packSamplesArray 사용
- strokeUtils: deepCopyStrokes, getStrokeBounds, getMaxY Float64Array 지원
- smoothing: buildSmoothPath/buildCenterlinePath union 시그니처 + unpack fallback
- nativePathBuilder: packSamples Float64Array 직통 (re-pack 생략)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JSON.stringify → binary packing (DataView + ArrayBuffer → base64).
decode 시 자동 감지 (첫 바이트 0x01=binary, 그 외=legacy JSON).
Float64Array stroke 직접 encode/decode (Phase 4-A 연동).

- timestamp 정밀도: stroke별 baseTimestamp(float64) + delta offset(float32)
- 색상 테이블: stroke color 중복 제거
- 하위 호환: 기존 JSON 데이터 + TextBoxData 마이그레이션 유지

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TanStack Query 백그라운드 refetch 시 setStrokes 재호출 방지.
initialLoadDoneRef로 초기 1회만 데이터 로드.

근본 해결: refactor/mat-313에서 useHandwritingManager 구조 리팩토링 필요.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- unused imports 제거 (useCallback, useRef, DocumentSnapshot, ReadonlyPoint)
- unused destructuring에 _ prefix (historyRef, maxYRef)
- no-explicit-any → HistoryEntry 타입 캐스팅 + eslint-disable 주석
- prettier 자동 포맷팅 적용

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Phase 4-D binary encoder → JSON 원복 (Float64Array→object 변환 후 JSON.stringify)
- binary 데이터 감지 시 빈 캔버스로 fallback (테스트 중 저장된 binary 대응)
- useDrawingState: markAsUnsaved 액션 추가
- ScrapDetailScreen: onChange → markAsUnsaved 연결 → autosave 활성화
- useHandwritingManager: unmount 시 저장 (뒤로가기/탭 전환 대응)
- lastSavedDataRef 비교로 query refetch 시 중복 로드 방지

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b0nsu and others added 3 commits April 17, 2026 15:24
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
base 브랜치 변경사항 통합. mat-269 최적화 코드 유지.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[MAT-269] 필기 성능 최적화 + PR #262 리뷰 반영
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- double quotes → single quotes (prettier)
- trailing commas, formatting fixes
- no-explicit-any → proper type cast

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
handwritingEncoder:
- Float64Array 타입 narrowing 수정 (로컬 변수 추출)
- any → Record<string, unknown> 타입 캐스팅
- deprecated unescape/escape → encodeURIComponent/replace 방식
- prettier trailing comma 수정

useHandwritingManager:
- import/order: empty line 제거
- prettier: doSave 파라미터 포맷팅
- react-hooks/exhaustive-deps: isSaving, onColorRestore → ref로 전환
- deprecated InteractionManager.runAfterInteractions → requestAnimationFrame

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b0nsu and others added 2 commits April 17, 2026 15:47
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gManager

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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 68 out of 70 changed files in this pull request and generated 8 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 +228 to +238
const overlay = useMemo(
() =>
Platform.OS === 'ios' ? (
<StylusInputView
style={StyleSheet.absoluteFill}
acceptFingerInput={config.acceptFingerInput ?? false}
onStylusTouch={handleStylusTouch}
/>
) : null,
[handleStylusTouch]
);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

overlay is memoized only on handleStylusTouch, but it also depends on config.acceptFingerInput. If acceptFingerInput changes (e.g. toggling pencilOnly / nativeFingerInput), the native view won’t re-render and will keep the stale value. Include config.acceptFingerInput in the dependency array or avoid memoizing the element.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +41
static inline void altAzToTiltXY(double altitude, double azimuth,
double &outTiltX, double &outTiltY) {
if (altitude >= M_PI_2) {
// Pencil is perpendicular — no tilt
outTiltX = 0;
outTiltY = 0;
return;
}
if (altitude <= 0) {
outTiltX = std::atan2(std::cos(azimuth), 0.0);
outTiltY = std::atan2(std::sin(azimuth), 0.0);
return;
}
double tanAlt = std::tan(altitude);
outTiltX = std::atan2(std::cos(azimuth), tanAlt);
outTiltY = std::atan2(std::sin(azimuth), tanAlt);
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

altAzToTiltXY claims to mirror nativeStylusAdapter.tsx, but here it returns tilt in radians (atan2 result), while the TS implementation converts to degrees (RAD_TO_DEG) and rounds. This mismatch means native session samples will carry different tilt units than JS. Either convert to degrees here as well, or change the TS side to use radians consistently.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +27
## Install

```bash
pnpm add pointer-native-drawing
```

Peer dependencies:

- `react` >= 19.0.0
- `react-native` >= 0.81.0
- `@shopify/react-native-skia` >= 2.2.0
- `react-native-gesture-handler` >= 2.28.0
- `react-native-reanimated` >= 4.1.0

## Usage

```tsx
import React, { useRef } from 'react';
import { View } from 'react-native';
import { DrawingCanvas, DrawingCanvasRef } from 'pointer-native-drawing';

Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The README install/import instructions don’t match the actual package name (@repo/pointer-native-drawing). As written, pnpm add pointer-native-drawing and import ... from 'pointer-native-drawing' will fail for consumers. Update the docs to use the correct package name (or publish under the documented name).

Copilot uses AI. Check for mistakes.
Comment on lines +177 to +186
// Dispose previous Skia Paragraph native objects to prevent memory leaks
useEffect(() => {
const prev = prevParagraphsRef.current;
prevParagraphsRef.current = textBoxParagraphs?.map((p) => p.paragraph) ?? [];
return () => {
for (const p of prev) {
(p as { dispose?: () => void }).dispose?.();
}
};
}, [textBoxParagraphs]);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The paragraph disposal effect is leaking the current set of Skia Paragraph native objects on unmount. In the last render, prev is the previous list, so the cleanup disposes the previous list and never disposes textBoxParagraphs from the final render. Consider capturing the newly created paragraph list inside the effect and disposing that list in the returned cleanup (or disposing prevParagraphsRef.current on unmount).

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +277
{/* Left resize handle */}
<GestureDetector gesture={resizeLeftGesture}>
<View
style={[
styles.resizeHandle,
{
left: screenX - HANDLE_SIZE / 2,
top: screenY + scaledHeight / 2 - HANDLE_SIZE / 2,
},
]}
hitSlop={HANDLE_HIT_SLOP}
/>
</GestureDetector>

{/* Right resize handle */}
<GestureDetector gesture={resizeRightGesture}>
<View
style={[
styles.resizeHandle,
{
left: screenX + scaledWidth - HANDLE_SIZE / 2,
top: screenY + scaledHeight / 2 - HANDLE_SIZE / 2,
},
]}
hitSlop={HANDLE_HIT_SLOP}
/>
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

hitSlop on a plain <View /> doesn’t affect RNGH gesture hit-testing. As written, the resize handles will only be touchable within the 20x20 view bounds. Apply hit slop on the gesture (e.g. Gesture.Pan().hitSlop(...)) or wrap the handle with a touchable view sized for the desired hit target.

Copilot uses AI. Check for mistakes.
Comment on lines +326 to +383
// --- Render: Scroll mode (default) ---
return (
<View style={[styles.container, { backgroundColor }]}>
<ScrollView
ref={vc.scrollViewRef}
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
scrollEnabled={isScrollEnabled}
showsVerticalScrollIndicator
indicatorStyle='black'
onScroll={vc.handleScroll}
onLayout={vc.handleLayout}
scrollEventThrottle={16}
nestedScrollEnabled>
<GestureDetector gesture={composedGesture}>
<View style={[styles.canvasWrapper, { height: vc.canvasHeight }]}>
{children}
<View style={StyleSheet.absoluteFill} pointerEvents='none'>
<SkiaDrawingCanvasSurface
paths={paths}
strokes={strokes}
strokeBounds={rendererState.strokeBounds}
strokeColor={strokeColor}
normalizedPenStrokeWidth={docController.normalizedPenStrokeWidth}
livePathSV={livePathSV}
eraserCursor={docController.eraserCursor}
eraserSize={eraserSize}
canvasRef={canvasRef}
scrollOffsetY={viewport.scrollOffsetY}
viewportHeight={viewport.viewportHeight}
viewportWidth={vc.viewportSize.width}
textBoxes={textBoxState.textBoxes}
editingTextBoxId={textBoxState.editingId}
/>
</View>
<CanvasEditingOverlay
editingTextBox={textBoxState.editingTextBox}
onChangeText={textBoxActions.updateEditingText}
onHeightChange={textBoxActions.updateEditingHeight}
onBlur={textBoxActions.commitEditing}
nativeStylusOverlay={useNativeStylus ? nativeStylusAdapter?.overlay : undefined}
/>
</View>
</GestureDetector>
</ScrollView>
<CanvasSelectionOverlay
selectedTextBox={textBoxState.selectedTextBox}
onDelete={textBoxActions.deleteSelected}
onEdit={textBoxActions.editSelected}
onDismiss={textBoxActions.deselect}
onMoveStart={textBoxActions.beginMove}
onMoveUpdate={textBoxActions.updateMove}
onMoveEnd={textBoxActions.endMove}
onResizeStart={textBoxActions.beginResize}
onResizeUpdate={textBoxActions.updateResize}
onResizeEnd={textBoxActions.endResize}
setIsScrollEnabled={setIsScrollEnabled}
/>
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

In scroll mode, CanvasSelectionOverlay is rendered outside the ScrollView, but selectedTextBox coordinates appear to be in canvas/content coordinates. This will cause the selection box/handles to drift when the user scrolls. Consider rendering the selection overlay inside the scroll content (so it scrolls together), or pass the current scroll offset and subtract it when computing screenY (and similarly for x if horizontal scroll is possible).

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +20
"exports": {
".": {
"types": {
"import": "./dist/index.d.mts",
"require": "./dist/index.d.ts"
},
"react-native": "./src/index.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"default": "./dist/index.mjs"
},
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

The types.import export points to ./dist/index.d.mts, but the build config shown (tsup with dts: true) typically emits index.d.ts (and not necessarily index.d.mts). If index.d.mts isn’t generated, TS will fail to resolve types for ESM imports. Either ensure the build outputs .d.mts files, or point both import/require type conditions at the emitted .d.ts.

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +105
// --- Phase 3D: Register native push callback (bypasses Fabric events) ---
useEffect(() => {
const registered = registerLivePathCallback((path: SkPath) => {
updateLivePath(path);
});
if (__DEV__ && registered) {
console.log('[PointerDrawing] Phase 3D: native live path push registered');
}
}, [updateLivePath]);
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

registerLivePathCallback is installed globally (LivePathBridge stores the JS function) but there’s no unregistration/cleanup on unmount. This can retain the JS function/runtime longer than needed and makes it easy to leak callbacks across remounts. Consider exposing a native clearPushCallback/unregisterLivePathCallback and calling it from the useEffect cleanup here.

Copilot uses AI. Check for mistakes.
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.

3 participants