Skip to content

fix(WRP-1402): 디자인 시스템 렌더링 최적화 — analytic 그림자 + Switch·Menu jank 제거#519

Merged
knine79 merged 16 commits into
release/4.0.0from
refactor/WRP-1402-analytic-shadow
Jul 6, 2026
Merged

fix(WRP-1402): 디자인 시스템 렌더링 최적화 — analytic 그림자 + Switch·Menu jank 제거#519
knine79 merged 16 commits into
release/4.0.0from
refactor/WRP-1402-analytic-shadow

Conversation

@knine79

@knine79 knine79 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

개요

WRP-1402 디자인 시스템 렌더링 최적화. Blueprint 인터랙션 프레임 드랍(jank)의 원인을 제거하면서 시각적 외형은 유지한다.

배경

Blueprint 전반의 인터랙션(스위치 토글, Menu 라벨 갱신, 스크롤)에서 프레임 드랍이 관측됐다. 시뮬레이터·실기기(iOS 26) Release 모두 재현. 근본 메커니즘은 GPU 합성(렌더 서버) 부하 — 오프스크린 렌더링, 레이어 블렌딩, 그리고 iOS 26 네이티브 컨트롤의 fast-path 이탈이다. 앱-attach Time Profiler에는 잡히지 않아(렌더 서버는 별도 프로세스) CPU/색상 최적화로는 체감이 바뀌지 않았다.

변경 사항

1. Analytic 그림자 (오프스크린 렌더링 제거)

  • ShapeStyle.shadow(_:) API 추가 — Shape.fill에 적용하면 그림자가 지오메트리로부터 직접 그려져 오프스크린 패스가 발생하지 않는다. 기존 View.shadow(_:)는 임의 콘텐츠 실루엣을 알기 위해 오프스크린을 유발(특히 clipShape/머티리얼과 함께).
  • 적용: TextField·Select 표면, SegmentedControl·TextArea 인디케이터, FramedStyle.
  • 그림자 메트릭(색·반경·위치)은 기존 값 그대로 보존.

2. Switch — tint 기인 on/off jank 제거

  • 네이티브 Toggle/UISwitch커스텀 tint 색(.tint 또는 onTintColor)을 지정하는 순간 iOS 26 Liquid Glass 최적화 경로를 벗어나 fallback 렌더로 떨어져, on/off 애니메이션에서 프레임 드랍이 발생한다. (단일변수 격리로 확인: transform 무관, 브랜드색 무관, onTintColor 지정 한 줄만 빼면 사라짐)
  • 네이티브 대신 커스텀 Capsule(트랙) + Capsule(thumb)로 직접 그려 해당 경로를 회피. 트랙색은 기존 backgroundColor를 그대로 재사용하고, 비활성-꺼짐 상태(.clear)는 quaternaryLabel 캡슐로 대체해 반투명 트랙을 유지. thumb는 iOS 26 디자인에 맞춘 가로 캡슐 형태. 접근성은 accessibilityRepresentationToggle로 보존.

3. MenuOptionRow — 라벨 reflow jank 완화 (Blueprint)

  • Menu 라벨이 폭이 다른 문자열로 바뀌면 버튼 리사이즈 → 행 reflow로 프레임 드랍. 라벨을 maxWidth: .infinity로 채워 프레임을 고정.

검증

  • 시뮬레이터(iPhone 17 Pro, iOS 26)에서 각 컴포넌트 외형 시각 검증 (켜짐/꺼짐, small/medium, 체커 투명도).
  • Switch·Menu jank 재현/해소 확인.
  • 빌드 경고·에러 0.

🤖 Generated with Claude Code

@knine79 knine79 requested a review from a team as a code owner June 29, 2026 07:55
@knine79 knine79 requested review from jhseo and removed request for a team June 29, 2026 07:55
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Shadow.swift에서 공유 헬퍼와 ShapeStyle.shadow API가 추가되고, 여러 선택 입력 컴포넌트의 그림자 렌더링이 shape 기준으로 바뀝니다. Control.switch는 커스텀 캡슐 스위치로 교체되며, PreviewOption, 라이선스 스크립트, Blueprint 빌드 버전도 함께 수정됩니다.

Changes

Shadow 헬퍼 추출 및 컴포넌트 적용

Layer / File(s) Summary
Shadow 공유 헬퍼 추출 및 API 문서화
Sources/Montage/1 Components/9 Utilities/Shadow.swift
Shadow.LayercolorOpacity/radius/position/color 정적 헬퍼를 추가하고, ModifierShapeStyle가 이를 사용하도록 바꿉니다. View.shadow 문서 주석에 오프스크린 렌더링 경고와 ShapeStyle.shadow 안내가 추가됩니다.
ShapeStyle.shadow 문서 추가
documentation/utilities/ios-extensions/swiftuicore.md, documentation/utilities/ios-utility-components/shadow.md
ShapeStyle.shadow(Shadow.Level) 설명, Discussion, 예시를 추가하고 View.shadow 문서의 경고와 예시 구성을 갱신합니다.
컴포넌트별 shape fill 기반 그림자 적용
Sources/Montage/1 Components/3 Selection And Input/FramedStyle.swift, SegmentedControl.swift, TextArea.swift, Select.swift, TextField.swift
FramedStyle, SegmentedControl, TextArea, Select, TextField에서 View 레벨 shadow를 shape fill 또는 surface 기반 렌더링으로 바꿉니다.

Control 커스텀 캡슐 스위치 구현

Layer / File(s) Summary
커스텀 캡슐 스위치 구현
Sources/Montage/1 Components/3 Selection And Input/Control.swift
switchThumb를 추가하고, .switch 케이스를 thumb 계산, 배경 캡슐 ZStack, offset 애니메이션, 탭 처리, accessibilityRepresentation Toggle 매핑으로 교체합니다.

기타 변경

Layer / File(s) Summary
MenuOptionRow 라벨 폭 확장
Sources/Blueprint/Sources/Utilities/PreviewOption.swift
MenuOptionRow가 label 클로저로 Text를 만들고 최대 폭을 채우도록 바뀌며 Spacer(minLength: 0)가 제거됩니다.
라이선스 스크립트 HTTP 오류 처리 강화
scripts/generate_third_party_licenses.mjs
tryFetch()가 fetch 응답의 non-OK 상태를 null로 처리하고, curl 폴백에 HTTP 오류를 실패로 만드는 플래그를 사용합니다.
Blueprint 빌드 버전 갱신
Sources/Blueprint/Blueprint.xcodeproj/project.pbxproj
Debug와 Release의 CURRENT_PROJECT_VERSION20260629003으로 변경됩니다.

추정 코드 리뷰 노력

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • agiletalk

Possibly related PRs

  • wanteddev/montage-ios#507: Sources/Montage/1 Components/3 Selection And Input/TextField.swiftTextField 렌더링과 배경/그림자 처리 변경이 직접 겹칩니다.
  • wanteddev/montage-ios#518: scripts/generate_third_party_licenses.mjstryFetch()에서 non-OK 응답과 curl 폴백 처리 방식이 동일한 주제로 수정됩니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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
Title check ✅ Passed 제목이 analytic 그림자 적용, Switch 교체, Menu jank 완화라는 주요 변경을 정확히 요약합니다.
Description check ✅ Passed 설명이 렌더링 최적화, ShapeStyle.shadow 추가, Switch/Menu 개선 등 실제 변경 사항과 일치합니다.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/WRP-1402-analytic-shadow

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

@knine79 knine79 force-pushed the refactor/blueprint-preview-layout branch from 80c0a86 to fc31f89 Compare June 29, 2026 07:57
@knine79 knine79 force-pushed the refactor/WRP-1402-analytic-shadow branch from da016e2 to 04f422d Compare June 29, 2026 08:01
@github-actions github-actions Bot added the in review This issue requires a review. label Jun 29, 2026
@knine79 knine79 marked this pull request as draft June 29, 2026 08:02
@knine79 knine79 removed the request for review from jhseo June 29, 2026 08:02
@knine79 knine79 self-assigned this Jun 29, 2026
@knine79 knine79 added this to the 4.0.0 milestone Jun 29, 2026

@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

🧹 Nitpick comments (1)
Sources/Montage/1 Components/3 Selection And Input/Control.swift (1)

80-84: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

움직이는 thumb에는 View.shadow() 대신 analytic shadow를 쓰는 편이 좋겠습니다.

이 thumb는 토글할 때마다 애니메이션되는 요소인데, 여기만 다시 view-level shadow를 쓰면 이번 PR의 오프스크린 렌더링 제거 목적을 일부 되돌릴 수 있습니다. 가능하면 새로 추가된 ShapeStyle.shadow(_:) 쪽으로 맞춰 주세요.

🤖 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 `@Sources/Montage/1` Components/3 Selection And Input/Control.swift around
lines 80 - 84, The switchThumb view is using a view-level shadow, which can
reintroduce offscreen rendering for this animated thumb. Update the switchThumb
implementation in Control.swift to use the new ShapeStyle.shadow(_) analytic
shadow on the Capsule fill instead of View.shadow(), keeping the same visual
styling while matching the rest of the PR.
🤖 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 `@documentation/utilities/ios-extensions/swiftuicore.md`:
- Around line 20-31: `ShapeStyle.shadow` 문서 설명이 손상되어 핵심 문구와 Discussion 내용이
빠졌습니다. `ShapeStyle.shadow(level:)` DocC 블록을 `Sources/Montage/1 Components/9
Utilities/Shadow.swift`의 원문과 맞게 복원하고, 반환 설명과 Discussion의 GPU 합성 비용/오프스크린 회피 문구가
빠지지 않도록 다시 생성하거나 해당 문단을 보정하세요.

In `@documentation/utilities/ios-utility-components/shadow.md`:
- Around line 77-88: The `ShapeStyle.shadow` documentation text is malformed and
missing part of the intended wording. Regenerate or manually correct the DocC
content for `ShapeStyle.shadow` so the opening sentence is complete and
grammatical, and restore the missing Discussion sentence fragment to match the
source documentation; use the `ShapeStyle.shadow` section and its
`Discussion`/`Return Value` content as the source of truth.

In `@Sources/Montage/1` Components/3 Selection And Input/Select.swift:
- Around line 396-410: The Select background is applying the same shadow twice,
once in this background block and again in the higher-level RoundedRectangle
used by the Select container. Remove the redundant .shadow(...) from the
duplicated layer so only one shadow is applied, keeping the remaining shadow on
the appropriate RoundedRectangle path in Select.swift.

---

Nitpick comments:
In `@Sources/Montage/1` Components/3 Selection And Input/Control.swift:
- Around line 80-84: The switchThumb view is using a view-level shadow, which
can reintroduce offscreen rendering for this animated thumb. Update the
switchThumb implementation in Control.swift to use the new ShapeStyle.shadow(_)
analytic shadow on the Capsule fill instead of View.shadow(), keeping the same
visual styling while matching the rest of the PR.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b42b24bf-a20a-4ade-a3e7-df5de82f9dca

📥 Commits

Reviewing files that changed from the base of the PR and between 80c0a86 and da016e2.

📒 Files selected for processing (11)
  • Sources/Blueprint/Sources/Utilities/PreviewOption.swift
  • Sources/Montage/1 Components/3 Selection And Input/Control.swift
  • Sources/Montage/1 Components/3 Selection And Input/FramedStyle.swift
  • Sources/Montage/1 Components/3 Selection And Input/SegmentedControl.swift
  • Sources/Montage/1 Components/3 Selection And Input/Select.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextArea.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextField.swift
  • Sources/Montage/1 Components/9 Utilities/Shadow.swift
  • THIRD_PARTY_LICENSES.md
  • documentation/utilities/ios-extensions/swiftuicore.md
  • documentation/utilities/ios-utility-components/shadow.md

Comment thread documentation/utilities/ios-extensions/swiftuicore.md
Comment thread documentation/utilities/ios-utility-components/shadow.md
Comment thread Sources/Montage/1 Components/3 Selection And Input/Select.swift

@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 `@THIRD_PARTY_LICENSES.md`:
- Around line 22-27: The NOTICE block contains an HTTP error string instead of
valid license notice text, so update the THIRD_PARTY_LICENSES.md NOTICE section
to restore the actual upstream NOTICE content or remove the placeholder
entirely. Keep the change focused on the NOTICE entry itself and ensure the
documented third-party notice text is accurate and compliant.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 83ad4954-7c1d-49f2-b58f-f4928ff12758

📥 Commits

Reviewing files that changed from the base of the PR and between da016e2 and 04f422d.

📒 Files selected for processing (11)
  • Sources/Blueprint/Sources/Utilities/PreviewOption.swift
  • Sources/Montage/1 Components/3 Selection And Input/Control.swift
  • Sources/Montage/1 Components/3 Selection And Input/FramedStyle.swift
  • Sources/Montage/1 Components/3 Selection And Input/SegmentedControl.swift
  • Sources/Montage/1 Components/3 Selection And Input/Select.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextArea.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextField.swift
  • Sources/Montage/1 Components/9 Utilities/Shadow.swift
  • THIRD_PARTY_LICENSES.md
  • documentation/utilities/ios-extensions/swiftuicore.md
  • documentation/utilities/ios-utility-components/shadow.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • Sources/Montage/1 Components/3 Selection And Input/SegmentedControl.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextArea.swift
  • Sources/Montage/1 Components/3 Selection And Input/FramedStyle.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextField.swift
  • Sources/Blueprint/Sources/Utilities/PreviewOption.swift
  • Sources/Montage/1 Components/3 Selection And Input/Select.swift
  • Sources/Montage/1 Components/3 Selection And Input/Control.swift
  • Sources/Montage/1 Components/9 Utilities/Shadow.swift

Comment thread THIRD_PARTY_LICENSES.md Outdated
@github-actions github-actions Bot added accepted This issue has been reviewed. and removed in review This issue requires a review. labels Jun 29, 2026
@knine79 knine79 force-pushed the refactor/WRP-1402-analytic-shadow branch from 62af813 to 82951ca Compare June 29, 2026 08:27
knine79 and others added 13 commits June 30, 2026 13:35
네이티브 Toggle/UISwitch는 커스텀 tint(.tint·onTintColor)를 지정하는 순간 iOS 26 Liquid Glass 최적화 경로를 벗어나 on/off 애니메이션에서 프레임 드랍이 발생한다. 트랙(Capsule)·thumb(Capsule)을 직접 그려 그 경로를 피한다. 트랙색은 기존 backgroundColor를 그대로 쓰고, 비활성-꺼짐은 .clear(네이티브 색 노출용)이던 것을 ZStack 하위 quaternaryLabel 캡슐로 대체해 반투명 트랙을 유지한다. 접근성은 accessibilityRepresentation의 Toggle로 보존한다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01KB2eZUrUb5uKoUExoX9Fs7
선택값(menuLabel)이 폭이 다른 문자열로 바뀌면 Menu 버튼이 리사이즈되며 행이 reflow돼 프레임 드랍이 생긴다. 라벨을 maxWidth: .infinity로 채워 버튼 프레임을 고정한다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01KB2eZUrUb5uKoUExoX9Fs7
tryFetch가 응답 본문 문자열에만 의존해, HTTP 오류 응답(레이트리밋 안내·에러
페이지 등 '404: Not Found'가 아닌 본문)을 라이선스 텍스트로 오인하던 문제를 수정.
- fetch: 비-2xx 응답은 즉시 null 반환
- curl 폴백: -f로 HTTP >=400을 비0 종료로 처리

이로 인해 실제 태그(v1.0.0)보다 먼저 시도되는 잘못된 ref 후보(예: 1.0.0/LICENSE.md)의
일시적 비정상 응답을 채택해 pretendard-ios 라이선스가 Unknown으로 퇴화하던 현상을 방지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8gTT3K3Hci759WS9qMf8r
main의 생성기 수정(3785836·f13f69a65)을 체리픽해, NOTICE 페치 실패 시 에러 본문(400: Invalid request)을 기록하지 않고 블록을 생략한다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01KB2eZUrUb5uKoUExoX9Fs7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8gTT3K3Hci759WS9qMf8r

# Conflicts:
#	Sources/Blueprint/Blueprint.xcodeproj/project.pbxproj
@knine79 knine79 force-pushed the refactor/WRP-1402-analytic-shadow branch from 6fd4c84 to 57326af Compare June 30, 2026 04:37
@github-actions github-actions Bot added in review This issue requires a review. and removed accepted This issue has been reviewed. labels Jun 30, 2026
Base automatically changed from refactor/blueprint-preview-layout to release/4.0.0 July 6, 2026 05:57
@knine79 knine79 marked this pull request as ready for review July 6, 2026 05:58
@knine79 knine79 merged commit 964061a into release/4.0.0 Jul 6, 2026
2 checks passed
@knine79 knine79 deleted the refactor/WRP-1402-analytic-shadow branch July 6, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in review This issue requires a review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant