[L] 단일 봉지 컴포넌트 — 시각/모션/찢기/리스트 (#25)#26
Merged
Merged
Conversation
7-layer 글라싱지 합성으로 Sealed 봉지 정적 시각 구현.
- PouchState enum (sealed/tearing/torn) + case별 doc-comment
- PouchPaperLayer: paperBody/fiberTexture/topPrintBand/wrinkleHighlight/heatSeal/tearMarker
- PouchView shell + PouchShowcaseView 데모 진입점
- ContentView → Showcase 연결
라이트/다크 스크린샷 docs/screenshots/pouch/sealed-{light,dark}.png 캡처.
시각 평가/보강 옵션은 Stage 1 보고서 참조.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tamps (#25 stage1 v2) 작업지시자 1차 시각 검토 후 글라싱지 → 플라스틱 봉지 redesign. 봉지 변경: - 본체: warm off-white plastic (light 0xFCFAF5×0.92, dark translucent overlay) - 상하단 굵은 serrated heat-seal (top 14pt, bottom 12pt, pinking-shears 패턴) - 옆구리 V노치 좌/우 (실제 찢기 시작점) - 모서리 cornerRadius 8 → 3 (실 봉지 거의 직각) - fiber 텍스처 → 플라스틱 sheen 대각선 gradient - 우상단 V컷+화살표 마커 제거 슬롯 도장 추가 (사용자 생성 자산 6세트): - 원본 PNG → magick fuzz 8% 투명화 → Asset Catalog 통합 - A2 결정: Template 렌더링 + .foregroundStyle(slot color) - B3 결정: 한글 고정 (영문 자산은 .context/에 보관) - 슬롯별 회전 (-3°/+1.5°/-1.5°) + 인주 opacity (0.78/0.78/0.72) 헤더 영역 재배치: - 상단 heat-seal 아래로 이동 - SlotStamp 좌측, 약국 정보 우측 - 용법 라인 monospace (처방전 인쇄 느낌) - dotted divider로 알약 영역 구분 스크린샷 6장: 3 slots × 2 modes. 기존 v1 스크린샷은 sealed-v1-{light,dark}.png로 보존. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… v3) ADR-0009 채택 — 봉지 찢기 인터랙션을 봉지 상단(top edge)에서 중간 perforation 라인으로 이동. 시각/인터랙션/알약 흐름이 한 라인에 정렬되어 학습 비용 최소화. 코드 변경: - 신규 NotchedPouchShape: Path.subtracting() 으로 좌/우 반원 노치 차감 fill/mask/stroke 단일 source. 봉지 형태 자체가 깎여 진짜 절취선. - 옆구리 V노치 (sideTearNotches/notchPath/notchColor 등) 완전 제거. 기존 '> <' 시각 혼란 자연 해결. - perforationDashLine: 좌/우 노치 안쪽 끝까지 horizontal dash [3,3]. - 헤더 영역의 dotted divider overlay 제거 — perforation이 그 역할. 시각 사양 (확정): - 노치: 반원 ⌒, 반지름 4pt (240pt 봉지 너비 ~3.3%) - 점선: dash [3,3], lineWidth 0.7pt, opacity 0.40 - 가위 아이콘 없음 (사용자 결정) 문서: - ADR-0009 신규 작성, ADR README 색인 보강 (0007/0008/0009) - brief.md §핵심 인터랙션 명세 갱신: drag → swipe, top → middle - 변경 로그 v0.5 추가 - Stage 1 보고서에 redesign 2차 섹션 append 스크린샷 6장 갱신: 3 slots × 2 modes (덮어쓰기, 1차 시각은 git history). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
루트 .DS_Store가 과거에 실수로 staged 되어 추적 중이었음. .gitignore 규칙은 이미 있어 향후 신규 .DS_Store는 자동 무시됨. 기존 추적된 1개를 git rm --cached 로 untrack하여 매 작업마다 modified로 뜨던 노이즈 제거. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
봉지 안 알약 5종 시각 + 정적 배치 + Showcase 컨트롤. 신규: - CapsuleType enum (Models/Enums.swift) — tablet/softgel/capsule/powder/liquid/gummy - PillBody 모델 + PillMix enum + mock factory (봉지 bounds 안 자동 배치) - PillView — 6종 capsuleType 분기 (liquid는 EmptyView) · tablet: Circle + ring + radial highlight · softgel: Ellipse + 좌상단 흰 highlight · capsule: 두 톤 Capsule (좌측 색 + 우측 warm white) + 상단 광택 · powder: Canvas로 작은 원 7개 군집 · gummy: RoundedRect + 반투명 + 코너 highlight 봉지 너머 비침: - PouchView: pills layer를 ZStack 아래에 두고 .blur(0.4) + .opacity(0.94) - PaperLayer: 라이트 fill 0.92 → 0.78 (알약 가시성) - 다크는 0.18 그대로 (translucent overlay 효과) Showcase 컨트롤: - 알약 개수 Slider (0~8) - 조합 Picker (혼합/정제/캡슐/연질/가루/젤리) - Reset 버튼 (id 변경으로 강제 재생성) 색은 warm 약품 톤으로 cream 배경 + 슬롯 색조와 정합. 배치는 봉지 하단 정렬로 중력 정착 시각 (Stage 3 물리 초기 상태로 사용). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
직전 PR #22 (카테고리 16종 시드 자산) 미참조 누락 시정. 카테고리 자산이 단순 라벨 아이콘이 아니라 실제 알약 일러스트 (omega3=노란 softgel, vitaminD=노란 tablet, probiotics=흰 capsule 등). SwiftUI Shape 직접 그리기보다 사실성·카테고리 표현·코드량 모두 우수. PillBody 단순화: - capsuleType / color 폐기 - 신규 categoryKey: String — Supplement.categoryKey와 직접 매핑 - radius 13 → 16 (자산 시각 풍부 반영) PillView 통째 재작성: - 기존: switch capsuleType 6종 SwiftUI Shape (~120 LOC) - 신규: Image(pill.categoryKey).resizable().scaledToFit() (~10 LOC) PillMix 카테고리 기반 7개로 재정의: - 혼합 (16종) / 비타민 / 오메가 / 미네랄 / 캡슐 / 단일 omega3 / 단일 vitaminD - 기존 allTablet/allCapsule/allSoftgel/allPowder/allGummy 폐기 Asset 접근: - Categories/ namespace 미설정 (provides-namespace X) - Image("Categories/key") X → Image(key) 직접 접근 스크린샷 갱신: pills-mixed.png(light/dark), pills-vitamins-8.png 신규. 구식 pills-allcapsule-8.png 삭제. CapsuleType enum은 도메인 호환 위해 유지. #11 캡슐 자산 task가 이번 작업으로 자연 해소 — 별도 ADR 또는 scope 재정의 후속 검토. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage 3 진입 전 시각 검토에서 식별된 critical 4개 + 도장 사이즈 1개 fix. C1 라이트 알약 가시성: - 봉지 라이트 fill opacity 0.78 → 0.65 - PillView blur 0.4 제거, opacity 0.94 → 0.96 - 라이트에서 알약 형태/색 명확 식별 C2 알약 사이즈: - PillBody radius 16 → 22 (봉지 너비 14% → 20%) - mock spacing 2 → 3 - 8개는 두 줄로 자동 배치 C3 상단 heat seal serration: - stroke lineWidth 0.7 → 1.0 - heatSealEdge opacity 0.65 → 0.85 C4 perforation 점선: - color opacity 0.40 → 0.55 - lineWidth 0.7 → 0.9 M2 도장 사이즈 정규화 (자산 처리): 원본 자산이 캔버스 안에서 아침만 71%, 점심/저녁 80%로 그려져 있음. ImageMagick으로 3개 모두 trim → 1024px resize → 1280px 캔버스 중앙 배치. trimmed 비율 균일화 후 코드 변경 없이 visual size 동일. 스크린샷 9장 모두 갱신. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
봉지 안 알약은 floor 위에 놓인 게 아니라 떠있는데, 기존 자산은 모두 하단에 drop shadow가 베이크되어 시각 모순. 작업지시자가 ChatGPT GPT Image 2로 그림자 제거된 16종 PNG (image-v12~v27)을 재생성. 동일한 경로로 1x/2x/3x 변환 + 흰 배경 transparent 처리. 처리 흐름 (자산별): - 흰 배경 → magick -fuzz 2% -transparent white (대부분) - 흰 알약 (probiotics)만 fuzz 1% (캡슐 안 흰 알갱이 보존) - @1x 128px / @2x 256px / @3x 384px Lanczos resize 스크린샷 갱신: pills-mixed.png(light/dark), pills-vitamins-8.png. 알약이 봉지 안에서 떠있는 듯한 자연스러운 시각 — 가설 B 강화. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
알약이 봉지 비닐 너머로 더 또렷하게 보이도록 라이트 fill opacity 조정. 실제 약봉지 (image.png) 의 plastic 투명도와 더 정합. 다크 0.18 그대로 (이미 명확). 스크린샷 갱신: pills-mixed.png, pills-vitamins-8.png. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- MotionEngine: protocol + RealMotionEngine (CMMotionManager 60Hz) + MotionEngineMock (auto/manual) + Factory (#if simulator) - PillPhysicsEngine: pure-functional 2D — gravity (gScale 40, "살짝") + damping 0.92 + bounds AABB collision (restitution 0.3) + pair sphere-sphere collision (pairSeparation 0.5) - PillBody: velocity field for physics integration - PouchView: TimelineView 60Hz tick + advancePhysics with dt cap (1/30) to prevent jump on background return - Showcase: motion mode toggle (Auto/Manual) + manual gravity X/Y sliders Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 suites / 16 cases: - Gravity: 누적, x/y 분리, dt=0 가드, 빈 배열 안전 - Damping: 매 tick 0.92 비율, 60틱 후 사실상 정지 - Integration: velocity → position 통합 정확 - Bounds: 4면 충돌 + 반사 + 안전 영역 + 30초 정착 - Pair: 겹침 분리, 멀면 변화 X, 단일 안전, dist=0 NaN 가드 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- motion-t0/t3/t6.png — Mock auto 0/3/6초 시점 알약 흔들림 - task_W2_25_stage3.md — 5 stage 중 Stage 3 보고서 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…25 stage3 polish) - gScale 40 → 90 (terminal velocity 8.3 → 18.75 pt/s, ~2.3배 활발) - pairRestitution 0.7 신규 — 알약 간 1D elastic collision along normal - resolvePairCollisions 에 impulse 교환 추가 (equal mass, 침투 중일 때만) 작업지시자 피드백: - 흔들림 너무 약함 → gScale 보강 - 알약끼리 뭉개짐 → velocity 교환 부재가 원인. position 분리만으로는 momentum 교환 없어 부드럽게 미끄러지며 합쳐지듯 보임. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#25 stage3 polish) - 정면 충돌 시 velocity 부호 반전 검증 - 서로 멀어지는 중이면 impulse 미적용 검증 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… collision (#25 stage3 polish v2) 작업지시자 2차 피드백: - 여전히 너무 느림 → gScale 90 → 250 (terminal 18.75 → 52 pt/s) - 알약 여전히 겹침 → root cause: bounds/pair collision 의 'r = radius * 0.5' 가 시각 frame의 절반이라 시각상 침투해도 충돌 미발생 변경: - gScale 90 → 250 (terminal velocity 봉지 높이를 1초에 가로지름) - collisionRadiusRatio 0.9 신규 — 매직 0.5 비율을 시각 frame 정합으로 보정 - pairSeparation 0.5 → 1.0 + pairCollisionIterations 2 — 다중 침투 안정화 - MotionEngineMock auto: 4초 주기 / amplitude 1.0 — 데모 활발 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…istic pouch UX (#25 stage3 polish v3) 작업지시자 3차 피드백: - 여전히 덜 활발 → gScale 250 → 400 - 회전 부재 → angular velocity 모델 추가 - 1층/2층 비현실적 여백 → collisionRadiusRatio 0.9 → 0.6 (가로 capsule 자산은 frame 의 ~50%만 차지 — 0.9 ratio는 비현실) 신규: - PillBody.angularVelocity (deg/s) 필드 - PillPhysicsEngine.integrateRotation — rotation += av*dt + angularDamping - resolvePairCollisions — tangential rel velocity → 양쪽 반대 spin (vRelN 가드 위 — 정직각 스침에서도 회전 발생) - resolveBoundsCollision — 벽 평행 velocity → spin (벽 따라 굴러감) - mock initial spacing 3 → 1 (빽빽 시작) 상수: gScale 400, collisionRadiusRatio 0.6, angularDamping 0.95, pairSpinTransfer 1.5, wallSpinTransfer 0.6 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ngential transfer (#25 stage3 polish v3) 신규 Suite PillPhysicsEngineRotationTests (4 cases): - angularVelocity 매 tick rotation 누적 - angularDamping 0.95 적용 - 좌측 벽 충돌 시 dy → angular - 스쳐 지나갈 시 양쪽 반대 부호 spin bounds collision 4개 expected position: r=13.2 기준 (13.2/186.8/386.8/13.2) 정착 테스트 범위: 380~387 (정착점 maxY-r=386.8) 총 22/22 통과. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… spin (#25 stage3 polish v4) 작업지시자 4차 피드백: - 왼쪽으로 돌릴 때만 회전 → 우측은 약했음. root cause: reflection 후 velocity 로 spin 계산 → 강한 충돌일수록 spin 작아지는 구조 - 이동 너무 느림 → gravity 0.5 에서 terminal 41pt/s 로 부족 변경: - gScale 400 → 1000 (gravity=1 terminal 208pt/s, gravity=0.5 104pt/s) - bounds collision: pre-velocity 로 spin 계산 (preDx/preDy 캡처 후 반사) - wallSpinTransfer 0.6 → 1.5 (좌/우 회전 명확히) - pairSpinTransfer 1.5 → 2.5 (충돌 회전 강화) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tude (#25 stage3 polish v4) - 우측 벽 충돌 시 angular = -45 (좌측 +45 의 대칭) - 강한 충돌(preDx -200, preDy 100) 에서도 spin 150 유지 (post-velocity 사용 시 30) - 기존 좌측 wall test expected 18 → 45 (wallSpinTransfer 0.6 → 1.5) 총 24/24 통과. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… motion responsiveness (#25 stage3 polish v5) 작업지시자 5차 피드백 + 옵션 C 채택: - 좌우 기울임에 알약 뺑글뺑글 → 비현실적 - 이동 둔함 (gScale 1000도 부족) 회전 모델 재설계 (rolling-without-slipping): - Wall impulse 폐기 (충돌 시 큰 spin 부여 X) - 매 tick wall contact 검사 → ω 가 v_tangent/r 로 lerp 수렴 - 자유 비행 시 angularDamping 강하게 (0.95→0.80, 0.5초 안 정지) - pair 충돌 spin 약화 (2.5→0.3) 이동 부스트: - gScale 1000→1500, damping 0.92→0.95 - terminal: 208→500 pt/s @Gravity=1.0, 일상 0.5에서 250 pt/s - restitution 0.3→0.2 (안정 정착) 신규 상수: wallSpinLerp 0.3, wallContactEpsilon 1.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ted damping/restitution (#25 stage3 polish v5) 회전 모델 변경: - 좌측 벽 contact: target ω = dy/r → lerp 0.3 적용 (impulse 폐기) - 우측 벽 contact: 반대 부호 target - 좌측 벽 지속 contact 30 tick → target 으로 수렴 (신규) - 자유 비행 (벽 contact 없음) → ω 변화 X (신규) - angularDamping 0.95 → 0.80: 100 → 80 이동 변경: - damping 0.92 → 0.95: 100 → 95 (per tick) - restitution 0.3 → 0.2: bounds bounce velocity ±15 → ±10 - 정지 60 tick → 120 tick (damping 약해진 보정) 총 25/25 통과. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… stage3 polish v6) 작업지시자 6차 피드백: - gravity (0,1) 에서 알약들이 봉지 가운데 세로 column 으로 stack - 의도: 봉지 바닥에 가로로 펼쳐 흘러야 함 Root cause: sphere-sphere 모델은 vertical stack 시 push apart 가 위/아래로만 작용 → horizontal spread 0. 실 약봉지는 알약이 길쭉해 stack 시 옆으로 미끄러지지만 sphere 로는 못 표현. 수정: - resolvePairCollisions: |nx| < 0.3 인 vertical stack 충돌 시 위/아래 알약에 반대 부호 horizontal nudge (12 pt/s) - 부호는 id.hashValue & 1 로 deterministic 결정 → 알약마다 일관 - PillBody.mock: 시작 시 ±8 pt/s deterministic dx jitter (gravity 적용 시 자연 spread) 신규 상수: verticalStackBreakingThreshold 0.3, verticalStackBreakingNudge 12 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…25 stage3 polish v6) - 수직 stack(|nx|≈0) 시 양쪽 dx 반대 부호 nudge - 수평 정렬(nx=1) 은 stack-breaking 미적용 — 정상 elastic 만 총 27/27 통과. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…stage3 polish v7) 작업지시자 7차 피드백: - v6 stack-breaking 이 vertical(아래) 방향만 동작 - 좌/우/위 gravity 시 알약이 그쪽 벽 따라 column 형성, 안 깨짐 Root cause: v6 가 |nx|<0.3 (수직 stack 만) 으로 hardcode. 일반화: stack 의 본질은 'normal 이 gravity 방향과 평행'. 수정: - resolvePairCollisions(&pills, gravity:) 시그니처 변경 (gravity 전달) - |normal · ĝ| > 0.95 (cos 18°) 일 때 stack 으로 간주 - nudge 방향 = gravity 의 perpendicular (-gy, gx) - upper = pos · ĝ 작은 쪽 (gravity 반대편) - gNorm > 0.1 가드 (mock motion transition 시 작은 gravity skip) 상수 이름 변경: - verticalStackBreakingThreshold (0.3) → stackParallelDotThreshold (0.95) - verticalStackBreakingNudge → stackBreakingNudge Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ro-gravity guard (#25 stage3 polish v7) 기존 2개 (수직 stack / 수평 정렬) 대체 + 일반화: - gravity 아래(0,1): 수직 stack → ±x nudge - gravity 좌측(-1,0): 수평 stack → ±y nudge - gravity 대각(1,1): perpendicular(-0.707, 0.707) → dx·dy<0 - 수평 정렬 + 아래 gravity: |n·ĝ|=0 → nudge 미적용 - gravity 거의 0(0, 0.05): guard 로 nudge 미적용 resolvePairCollisions 호출 모두 gravity 인자 추가. 총 28/28 통과. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-gravity corner spread (#25 stage3 polish v8) 작업지시자 8차 피드백: - 대각선 gravity 시 알약이 살짝 서있는 케이스 Root cause: 대각 gravity (0.707, 0.707) 시 알약이 corner 모이면 일부는 wall-aligned stack (normal (0,±1) 또는 (±1,0)) 형성. 이 normal·gravity dot = 0.707 → v7 threshold 0.95 미달 → trigger X. 수정: - stackParallelDotThreshold 0.95 (cos 18°) → 0.7 (cos 45°) 대각 gravity 에서 wall-aligned stack 도 catch - stackBreakingNudge 12 → 18 (corner 모임 spread 강도) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… stage3 polish v8) 대각 gravity (1,1) + wall-aligned vertical stack normal (0,1) → dot 0.707 > threshold 0.7 → nudge 발생 검증. 총 29/29 통과. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#25) Mock: Timer + closure capture self → Task + Task.sleep - @sendable closure 안에서 var self 캡처 불가 (Swift 6) - Task { @mainactor [weak self] } 로 main isolation 보장 + cancel 명확 Real: CMMotionManager closure → MainActor.assumeIsolated - .main OperationQueue 라 메인 스레드 fire 사실 단언 - closure capture 변경 없이 self 호출 가능 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task.sleep(nanoseconds: UInt64(1_000_000_000.0 / 30.0)) → Task.sleep(for: .seconds(1.0 / 30.0)) 30Hz tick interval 의도가 한눈에 보이도록. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rientation-aware gravity (#25 stage3 polish v9) PillPhysicsEngine: - 정지/동마찰 분리 (staticFrictionGravityThreshold 0.08, kinetic 0.03, staticFrictionVelocityThreshold 5) — 안 흔들면 안 움직임 - frame-rate 보정 damping (pow(damping, dt*60)) — dt 변동에 일관 거동 - sleepIfNeeded — 잔류 속도/회전 0 으로 붙여 sensor noise creep 차단 - settlingNormalVelocity 45 — 약한 침범은 튕김 X 정착 - StepResult — 충돌 강도 기반 haptic 이벤트 (threshold 160, fullScale 440) - tick 에서 bounds collision 2회 호출 — pair push-apart 후 재클램프 RealMotionEngine: - smoothingFactor 0.22 lerp — 센서 떨림 완화 - hasReceivedGravity flag — 첫 샘플 즉시 반영 (시작 lag 방지) - mapToCurrentScreenCoordinates — portrait/upsideDown/landscape 4방향 회전 좌표 매핑 (UIWindowScene.interfaceOrientation 기반) PouchView: tick StepResult 활용 (햅틱 연동) 테스트 추가: - 작은_중력은_정지마찰로_무시된다 - damping은_dt_기반으로_적용된다 - 약한_벽_침범은_튕기지_않고_정착한다 - 강한_벽_충돌은_haptic_event를_남긴다 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on (#25 stage4) - ZigZagTear Shape: amplitude 3pt, halfPeriod 6pt, animatableData=progress - state .sealed → 미표시, .tearing(p) → progress 비율 zigzag, .torn → full + cut-edge shade - 좌/우 inset 16pt (PaperLayer 점선 inset 과 동일) - perforation Y 위치는 PouchView.perforationY(in:) 공유 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d haptics (#25 stage4) - state @binding 으로 변경 (gesture 가 변경 가능) - DragGesture(minimumDistance: 4) — perforation Y ±20pt 시작점 hit-test - progress = translation.width / (width - tearMargin*2), clamp 0~1 - 매 10% step 도달 시 light haptic, 50% 임계 통과 시 success notification - spring(0.32, 0.74) 으로 sealed↔torn snap - PouchTearLayer ZStack 합성 + perforationY(in:) static helper 신규 - PouchHapticDriver 에 playTearStep / playTearSuccess 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- pouchState State + PouchView 에 binding 전달 - "찢기" / "봉합" 버튼: withAnimation 으로 spring 전환 - "Tear: sealed/tearing N%/torn" 라벨로 상태 가시화 - Reset 시 .sealed 복귀 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… redesign) 작업지시자 피드백: 기존 single-line zigzag 시각이 약함 — "찢어졌다" 가 시각으로 안 와닿음. 두 가지 스타일 구현 + Showcase Picker 토글: - TearStyle.lift: 봉지 위쪽 조각이 PaperLayer 복제본 + Rectangle mask 로 분리, progress 따라 위로 -8pt + 시계방향 6° 회전 + shadow. 들린 조각 하단 + 본체 상단 모두 jagged edge. - TearStyle.gap: perforation 라인에 어두운 직사각형(rect) 이 progress 따라 좌→우 점진적 확대 (max width usable*progress, max height 5pt). 위/아래 jagged edge 모두 표시. PouchView 에 tearStyle 인자 추가, Showcase 에 segmented Picker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion (#25 stage4 approach A) 작업지시자 결정: 방식 A (PaperLayer 자체를 두 조각으로 분리). 이전 방식 B (PaperLayer 복제본 mask + transform) 는 본체와 복제본 이중 그리기로 들린 조각 아래 봉지 안쪽이 막혀 보이는 한계. 변경: - PouchPaperTop / PouchPaperBottom wrapper view 신규 (PouchPaperLayer.swift) 각자 PouchPaperLayer 를 그리고 horizontal Rectangle mask 로 위/아래만 노출 - PouchView: tearStyle == .lift 일 때 PaperBottom + PaperTop(transform) 두 조각. .gap 일 때는 기존 PouchPaperLayer (단일) 유지. - PaperTop transform: progress 따라 offset -8pt + 회전 6° + shadow 0~0.20 - PouchTearLayer.liftView 단순화: 본체 측 jagged edge 만 (PaperLayer 분리는 PouchView 가 처리) 이제 위쪽 조각이 들리면 그 자리에 알약 영역이 진짜 보임 — 진정한 분리 시각. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#25 stage4 jagged mask) 작업지시자 피드백: - 지그재그가 너무 규칙적 - 위/아래 단면이 안 맞음 (mask 가 직선 + zigzag stroke 만 그어진 상태) 해결: mask 자체를 jagged path 로 + 위/아래 같은 seed 사용해 단면 일치. 신규: - JaggedTearPath Shape — seeded RNG 기반 무작위 jagged. region: .top/.bottom 로 위/아래 mask 영역. periodJitter 0.6~1.4 + ampJitter 0.35~1.30 + alternating direction. progress 비율만큼 좌→우 진행, 나머지는 perforation Y 직선. - JaggedRNG (xorshift) — deterministic, 매 frame 같은 path → flicker X - PouchPaperSplit namespace — tearInset, tearSeed 공유 상수 수정: - PouchPaperTop/Bottom: tearProgress: Double props 추가, mask 를 JaggedTearPath 로 (양쪽 같은 seed) - PouchView.paperLayerStack: tearProgress 전달 - PouchTearLayer.liftView: 빈 view (mask 가 시각 표현 — 별도 stroke 불필요) 이제 위/아래 조각 단면이 무작위 jagged + 정확히 맞물림. sealed 일 때는 perforation Y 직선이라 봉합 시각 자연스러움. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…off (#25 stage4 progress hold) 작업지시자 피드백: - drag 끝나면 50% 임계로 sealed/torn snap → 어색 (확 줄거나 확 찢김) - 진행도 그대로 유지하고 다음 drag 가 이어서 진행하기 변경: - 50% 임계 폐기. snap-back / auto-torn 제거. - handleTearEnded: 진행도 유지, dragBaseProgress 만 갱신 (다음 drag 의 base) - handleTearChanged: dragBaseProgress + dragDelta = progress. 100% 도달 시에만 자동 .torn (success haptic + spring) - onChange(of: state): 외부 .sealed/.torn 변경(Showcase 버튼) 시 base 동기화. drag 중 .tearing 갱신은 자기 자신이라 무시. 이제 30% 까지 찢고 손 떼면 30% 유지, 다시 drag 하면 30% 부터 이어짐. 100% 도달 (또는 봉합 버튼) 시에만 명시적 전환. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ge4 cleanup) 작업지시자 결정: lift 모드만 채택. gap 제거. 변경: - TearStyle enum, .gap case, gapView, ZigZagEdge Shape 제거 - PouchTearLayer view 자체 제거 (mask 가 시각 표현 — view 불필요) - PouchTearLayer.swift 는 JaggedTearPath + JaggedRNG 만 보유 (PaperTop/Bottom mask 가 사용) - PouchView: tearStyle props 제거, paperLayerStack 단순화 (항상 PaperBottom + PaperTop transform) - PouchShowcaseView: tearStyle State + Picker 제거 이제 단일 분기 — PaperLayer 분리 + JaggedTearPath mask 만. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hold → cleanup) (#25 stage4) Stage 4 보고서에 5개 polish 사이클 박제: 1. Lift/Gap 두 스타일 비교 (Picker 토글) 2. Approach A 채택 (PaperLayer 분리, B의 이중 그리기 한계 해결) 3. Jagged mask + interlocking 단면 (seeded RNG, 위/아래 동일 path) 4. Progress hold + resume (50% snap 폐기, drag 끝나도 진행도 유지) 5. Gap 제거 + cleanup (lift 단일) 각 사이클의 root cause / 변경 / commit hash / 의사결정 박제. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#25 stage5 prototype 1+4) 작업지시자 결정: 옵션 1(봉지 fade-out + 자리에 리스트) + 4(알약 stagger spring 안착) 결합. 신규: - PillCategoryDisplayName.label(for:) — 16종 카테고리 → 한글 라벨 - PouchView.listMode State — torn 진입 시 true - pillsBeforeTear State — 봉합 시 mock 위치 복원용 백업 - syncListMode / startListLayout / restorePhysicsLayout 함수 - listSlotPosition: 봉지 안 좌측 정렬, 세로 균등 간격 (rowSpacing = height / (count+1)) 동작: - .torn 진입 → physics 정지 + 알약별 spring(.55, .72) 으로 list slot 이동. stagger delay = index * 0.08초. - PaperTop/Bottom 이 listMode 시 opacity 0 + offset y -30, easeOut 0.45초. - 알약 옆에 한글 라벨 표시 (offset x +60, opacity transition). - .sealed 복귀 → spring(.45, .78) 으로 mock 위치 복원, paper 다시 fade-in. 포물선은 차후 polish — 첫 cut 은 spring 직선 보간으로 시작. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
작업지시자 결정: layout B (이름 · 회차 / 시각).
신규:
- PillBody.dose: Int — 회차 ("1정", "2정"). mock 에서 1/3 알약은 2정.
- PillBody.takenAt: Date? — torn 시 PouchView 가 set, 봉합 시 nil 복원.
Layout:
- HStack(width: 180): 좌측 "오메가3 · 1정" (PPFont.body, primary)
+ Spacer + 우측 시각 "7:23" (callout monospaced, secondary)
- 알약 우측 startOffset 30pt + labelWidth 180pt 영역.
- takenAt 은 한 봉지 = 한 슬롯 동시 복용 → 모든 알약 같은 시각.
DateFormatter 대신 Text(date, format: .dateTime.hour().minute()) 사용 — locale 자동.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…stage5) 옵션 1+4 (봉지 fade + stagger spring) + layout B (이름·회차/시각) 박제. 스코프 변경 (낙하 → list mode) 의사결정 + 누적 커밋 정리. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cleanup) PouchTearLayer view 자체는 stage4 cleanup 에서 제거됨. 파일 안에는 JaggedTearPath Shape + JaggedRNG 만 남았으니 파일명 일치시킴. 헤더 코멘트도 갱신. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ace (#25 cleanup) 이전: PouchView.pillBounds / PouchView.perforationY / PouchPaperLayer.Const 세 곳에 같은 magic number (14+38+36) 가 hardcode. 신규 PouchGeometry enum — single source: - topSealHeight 14, bottomSealHeight 12 - headerCenterOffset 38, headerDividerOffset 36 - perforationY(in:), pillBounds(in:) 정적 함수 PouchView.pillBounds / perforationY 는 PouchGeometry 로 위임 (외부 호출 호환성 유지). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #25
Summary
단일 약봉지 컴포넌트 — 한국 약국 약봉지 UX 정밀 재현. 5 stage L 사이클로 진행, 총 9차 polish 사이클을 거쳐 모션 / 찢기 / 리스트 전환까지 완성.
Stage 보고서
ADR
변경 통계
가설 B 체크
Test plan
xcodebuild build통과 (iPhone 17 Pro / iOS 26.4)별도 task (이번 PR 외)
docs/brief.md§화면 참조🤖 Generated with Claude Code