Skip to content

feat: [iOS] Textarea 업데이트#516

Draft
knine79 wants to merge 4 commits into
release/4.0.0from
feature/WRP-1247
Draft

feat: [iOS] Textarea 업데이트#516
knine79 wants to merge 4 commits into
release/4.0.0from
feature/WRP-1247

Conversation

@knine79

@knine79 knine79 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

개요

수정사항

  • TextArea 컴포넌트 시안 정합 업데이트
  • 연계되는 TextField 정리
  • Blueprint 프리뷰(TextArea·TextField) 갱신 및 DocC/MCP 문서 재생성

미리보기

작업 전 작업 후
스크린샷 스크린샷

- size(large/medium) 속성 추가 및 사이즈별 토큰 적용
- focus ring 추가, 상태별 테두리 색상 정비(negative는 빨강 ring)
- resize 높이 정책을 줄 수 기준(최소 2줄, limit 6줄)으로 정밀화, 입력 폰트 사이즈별(body2/label1) 적용
- character count UI 제거 → maxLength(입력 제한·축소 시 기존 텍스트 잘림) + onTextChange API로 대체
- bottom resource 재설계: button/iconButton/icon/contentBadge/segmentedControl/primaryIconButton/slot, 사이즈별 크기·leading 제약·가변 높이
- primaryIconButton을 Button(solid/primary/iconOnly)로 구성
- bottom 전용 아이콘 SegmentedControl 신설(슬라이드 선택 애니메이션)
- maxLength 초과 붙여넣기 시 캐럿을 삽입 끝으로 이동
- TextArea heading/requiredBadge/description API 제거
- TextField heading/requiredBadge 및 Status의 description API 제거
- TextArea/TextField Blueprint 프리뷰 룩앤필 정리 및 DocC 문서 재생성

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hm3CNGafWMFkTDmZ8gWuoT
@knine79 knine79 self-assigned this Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8eaea9b3-555a-4342-8a5f-ea0b27408de5

📥 Commits

Reviewing files that changed from the base of the PR and between fc2ba6a and 5095fa6.

📒 Files selected for processing (1)
  • Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift

Summary by CodeRabbit

  • New Features
    • TextArea에 size/resize 선택, maxLength 길이 제한, onTextChange, 입력 단계 변환(inputTransform)을 추가했습니다.
    • TextArea 하단 리소스를 button/콘텐츠 배지/아이콘/아이콘 버튼/세그먼트/커스텀 slot 중심으로 재편해 구성을 확장했습니다.
    • TextField 옵션 UI를 간소화하고 자동완성 갱신 흐름을 개선했습니다.
  • Bug Fixes
    • 붙여넣기/입력 시 초과 처리와 커서 위치 복원이 더 안정적으로 동작합니다.
  • Documentation
    • TextArea/TextField iOS 문서 예시와 API 안내를 최신 구성을 반영해 업데이트했습니다.

Walkthrough

TextArea와 TextField의 공개 API, 렌더링, 프리뷰, 문서, MCP 스키마가 새 size/resize/maxLength/onTextChange 및 단순화된 status 모델에 맞게 갱신됐다.

Changes

입력 컴포넌트 재구성

Layer / File(s) Summary
TextArea 공개 API 변경
Sources/Montage/1 Components/3 Selection And Input/TextArea.swift
TextArea.Resource 케이스가 새 구성으로 교체되고 isLeadingAllowed가 추가됐다. size(_:), maxLength(_:), onTextChange(_:)가 추가되며 heading, requiredBadge, description, inputCharacterLimit이 제거됐다. bottomResources는 leading 필터링과 side당 최대 3개 제한을 적용한다.
TextArea 렌더링과 입력 제한
Sources/Montage/1 Components/3 Selection And Input/TextArea.swift
bodyeditor가 높이 계산, placeholder, onTextChange, focusRing, Bottom 구성으로 재정리됐다. UITextViewWrapperinputLimitinputTransform을 사용하고, caret 복원과 Coordinator 상태 갱신이 추가됐다. Size 토큰도 확장됐다.
TextArea 문서와 MCP 스키마
documentation/components/selection and input/textarea/ios.md, packages/montage-mcp/data/components.json
TextArea iOS 문서가 size, resize, maxLength, onTextChange, inputTransform 중심으로 갱신됐다. Resource, Resize, Size 문서와 isLeadingAllowed, slot 설명이 재구성되고, components.json의 TextArea 및 TextArea.Resource 스키마도 동기화됐다.
TextAreaPreview 재구성
Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift
Size/Resize 열거형과 resources 구성이 새 Resource 모델 기준으로 바뀌었다. 프리뷰 상태, TextArea 호출 체인, 옵션 UI, leading 필터링, maxLength 표시와 슬라이더 동작이 변경됐다.
TextField 공개 API 변경
Sources/Montage/1 Components/3 Selection And Input/TextField.swift
Statusdescription 연관값이 없는 단순 케이스로 바뀌고, TrailingButtonInfo는 variant 없이 초기화되도록 단순화됐다. heading(_:)requiredBadge(_:)가 제거되며 bodyinputField 중심으로 축소됐다. Size 토큰 기반 색상과 레이아웃 계산도 갱신됐다.
TextFieldPreview 재구성
Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift
Variant 매핑이 Status 단순 케이스 기준으로 바뀌고, 옵션 상태와 레이아웃 헬퍼가 재배치됐다. 자동완성 데이터소스 갱신이 refreshAutoCompletion()으로 분리되며, TextField modifier 체인과 옵션 UI에서 heading/requiredBadge/description 항목이 제거됐다.
TextField 문서와 MCP 스키마
documentation/components/selection and input/textfield/ios.md, packages/montage-mcp/data/components.json
TextField iOS 문서 예시와 Status 케이스 설명이 description 연관값 없는 형태로 갱신됐다. components.json의 TextField modifier 스키마에서도 headingrequiredBadge가 제거됐다.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.81% 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 TextArea 업데이트와 관련된 핵심 변경을 간결하게 요약하고 있어 제목으로 적절합니다.
Description check ✅ Passed TextArea, TextField, 프리뷰, 문서 재생성 등 변경 내용이 PR과 일치합니다.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/WRP-1247

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

@knine79 knine79 added the in review This issue requires a review. label Jun 24, 2026
@knine79 knine79 added this to the 4.0.0 milestone Jun 24, 2026
@knine79 knine79 changed the base branch from main to release/4.0.0 June 24, 2026 09:40

@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: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/montage-mcp/data/tokens.json (1)

357-469: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

실제 토큰 식별자가 MCP 데이터에서 빠졌습니다.

이제 공개 API는 Spacing/Opacity의 enum case가 아니라 CGFloat.spacing16, CGFloat.opacity52, CGFloat.radius12, CGFloat.primitive24 같은 정적 프로퍼티인데, 여기서는 각 네임스페이스의 allValues/max/min만 직렬화하고 있습니다. 그 결과 MCP 소비자는 새 토큰 이름을 검색·자동완성·매핑할 수 없고, 기존 enum case 제거 이후에는 개별 토큰을 발견할 경로가 사라집니다. CGFloat 확장의 실제 토큰 프로퍼티들도 별도 섹션이나 identifiers로 함께 노출해야 합니다.

🤖 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 `@packages/montage-mcp/data/tokens.json` around lines 357 - 469, The MCP token
metadata is only serializing aggregate enum-style entries for Spacing, Opacity,
Radius, and Primitive, so the real public token identifiers exposed by the
CGFloat extension are missing. Update the token JSON generation in the relevant
token sections to include the actual CGFloat static properties (for example the
spacing/opacity/radius/primitive token names) alongside or instead of the
generic allValues/max/min identifiers, so MCP consumers can search,
autocomplete, and map the real token symbols. Use the existing Spacing, Opacity,
Radius, Dimension, Primitive, and Shadow entries as the place to attach the
missing identifiers.
🤖 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-utility-components/opacity.md`:
- Around line 14-23: The SwiftUI example uses a CGFloat opacity token directly,
which does not match the Double-based API expected by myView.opacity; update the
example in the opacity documentation so the token is wrapped/conveyed as a
Double-compatible value while keeping the CGFloat examples for alphaComponent
and CGFloat variables unchanged. Use the opacity.md SwiftUI snippet as the place
to adjust the myView.opacity usage and ensure the example reflects the correct
type for SwiftUI.

In `@documentation/utilities/ios-utility-components/typography.md`:
- Around line 223-226: The `uiTextStyle` description is incomplete and missing
the referenced source, so update the documentation text near `uiTextStyle` to
explicitly mention `textStyle` and complete the sentence. Keep the same meaning
as the existing `textStyle` explanation, but rewrite the `uiTextStyle` line so
the relationship is clear and the sentence reads naturally.

In `@packages/montage-mcp/data/components.json`:
- Around line 1033-1036: The `interactionColor(_:)` method summary in
`components.json` has a truncated default value, so update the `summary` for the
`interactionColor(Color.Semantic) -> IconButton` entry to match the documented
default from the `IconButton` docs. Use the existing `interactionColor(_:)`
method record as the target and replace the incomplete `(기본값: )` text with the
correct default value so MCP consumers see the full description.

In `@Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift`:
- Around line 162-165: `TextAreaPreview`에서 `leadingResources`에 `suffix(3)`를 직접
대입하는 부분이 타입 불일치로 컴파일되지 않습니다. `leadingResources`와 `trailingResources`가
`[TextArea.Resource]`이므로, `suffix(3)`의 `ArraySlice<TextArea.Resource>` 결과를
`Array(...)`로 감싸도록 `TextAreaPreview`의 리소스 수집 로직을 수정하세요.

In `@Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift`:
- Around line 254-256: The auto-complete toggle in TextFieldPreview only updates
usingSuggestions, so the existing suggestion list can remain visible after
turning it off. Update the toggle handling in the
HStack/toggleRow("autoComplete", $usingSuggestions) flow so that when
usingSuggestions becomes false, autoCompletionDataSource is immediately reset to
nil as well. Use the existing TextFieldPreview state properties to keep the
preview synchronized right away.

In `@Sources/Montage/1` Components/3 Selection And Input/TextArea.swift:
- Around line 37-45: DocC in TextArea.Size is describing fixed minimum heights
that do not match the actual implementation in resolvedMinHeight, so update the
enum documentation to reflect the real height behavior instead of implying 48/44
constants. Adjust the comments on Size.large and Size.medium to align with the
2-line-based min-height calculation used by TextArea so consumers understand the
true contract.
- Around line 645-657: The icon segment in TextArea.segment is only tappable via
onTapGesture, so VoiceOver does not expose it as an interactive control with
name or selected state. Refactor the segment view to use a Button-based control
for each item, and add appropriate accessibilityLabel plus
selected/accessibilityValue or hint so the segmented control in TextArea is
announced and operable by VoiceOver.
- Around line 335-353: The fixed resize path in TextArea’s resolvedMinHeight and
resolvedMaxHeight should normalize the `.fixed(min:max)` bounds before they are
used. Update the `resize` handling so that the `.fixed` case always returns
ordered min/max values from the `TextArea` layout helpers, preventing an invalid
`frame(minHeight:maxHeight:)` when the inputs are reversed. Ensure the
normalization is applied consistently wherever `resolvedMinHeight` and
`resolvedMaxHeight` are derived from `resize`.

In `@Sources/Montage/1` Components/3 Selection And Input/TextField.swift:
- Around line 564-566: TextField의 fieldTextColor가 항상 .semantic(.labelNormal)로
고정되어 있어 disable 상태에서도 본문 텍스트가 활성 색상으로 보입니다. fieldTextColor 로직을 수정해 TextArea와 같은
방식으로 disable 여부를 반영하도록 하고, 비활성일 때는 disabled 톤의 색상을 반환하도록 TextField의 색상 계산을
조정하세요.
- Around line 424-440: `backgroundColor(_:)`가 저장하는 `customBackgroundColor`가
`fieldBackground`에서 전혀 사용되지 않아 공개 API가 무시됩니다. `TextField`의 `fieldBackground`에서
`customBackgroundColor`를 우선 적용하도록 분기하고, 값이 없을 때만 기존 `disable`/`colorScheme` 기반
배경 로직을 사용하도록 수정하세요. `backgroundColor(_:)`, `customBackgroundColor`,
`fieldBackground`를 함께 점검해 리팩터링 전 동작을 복원하면 됩니다.

In `@Sources/Montage/1` Components/5 Loading/Skeleton.swift:
- Around line 221-226: `Skeleton`의 `GeometryReader` 안에서 자동 `lineNumber`를 계산할 때
`round(proxy.size.height / scaledLineHeight)` 때문에 애매한 높이에서 한 줄을 과하게 그려 하단이 잘릴 수
있습니다. `effectiveLineCount` 계산식을 수정해 실제로 들어갈 수 있는 줄 수만 반영하도록 내림 기반 계산으로 바꾸고,
`kind.lineNumber`가 0일 때만 이 자동 계산이 적용되도록 유지하세요.

In `@Sources/Montage/1` Components/6 Navigations/Category.swift:
- Line 124: `Category`의 우측 액션 버튼에서 `iconSize`를 그대로
`IconButton.NormalSize.custom(size:)`에 넘기고 있어 새 계약(컨테이너 한 변 길이)과 맞지 않습니다.
`Category` 내 해당 `IconButton(variant: .normal(size: ...), icon: icon)` 호출을 찾아
`iconSize`(20/22/24)를 직접 전달하지 말고, `NormalSize`의 기존 케이스로 매핑하거나 목표 아이콘 크기에 맞는 컨테이너
크기로 변환해서 넘기도록 수정하세요.

In `@Sources/Montage/1` Components/6 Navigations/Tab.swift:
- Line 175: `Tab`의 아이콘 크기 값을 `IconButton.NormalSize.custom(size:)`에 그대로 넘기면 의미가
바뀌어 탭 버튼이 작게 렌더링됩니다. `Tab`의 `size`에서 계산한 `iconSize`를 직접 쓰지 말고, `Tab.Size`를
`IconButton.NormalSize`로 매핑하는 전용 변환 로직을 추가해 컨테이너 크기 기준으로 전달되도록 수정하세요.
`IconButton(variant:icon:)` 호출부와 `iconButtonSize` 같은 보조 프로퍼티/메서드를 찾아 이 매핑을 적용하면
됩니다.

---

Outside diff comments:
In `@packages/montage-mcp/data/tokens.json`:
- Around line 357-469: The MCP token metadata is only serializing aggregate
enum-style entries for Spacing, Opacity, Radius, and Primitive, so the real
public token identifiers exposed by the CGFloat extension are missing. Update
the token JSON generation in the relevant token sections to include the actual
CGFloat static properties (for example the spacing/opacity/radius/primitive
token names) alongside or instead of the generic allValues/max/min identifiers,
so MCP consumers can search, autocomplete, and map the real token symbols. Use
the existing Spacing, Opacity, Radius, Dimension, Primitive, and Shadow entries
as the place to attach the missing identifiers.
🪄 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: debc207d-b199-4403-997d-e263cf6e8acf

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9ea73 and 97b16b3.

📒 Files selected for processing (62)
  • .gitignore
  • Sources/Blueprint/Blueprint.xcodeproj/project.pbxproj
  • Sources/Blueprint/Sources/Scene/ComponentList/ComponentListView.swift
  • Sources/Blueprint/Sources/Scene/Previews/ActionAreaPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/ButtonPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/IconButtonPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/SkeletonPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/TypographyPreview.swift
  • Sources/Montage/1 Components/2 Actions/Button.swift
  • Sources/Montage/1 Components/2 Actions/IconButton.swift
  • Sources/Montage/1 Components/2 Actions/OutlinedUIButton.swift
  • Sources/Montage/1 Components/2 Actions/SolidUIButton.swift
  • Sources/Montage/1 Components/3 Selection And Input/Control.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/4 Contents/Accordion.swift
  • Sources/Montage/1 Components/4 Contents/Card.swift
  • Sources/Montage/1 Components/4 Contents/ContentBadgeUIView.swift
  • Sources/Montage/1 Components/4 Contents/Thumbnail.swift
  • Sources/Montage/1 Components/5 Loading/Skeleton.swift
  • Sources/Montage/1 Components/6 Navigations/Category.swift
  • Sources/Montage/1 Components/6 Navigations/Tab.swift
  • Sources/Montage/1 Components/7 Feedback/SnackBar.swift
  • Sources/Montage/1 Components/8 Presentation/Popover.swift
  • Sources/Montage/1 Components/9 Utilities/Color.swift
  • Sources/Montage/1 Components/9 Utilities/Dimension.swift
  • Sources/Montage/1 Components/9 Utilities/InteractionUIView.swift
  • Sources/Montage/1 Components/9 Utilities/ModalNavigation.swift
  • Sources/Montage/1 Components/9 Utilities/Opacity.swift
  • Sources/Montage/1 Components/9 Utilities/Primitive.swift
  • Sources/Montage/1 Components/9 Utilities/Radius.swift
  • Sources/Montage/1 Components/9 Utilities/Spacing.swift
  • Sources/Montage/1 Components/9 Utilities/Typography.swift
  • Sources/Montage/2 Utilities/Extension/SwiftUI/SwiftUI+Debug.swift
  • Sources/Montage/2 Utilities/Extension/UIKit/NSAttributedString+.swift
  • Sources/Montage/Asset/Color.xcassets/Pink/pink100.colorset/Contents.json
  • Sources/Montage/Asset/Color.xcassets/Violet/violet0.colorset/Contents.json
  • Sources/Montage/Asset/Color.xcassets/Violet/violet100.colorset/Contents.json
  • documentation/components/actions/button/ios.md
  • documentation/components/actions/iconbutton/ios.md
  • documentation/components/contents/accordion/ios.md
  • documentation/components/loading/skeleton/ios.md
  • documentation/components/selection and input/textarea/ios.md
  • documentation/components/selection and input/textfield/ios.md
  • documentation/utilities/ios-extensions/corefoundation.md
  • documentation/utilities/ios-extensions/swift.md
  • documentation/utilities/ios-utility-components/color.md
  • documentation/utilities/ios-utility-components/dimension.md
  • documentation/utilities/ios-utility-components/modalnavigation.md
  • documentation/utilities/ios-utility-components/opacity.md
  • documentation/utilities/ios-utility-components/primitive.md
  • documentation/utilities/ios-utility-components/radius.md
  • documentation/utilities/ios-utility-components/spacing.md
  • documentation/utilities/ios-utility-components/typography.md
  • packages/montage-mcp/data/components.json
  • packages/montage-mcp/data/markdown/coding-guidelines.md
  • packages/montage-mcp/data/markdown/color-usage.md
  • packages/montage-mcp/data/tokens.json
  • scripts/build_mcp_data.js
💤 Files with no reviewable changes (10)
  • Sources/Montage/1 Components/2 Actions/SolidUIButton.swift
  • Sources/Montage/1 Components/4 Contents/ContentBadgeUIView.swift
  • documentation/utilities/ios-extensions/swift.md
  • Sources/Montage/1 Components/9 Utilities/InteractionUIView.swift
  • documentation/components/contents/accordion/ios.md
  • Sources/Montage/1 Components/2 Actions/OutlinedUIButton.swift
  • documentation/components/loading/skeleton/ios.md
  • Sources/Blueprint/Sources/Scene/Previews/SkeletonPreview.swift
  • documentation/utilities/ios-utility-components/modalnavigation.md
  • Sources/Montage/1 Components/4 Contents/Accordion.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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/montage-mcp/data/tokens.json (1)

357-469: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

실제 토큰 식별자가 MCP 데이터에서 빠졌습니다.

이제 공개 API는 Spacing/Opacity의 enum case가 아니라 CGFloat.spacing16, CGFloat.opacity52, CGFloat.radius12, CGFloat.primitive24 같은 정적 프로퍼티인데, 여기서는 각 네임스페이스의 allValues/max/min만 직렬화하고 있습니다. 그 결과 MCP 소비자는 새 토큰 이름을 검색·자동완성·매핑할 수 없고, 기존 enum case 제거 이후에는 개별 토큰을 발견할 경로가 사라집니다. CGFloat 확장의 실제 토큰 프로퍼티들도 별도 섹션이나 identifiers로 함께 노출해야 합니다.

🤖 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 `@packages/montage-mcp/data/tokens.json` around lines 357 - 469, The MCP token
metadata is only serializing aggregate enum-style entries for Spacing, Opacity,
Radius, and Primitive, so the real public token identifiers exposed by the
CGFloat extension are missing. Update the token JSON generation in the relevant
token sections to include the actual CGFloat static properties (for example the
spacing/opacity/radius/primitive token names) alongside or instead of the
generic allValues/max/min identifiers, so MCP consumers can search,
autocomplete, and map the real token symbols. Use the existing Spacing, Opacity,
Radius, Dimension, Primitive, and Shadow entries as the place to attach the
missing identifiers.
🤖 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-utility-components/opacity.md`:
- Around line 14-23: The SwiftUI example uses a CGFloat opacity token directly,
which does not match the Double-based API expected by myView.opacity; update the
example in the opacity documentation so the token is wrapped/conveyed as a
Double-compatible value while keeping the CGFloat examples for alphaComponent
and CGFloat variables unchanged. Use the opacity.md SwiftUI snippet as the place
to adjust the myView.opacity usage and ensure the example reflects the correct
type for SwiftUI.

In `@documentation/utilities/ios-utility-components/typography.md`:
- Around line 223-226: The `uiTextStyle` description is incomplete and missing
the referenced source, so update the documentation text near `uiTextStyle` to
explicitly mention `textStyle` and complete the sentence. Keep the same meaning
as the existing `textStyle` explanation, but rewrite the `uiTextStyle` line so
the relationship is clear and the sentence reads naturally.

In `@packages/montage-mcp/data/components.json`:
- Around line 1033-1036: The `interactionColor(_:)` method summary in
`components.json` has a truncated default value, so update the `summary` for the
`interactionColor(Color.Semantic) -> IconButton` entry to match the documented
default from the `IconButton` docs. Use the existing `interactionColor(_:)`
method record as the target and replace the incomplete `(기본값: )` text with the
correct default value so MCP consumers see the full description.

In `@Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift`:
- Around line 162-165: `TextAreaPreview`에서 `leadingResources`에 `suffix(3)`를 직접
대입하는 부분이 타입 불일치로 컴파일되지 않습니다. `leadingResources`와 `trailingResources`가
`[TextArea.Resource]`이므로, `suffix(3)`의 `ArraySlice<TextArea.Resource>` 결과를
`Array(...)`로 감싸도록 `TextAreaPreview`의 리소스 수집 로직을 수정하세요.

In `@Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift`:
- Around line 254-256: The auto-complete toggle in TextFieldPreview only updates
usingSuggestions, so the existing suggestion list can remain visible after
turning it off. Update the toggle handling in the
HStack/toggleRow("autoComplete", $usingSuggestions) flow so that when
usingSuggestions becomes false, autoCompletionDataSource is immediately reset to
nil as well. Use the existing TextFieldPreview state properties to keep the
preview synchronized right away.

In `@Sources/Montage/1` Components/3 Selection And Input/TextArea.swift:
- Around line 37-45: DocC in TextArea.Size is describing fixed minimum heights
that do not match the actual implementation in resolvedMinHeight, so update the
enum documentation to reflect the real height behavior instead of implying 48/44
constants. Adjust the comments on Size.large and Size.medium to align with the
2-line-based min-height calculation used by TextArea so consumers understand the
true contract.
- Around line 645-657: The icon segment in TextArea.segment is only tappable via
onTapGesture, so VoiceOver does not expose it as an interactive control with
name or selected state. Refactor the segment view to use a Button-based control
for each item, and add appropriate accessibilityLabel plus
selected/accessibilityValue or hint so the segmented control in TextArea is
announced and operable by VoiceOver.
- Around line 335-353: The fixed resize path in TextArea’s resolvedMinHeight and
resolvedMaxHeight should normalize the `.fixed(min:max)` bounds before they are
used. Update the `resize` handling so that the `.fixed` case always returns
ordered min/max values from the `TextArea` layout helpers, preventing an invalid
`frame(minHeight:maxHeight:)` when the inputs are reversed. Ensure the
normalization is applied consistently wherever `resolvedMinHeight` and
`resolvedMaxHeight` are derived from `resize`.

In `@Sources/Montage/1` Components/3 Selection And Input/TextField.swift:
- Around line 564-566: TextField의 fieldTextColor가 항상 .semantic(.labelNormal)로
고정되어 있어 disable 상태에서도 본문 텍스트가 활성 색상으로 보입니다. fieldTextColor 로직을 수정해 TextArea와 같은
방식으로 disable 여부를 반영하도록 하고, 비활성일 때는 disabled 톤의 색상을 반환하도록 TextField의 색상 계산을
조정하세요.
- Around line 424-440: `backgroundColor(_:)`가 저장하는 `customBackgroundColor`가
`fieldBackground`에서 전혀 사용되지 않아 공개 API가 무시됩니다. `TextField`의 `fieldBackground`에서
`customBackgroundColor`를 우선 적용하도록 분기하고, 값이 없을 때만 기존 `disable`/`colorScheme` 기반
배경 로직을 사용하도록 수정하세요. `backgroundColor(_:)`, `customBackgroundColor`,
`fieldBackground`를 함께 점검해 리팩터링 전 동작을 복원하면 됩니다.

In `@Sources/Montage/1` Components/5 Loading/Skeleton.swift:
- Around line 221-226: `Skeleton`의 `GeometryReader` 안에서 자동 `lineNumber`를 계산할 때
`round(proxy.size.height / scaledLineHeight)` 때문에 애매한 높이에서 한 줄을 과하게 그려 하단이 잘릴 수
있습니다. `effectiveLineCount` 계산식을 수정해 실제로 들어갈 수 있는 줄 수만 반영하도록 내림 기반 계산으로 바꾸고,
`kind.lineNumber`가 0일 때만 이 자동 계산이 적용되도록 유지하세요.

In `@Sources/Montage/1` Components/6 Navigations/Category.swift:
- Line 124: `Category`의 우측 액션 버튼에서 `iconSize`를 그대로
`IconButton.NormalSize.custom(size:)`에 넘기고 있어 새 계약(컨테이너 한 변 길이)과 맞지 않습니다.
`Category` 내 해당 `IconButton(variant: .normal(size: ...), icon: icon)` 호출을 찾아
`iconSize`(20/22/24)를 직접 전달하지 말고, `NormalSize`의 기존 케이스로 매핑하거나 목표 아이콘 크기에 맞는 컨테이너
크기로 변환해서 넘기도록 수정하세요.

In `@Sources/Montage/1` Components/6 Navigations/Tab.swift:
- Line 175: `Tab`의 아이콘 크기 값을 `IconButton.NormalSize.custom(size:)`에 그대로 넘기면 의미가
바뀌어 탭 버튼이 작게 렌더링됩니다. `Tab`의 `size`에서 계산한 `iconSize`를 직접 쓰지 말고, `Tab.Size`를
`IconButton.NormalSize`로 매핑하는 전용 변환 로직을 추가해 컨테이너 크기 기준으로 전달되도록 수정하세요.
`IconButton(variant:icon:)` 호출부와 `iconButtonSize` 같은 보조 프로퍼티/메서드를 찾아 이 매핑을 적용하면
됩니다.

---

Outside diff comments:
In `@packages/montage-mcp/data/tokens.json`:
- Around line 357-469: The MCP token metadata is only serializing aggregate
enum-style entries for Spacing, Opacity, Radius, and Primitive, so the real
public token identifiers exposed by the CGFloat extension are missing. Update
the token JSON generation in the relevant token sections to include the actual
CGFloat static properties (for example the spacing/opacity/radius/primitive
token names) alongside or instead of the generic allValues/max/min identifiers,
so MCP consumers can search, autocomplete, and map the real token symbols. Use
the existing Spacing, Opacity, Radius, Dimension, Primitive, and Shadow entries
as the place to attach the missing identifiers.
🪄 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: debc207d-b199-4403-997d-e263cf6e8acf

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9ea73 and 97b16b3.

📒 Files selected for processing (62)
  • .gitignore
  • Sources/Blueprint/Blueprint.xcodeproj/project.pbxproj
  • Sources/Blueprint/Sources/Scene/ComponentList/ComponentListView.swift
  • Sources/Blueprint/Sources/Scene/Previews/ActionAreaPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/ButtonPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/IconButtonPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/SkeletonPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/TypographyPreview.swift
  • Sources/Montage/1 Components/2 Actions/Button.swift
  • Sources/Montage/1 Components/2 Actions/IconButton.swift
  • Sources/Montage/1 Components/2 Actions/OutlinedUIButton.swift
  • Sources/Montage/1 Components/2 Actions/SolidUIButton.swift
  • Sources/Montage/1 Components/3 Selection And Input/Control.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/4 Contents/Accordion.swift
  • Sources/Montage/1 Components/4 Contents/Card.swift
  • Sources/Montage/1 Components/4 Contents/ContentBadgeUIView.swift
  • Sources/Montage/1 Components/4 Contents/Thumbnail.swift
  • Sources/Montage/1 Components/5 Loading/Skeleton.swift
  • Sources/Montage/1 Components/6 Navigations/Category.swift
  • Sources/Montage/1 Components/6 Navigations/Tab.swift
  • Sources/Montage/1 Components/7 Feedback/SnackBar.swift
  • Sources/Montage/1 Components/8 Presentation/Popover.swift
  • Sources/Montage/1 Components/9 Utilities/Color.swift
  • Sources/Montage/1 Components/9 Utilities/Dimension.swift
  • Sources/Montage/1 Components/9 Utilities/InteractionUIView.swift
  • Sources/Montage/1 Components/9 Utilities/ModalNavigation.swift
  • Sources/Montage/1 Components/9 Utilities/Opacity.swift
  • Sources/Montage/1 Components/9 Utilities/Primitive.swift
  • Sources/Montage/1 Components/9 Utilities/Radius.swift
  • Sources/Montage/1 Components/9 Utilities/Spacing.swift
  • Sources/Montage/1 Components/9 Utilities/Typography.swift
  • Sources/Montage/2 Utilities/Extension/SwiftUI/SwiftUI+Debug.swift
  • Sources/Montage/2 Utilities/Extension/UIKit/NSAttributedString+.swift
  • Sources/Montage/Asset/Color.xcassets/Pink/pink100.colorset/Contents.json
  • Sources/Montage/Asset/Color.xcassets/Violet/violet0.colorset/Contents.json
  • Sources/Montage/Asset/Color.xcassets/Violet/violet100.colorset/Contents.json
  • documentation/components/actions/button/ios.md
  • documentation/components/actions/iconbutton/ios.md
  • documentation/components/contents/accordion/ios.md
  • documentation/components/loading/skeleton/ios.md
  • documentation/components/selection and input/textarea/ios.md
  • documentation/components/selection and input/textfield/ios.md
  • documentation/utilities/ios-extensions/corefoundation.md
  • documentation/utilities/ios-extensions/swift.md
  • documentation/utilities/ios-utility-components/color.md
  • documentation/utilities/ios-utility-components/dimension.md
  • documentation/utilities/ios-utility-components/modalnavigation.md
  • documentation/utilities/ios-utility-components/opacity.md
  • documentation/utilities/ios-utility-components/primitive.md
  • documentation/utilities/ios-utility-components/radius.md
  • documentation/utilities/ios-utility-components/spacing.md
  • documentation/utilities/ios-utility-components/typography.md
  • packages/montage-mcp/data/components.json
  • packages/montage-mcp/data/markdown/coding-guidelines.md
  • packages/montage-mcp/data/markdown/color-usage.md
  • packages/montage-mcp/data/tokens.json
  • scripts/build_mcp_data.js
💤 Files with no reviewable changes (10)
  • Sources/Montage/1 Components/2 Actions/SolidUIButton.swift
  • Sources/Montage/1 Components/4 Contents/ContentBadgeUIView.swift
  • documentation/utilities/ios-extensions/swift.md
  • Sources/Montage/1 Components/9 Utilities/InteractionUIView.swift
  • documentation/components/contents/accordion/ios.md
  • Sources/Montage/1 Components/2 Actions/OutlinedUIButton.swift
  • documentation/components/loading/skeleton/ios.md
  • Sources/Blueprint/Sources/Scene/Previews/SkeletonPreview.swift
  • documentation/utilities/ios-utility-components/modalnavigation.md
  • Sources/Montage/1 Components/4 Contents/Accordion.swift
🛑 Comments failed to post (13)
documentation/utilities/ios-utility-components/opacity.md (1)

14-23: 📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

git ls-files documentation/utilities/ios-utility-components/opacity.md documentation | sed -n '1,120p'

Repository: wanteddev/montage-ios

Length of output: 3501


🏁 Script executed:

rg -n "opacity88|opacity52|opacity43|withAlphaComponent|\.opacity\(" documentation -S

Repository: wanteddev/montage-ios

Length of output: 1263


🏁 Script executed:

rg -n "static let opacity(43|52|88)|extension CGFloat|typealias CGFloat|func opacity\\(_ opacity: Double\\)" . -S

Repository: wanteddev/montage-ios

Length of output: 1351


🏁 Script executed:

rg -n "static let opacity(43|52|88)|extension CGFloat|typealias CGFloat|func opacity\\(_ opacity: Double\\)" . -S

Repository: wanteddev/montage-ios

Length of output: 1351


🏁 Script executed:

sed -n '1,120p' 'Sources/Montage/1 Components/9 Utilities/Opacity.swift' && printf '\n---\n' && sed -n '1,80p' documentation/utilities/ios-utility-components/opacity.md

Repository: wanteddev/montage-ios

Length of output: 3365


🏁 Script executed:

sed -n '1,120p' 'Sources/Montage/1 Components/9 Utilities/Opacity.swift' && printf '\n---\n' && sed -n '1,80p' documentation/utilities/ios-utility-components/opacity.md

Repository: wanteddev/montage-ios

Length of output: 3365


🏁 Script executed:

sed -n '1,120p' 'Sources/Montage/1 Components/9 Utilities/Opacity.swift' && printf '\n---\n' && sed -n '1,80p' documentation/utilities/ios-utility-components/opacity.md

Repository: wanteddev/montage-ios

Length of output: 3365


🏁 Script executed:

sed -n '1,120p' 'Sources/Montage/1 Components/9 Utilities/Opacity.swift' && printf '\n---\n' && sed -n '1,80p' documentation/utilities/ios-utility-components/opacity.md

Repository: wanteddev/montage-ios

Length of output: 3365


SwiftUI 예제는 Double로 감싸주세요.
CGFloat.opacity{N} 토큰만 노출되므로 myView.opacity(.opacity88)는 그대로는 타입이 맞지 않습니다.

수정 예시
- myView.opacity(.opacity88)
+ myView.opacity(Double(.opacity88))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.


🤖 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 `@documentation/utilities/ios-utility-components/opacity.md` around lines 14 -
23, The SwiftUI example uses a CGFloat opacity token directly, which does not
match the Double-based API expected by myView.opacity; update the example in the
opacity documentation so the token is wrapped/conveyed as a Double-compatible
value while keeping the CGFloat examples for alphaComponent and CGFloat
variables unchanged. Use the opacity.md SwiftUI snippet as the place to adjust
the myView.opacity usage and ensure the example reflects the correct type for
SwiftUI.
documentation/utilities/ios-utility-components/typography.md (1)

223-226: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

uiTextStyle 설명 문장이 끊겨 있습니다.

"Dynamic Type 스케일의 기준이 되는 UIKit 텍스트 스타일. 과 동일한 논리..."는 참조 대상이 빠져 의미가 불분명합니다. textStyle을 명시해서 문장을 완성하는 편이 좋겠습니다.

🤖 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 `@documentation/utilities/ios-utility-components/typography.md` around lines
223 - 226, The `uiTextStyle` description is incomplete and missing the
referenced source, so update the documentation text near `uiTextStyle` to
explicitly mention `textStyle` and complete the sentence. Keep the same meaning
as the existing `textStyle` explanation, but rewrite the `uiTextStyle` line so
the relationship is clear and the sentence reads naturally.
packages/montage-mcp/data/components.json (1)

1033-1036: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

interactionColor(_:) 요약의 기본값이 잘려 있습니다.

여기 요약은 (기본값: ).로 저장돼 있어서 MCP 소비자 쪽 문서가 불완전하게 노출됩니다. 같은 PR의 documentation/components/actions/iconbutton/ios.md에는 기본값이 .labelNormal로 적혀 있으니 JSON도 동일하게 맞춰야 합니다.

수정 예시
         {
           "name": "interactionColor(_:)",
           "signature": "func interactionColor(Color.Semantic) -> IconButton",
-          "summary": "hover / press 시 인터랙션 영역에 사용할 색상을 설정합니다(기본값: ).",
+          "summary": "hover / press 시 인터랙션 영역에 사용할 색상을 설정합니다(기본값: `.labelNormal`).",
           "kind": "method"
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

          "name": "interactionColor(_:)",
          "signature": "func interactionColor(Color.Semantic) -> IconButton",
          "summary": "hover / press 시 인터랙션 영역에 사용할 색상을 설정합니다(기본값: `.labelNormal`).",
          "kind": "method"
🤖 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 `@packages/montage-mcp/data/components.json` around lines 1033 - 1036, The
`interactionColor(_:)` method summary in `components.json` has a truncated
default value, so update the `summary` for the `interactionColor(Color.Semantic)
-> IconButton` entry to match the documented default from the `IconButton` docs.
Use the existing `interactionColor(_:)` method record as the target and replace
the incomplete `(기본값: )` text with the correct default value so MCP consumers
see the full description.
Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift (1)

162-165: 🎯 Functional Correctness | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== TextAreaPreview.swift (relevant lines) ==\n'
nl -ba Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift | sed -n '130,210p'

printf '\n== Search for leadingResources / trailingResources declarations ==\n'
rg -n "leadingResources|trailingResources" Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift Sources/Blueprint/Sources -g '*.swift'

printf '\n== Search for TextArea.Resource definition ==\n'
rg -n "struct Resource|enum Resource|typealias Resource|protocol Resource" Sources/Blueprint/Sources -g '*.swift'

Repository: wanteddev/montage-ios

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== TextAreaPreview.swift (relevant lines) ==\n'
awk 'NR>=130 && NR<=210 { printf "%4d  %s\n", NR, $0 }' Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift

printf '\n== Search for leadingResources / trailingResources declarations ==\n'
rg -n "leadingResources|trailingResources" Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift Sources/Blueprint/Sources -g '*.swift' || true

printf '\n== Search for TextArea.Resource definition ==\n'
rg -n "struct Resource|enum Resource|typealias Resource|protocol Resource" Sources/Blueprint/Sources -g '*.swift' || true

Repository: wanteddev/montage-ios

Length of output: 6937


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== TextAreaPreview.swift (relevant lines) ==\n'
awk 'NR>=130 && NR<=210 { printf "%4d  %s\n", NR, $0 }' Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift

printf '\n== Search for leadingResources / trailingResources declarations ==\n'
rg -n "leadingResources|trailingResources" Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift Sources/Blueprint/Sources -g '*.swift' || true

printf '\n== Search for TextArea.Resource definition ==\n'
rg -n "struct Resource|enum Resource|typealias Resource|protocol Resource" Sources/Blueprint/Sources -g '*.swift' || true

Repository: wanteddev/montage-ios

Length of output: 6937


suffix(3) 결과를 Array로 감싸세요

leadingResourcestrailingResources[TextArea.Resource]인데, suffix(3)ArraySlice<TextArea.Resource>를 반환합니다. 현재 코드는 타입이 맞지 않아 컴파일되지 않습니다. Array(...)로 변환하세요.

수정 예시
-                                        leadingResources = leadingResources.suffix(3)
+                                        leadingResources = Array(leadingResources.suffix(3))
...
-                                    trailingResources = trailingResources.suffix(3)
+                                    trailingResources = Array(trailingResources.suffix(3))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                                    if let resource = resources[index] {
                                        leadingResources.append(resource)
                                        leadingResources = Array(leadingResources.suffix(3))
                                    }
🤖 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/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift` around lines
162 - 165, `TextAreaPreview`에서 `leadingResources`에 `suffix(3)`를 직접 대입하는 부분이 타입
불일치로 컴파일되지 않습니다. `leadingResources`와 `trailingResources`가
`[TextArea.Resource]`이므로, `suffix(3)`의 `ArraySlice<TextArea.Resource>` 결과를
`Array(...)`로 감싸도록 `TextAreaPreview`의 리소스 수집 로직을 수정하세요.
Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift (1)

254-256: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

자동완성 토글을 끌 때 기존 제안도 바로 지워야 합니다.

지금은 usingSuggestionsfalse가 되고 autoCompletionDataSource는 유지돼서, 이미 열린 제안 목록이 다음 입력 전까지 남을 수 있습니다. 토글 off 시 nil로 리셋해 프리뷰 상태를 즉시 맞춰 주세요.

수정 예시
         .transparentChecking(isPresented: showTransparentChecker, checkerSize: 201, checkerColor: .red)
+        .onChange(of: usingSuggestions) { enabled in
+            if !enabled {
+                autoCompletionDataSource = nil
+            }
+        }
🤖 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/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift` around lines
254 - 256, The auto-complete toggle in TextFieldPreview only updates
usingSuggestions, so the existing suggestion list can remain visible after
turning it off. Update the toggle handling in the
HStack/toggleRow("autoComplete", $usingSuggestions) flow so that when
usingSuggestions becomes false, autoCompletionDataSource is immediately reset to
nil as well. Use the existing TextFieldPreview state properties to keep the
preview synchronized right away.
Sources/Montage/1 Components/3 Selection And Input/TextArea.swift (3)

37-45: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

공개 DocC 설명의 최소 높이 수치가 구현과 다릅니다.

지금 기본 높이는 resolvedMinHeight에서 2줄 기준으로 계산되는데, Size.large/.medium 설명은 각각 48/44의 고정 최소 높이처럼 안내하고 있습니다. 이 상태로 DocC가 생성되면 소비자가 높이 계약을 잘못 이해하게 됩니다.

🤖 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/TextArea.swift around
lines 37 - 45, DocC in TextArea.Size is describing fixed minimum heights that do
not match the actual implementation in resolvedMinHeight, so update the enum
documentation to reflect the real height behavior instead of implying 48/44
constants. Adjust the comments on Size.large and Size.medium to align with the
2-line-based min-height calculation used by TextArea so consumers understand the
true contract.

335-353: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

.fixed(min:max) 범위를 정규화해야 합니다.

min > max가 들어오면 그대로 frame(minHeight:maxHeight:)에 전달되어 레이아웃이 비정상적으로 동작할 수 있습니다. 공개 API인 만큼 여기서 최소/최대값을 정렬해 두는 편이 안전합니다.

수정 예시
     private var resolvedMinHeight: CGFloat? {
         _ = dynamicTypeSize // Dynamic Type 변경 시 높이 재계산을 위한 의존성 등록
         switch resize {
         case .normal, .limit:
             return height(forRows: minRows)
-        case .fixed(let min, _):
-            return min
+        case .fixed(let min, let max):
+            return Swift.min(min, max)
         }
     }

     private var resolvedMaxHeight: CGFloat? {
         _ = dynamicTypeSize
         switch resize {
         case .normal: return .infinity
         case .limit: return height(forRows: maxRows)
-        case .fixed(_, let max): return max
+        case .fixed(let min, let max): return Swift.max(min, max)
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    private var resolvedMinHeight: CGFloat? {
        _ = dynamicTypeSize // Dynamic Type 변경 시 높이 재계산을 위한 의존성 등록
        switch resize {
        case .normal, .limit:
            return height(forRows: minRows)
        case .fixed(let min, let max):
            return Swift.min(min, max)
        }
    }

    /// resize 방식에 따른 최대 높이. `.limit`은 폰트 줄높이 기준 6줄로 계산되어 Dynamic Type 변경에도
    /// 항상 정확히 6줄을 유지한다.
    private var resolvedMaxHeight: CGFloat? {
        _ = dynamicTypeSize
        switch resize {
        case .normal: return .infinity
        case .limit: return height(forRows: maxRows)
        case .fixed(let min, let max): return Swift.max(min, max)
        }
    }
🤖 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/TextArea.swift around
lines 335 - 353, The fixed resize path in TextArea’s resolvedMinHeight and
resolvedMaxHeight should normalize the `.fixed(min:max)` bounds before they are
used. Update the `resize` handling so that the `.fixed` case always returns
ordered min/max values from the `TextArea` layout helpers, preventing an invalid
`frame(minHeight:maxHeight:)` when the inputs are reversed. Ensure the
normalization is applied consistently wherever `resolvedMinHeight` and
`resolvedMaxHeight` are derived from `resize`.

645-657: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

아이콘 세그먼트가 VoiceOver에서 조작 가능한 컨트롤로 노출되지 않습니다.

지금은 ImageonTapGesture만 붙어 있어서 버튼 역할, 접근 가능한 이름, 선택 상태가 전달되지 않습니다. Bottom segmented control은 아이콘만 보이기 때문에 Button 기반으로 바꾸고 각 세그먼트에 accessibilityLabel/선택 상태를 함께 제공해야 합니다. As per path instructions, VoiceOver 지원, 적절한 accessibilityLabel과 accessibilityHint 제공.

🤖 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/TextArea.swift around
lines 645 - 657, The icon segment in TextArea.segment is only tappable via
onTapGesture, so VoiceOver does not expose it as an interactive control with
name or selected state. Refactor the segment view to use a Button-based control
for each item, and add appropriate accessibilityLabel plus
selected/accessibilityValue or hint so the segmented control in TextArea is
announced and operable by VoiceOver.

Source: Path instructions

Sources/Montage/1 Components/3 Selection And Input/TextField.swift (2)

424-440: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

backgroundColor(_:)가 현재 완전히 무시됩니다.

backgroundColor(_:)customBackgroundColor를 저장하지만 fieldBackground에서 그 값을 전혀 읽지 않아, 공개 modifier가 더 이상 동작하지 않습니다. 이번 리팩터링으로 API 계약이 깨졌습니다.

수정 예시
     `@ViewBuilder`
     var fieldBackground: some View {
         Group {
-            if disable {
+            if disable {
                 SwiftUI.Color.semantic(.fillAlternative)
+            } else if let customBackgroundColor {
+                customBackgroundColor
             } else if colorScheme == .light {
                 SwiftUI.Color.atomic(.common100)
                     .opacity(0.8)
                     .background(.ultraThinMaterial)
             } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    `@ViewBuilder`
    var fieldBackground: some View {
        Group {
            if disable {
                SwiftUI.Color.semantic(.fillAlternative)
            } else if let customBackgroundColor {
                customBackgroundColor
            } else if colorScheme == .light {
                SwiftUI.Color.atomic(.common100)
                    .opacity(0.8)
                    .background(.ultraThinMaterial)
            } else {
                SwiftUI.Color.atomic(.coolNeutral17)
                    .opacity(0.61)
                    .background(.ultraThinMaterial)
            }
        }
        .clipShape(RoundedRectangle(cornerRadius: size.cornerRadius))
    }
🤖 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/TextField.swift around
lines 424 - 440, `backgroundColor(_:)`가 저장하는 `customBackgroundColor`가
`fieldBackground`에서 전혀 사용되지 않아 공개 API가 무시됩니다. `TextField`의 `fieldBackground`에서
`customBackgroundColor`를 우선 적용하도록 분기하고, 값이 없을 때만 기존 `disable`/`colorScheme` 기반
배경 로직을 사용하도록 수정하세요. `backgroundColor(_:)`, `customBackgroundColor`,
`fieldBackground`를 함께 점검해 리팩터링 전 동작을 복원하면 됩니다.

564-566: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

비활성 상태의 입력 텍스트가 활성 색상으로 남아 있습니다.

disable == true여도 본문 텍스트가 항상 .labelNormal이라서, 배경/placeholder/트레일링 버튼과 달리 비활성 상태가 시각적으로 전달되지 않습니다. TextArea와 같은 disabled 톤으로 맞추는 편이 자연스럽습니다.

🤖 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/TextField.swift around
lines 564 - 566, TextField의 fieldTextColor가 항상 .semantic(.labelNormal)로 고정되어 있어
disable 상태에서도 본문 텍스트가 활성 색상으로 보입니다. fieldTextColor 로직을 수정해 TextArea와 같은 방식으로
disable 여부를 반영하도록 하고, 비활성일 때는 disabled 톤의 색상을 반환하도록 TextField의 색상 계산을 조정하세요.
Sources/Montage/1 Components/5 Loading/Skeleton.swift (1)

221-226: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

자동 줄 수 계산에서 round는 마지막 라인을 잘리게 만들 수 있습니다.

Line 225는 높이가 1.5줄처럼 애매한 경우 반올림으로 한 줄을 더 그려서 하단 막대가 clipping 됩니다. 여기서는 실제로 들어가는 줄 수만 계산하도록 내림을 써야 합니다.

제안 수정
-                        let effectiveLineCount = kind.lineNumber > 0
-                            ? kind.lineNumber
-                            : max(1, Int(round(proxy.size.height / scaledLineHeight)))
+                        let effectiveLineCount = kind.lineNumber > 0
+                            ? kind.lineNumber
+                            : max(1, Int(floor(proxy.size.height / scaledLineHeight)))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                    GeometryReader { proxy in
                        let spacing = scaledLineSpacing
                        let effectiveLineCount = kind.lineNumber > 0
                            ? kind.lineNumber
                            : max(1, Int(floor(proxy.size.height / scaledLineHeight)))
                        let barHeight = max(0, scaledLineHeight - spacing)
🤖 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/5 Loading/Skeleton.swift around lines 221 -
226, `Skeleton`의 `GeometryReader` 안에서 자동 `lineNumber`를 계산할 때
`round(proxy.size.height / scaledLineHeight)` 때문에 애매한 높이에서 한 줄을 과하게 그려 하단이 잘릴 수
있습니다. `effectiveLineCount` 계산식을 수정해 실제로 들어갈 수 있는 줄 수만 반영하도록 내림 기반 계산으로 바꾸고,
`kind.lineNumber`가 0일 때만 이 자동 계산이 적용되도록 유지하세요.
Sources/Montage/1 Components/6 Navigations/Category.swift (1)

124-124: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

iconSize를 그대로 .custom(size:)에 넘기면 새 계약과 단위가 어긋납니다.

여기서의 iconSize는 기존 아이콘 크기 값(20/22/24)인데, 새 IconButton.NormalSize.custom(size:)는 컨테이너 한 변 길이를 받습니다. 그래서 20/22는 24pt로 클램프되어 아이콘이 더 작게 렌더링되고, 카테고리 우측 액션 버튼 크기가 의도보다 줄어듭니다. 이 값은 새 NormalSize 케이스로 매핑하거나, 목표 아이콘 크기에 맞는 컨테이너 크기로 변환해서 넘겨야 합니다.

🤖 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/6 Navigations/Category.swift at line 124,
`Category`의 우측 액션 버튼에서 `iconSize`를 그대로 `IconButton.NormalSize.custom(size:)`에
넘기고 있어 새 계약(컨테이너 한 변 길이)과 맞지 않습니다. `Category` 내 해당 `IconButton(variant:
.normal(size: ...), icon: icon)` 호출을 찾아 `iconSize`(20/22/24)를 직접 전달하지 말고,
`NormalSize`의 기존 케이스로 매핑하거나 목표 아이콘 크기에 맞는 컨테이너 크기로 변환해서 넘기도록 수정하세요.
Sources/Montage/1 Components/6 Navigations/Tab.swift (1)

175-175: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

iconSize.custom(size:)에 그대로 넘기면 단위가 바뀝니다.

Line 175에서 iconSize는 기존 의미대로 아이콘 크기(20/22/24)인데, IconButton.NormalSize.custom(size:)는 컨테이너 한 변 길이를 받습니다. 지금처럼 넘기면 20/22/24가 24pt로 클램프되고 아이콘도 재계산돼서, 탭의 small/medium/large가 거의 같은 작은 버튼으로 렌더링됩니다. Tab.SizeNormalSize로 직접 매핑하거나, 컨테이너 크기로 변환한 값을 넘겨야 합니다.

예시 수정안
-                        if resize == .hug, let icon, let iconButtonAction {
-                            IconButton(variant: .normal(size: .custom(size: iconSize)), icon: icon) {
+                        if resize == .hug, let icon, let iconButtonAction {
+                            IconButton(variant: .normal(size: iconButtonSize), icon: icon) {
                                 iconButtonAction()
                             }
                             .padding(.trailing, horizontalPadding ? 16 : 0)
                         }
private var iconButtonSize: IconButton.NormalSize {
    switch size {
    case .small: .large   // 20pt icon
    case .medium: .custom(size: 33) // convert desired icon size to container size
    case .large: .xlarge  // 24pt icon
    }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                            IconButton(variant: .normal(size: iconButtonSize), icon: icon) {
                                iconButtonAction()
                            }
                            .padding(.trailing, horizontalPadding ? 16 : 0)
                        }

    private var iconButtonSize: IconButton.NormalSize {
        switch size {
        case .small: .large
        case .medium: .custom(size: 33)
        case .large: .xlarge
        }
    }
🤖 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/6 Navigations/Tab.swift at line 175, `Tab`의 아이콘
크기 값을 `IconButton.NormalSize.custom(size:)`에 그대로 넘기면 의미가 바뀌어 탭 버튼이 작게 렌더링됩니다.
`Tab`의 `size`에서 계산한 `iconSize`를 직접 쓰지 말고, `Tab.Size`를 `IconButton.NormalSize`로
매핑하는 전용 변환 로직을 추가해 컨테이너 크기 기준으로 전달되도록 수정하세요. `IconButton(variant:icon:)` 호출부와
`iconButtonSize` 같은 보조 프로퍼티/메서드를 찾아 이 매핑을 적용하면 됩니다.

@knine79

knine79 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai PR 타겟 브랜치를 잘못 설정해서 수정했어. 처음부터 다시 리뷰해줘

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

@knine79 타겟 브랜치 수정 확인했습니다! 수정된 타겟 브랜치를 기준으로 처음부터 다시 전체 리뷰를 진행하겠습니다.

✅ Action performed

Full review finished.

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/Montage/1 Components/3 Selection And Input/TextField.swift (1)

372-382: 🎯 Functional Correctness | 🟠 Major

접근성 이름과 상태 값을 분리하세요.
TextField("", ...)는 필드 목적을 명확히 읽을 라벨이 없고, .accessibilityValue(accessibilityStatusDescription)가 입력값 대신 "오류" 또는 빈 문자열을 덮어씁니다. 필드 목적은 accessibilityLabel로 제공하고, 상태 설명은 accessibilityHint로 분리하세요.

🤖 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/TextField.swift around
lines 372 - 382, The TextField accessibility setup currently hides the field
purpose and overwrites the value with status text; update the SwiftUI.TextField
in TextField.swift so its purpose is exposed via accessibilityLabel and the
status message from accessibilityStatusDescription is moved to accessibilityHint
instead of accessibilityValue. Keep the actual text value available to assistive
technologies, and use the existing TextField initializer and modifiers near
focused(_:) and accessibilityValue(...) to make the change cleanly.

Source: Path instructions

🤖 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 `@Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift`:
- Around line 184-188: `TextFieldPreview`의 `onChange(of: text)`만으로는
`usingSuggestions` 토글 시 `autoCompletionDataSource`가 동기화되지 않습니다.
`usingSuggestions`(또는 `autoComplete`) 상태가 변경될 때도 현재 텍스트 상태를 기준으로 자동완성 데이터를 즉시
갱신하거나 nil로 정리하도록 별도의 상태 변경 처리 로직을 추가하세요. `text`를 다루는 기존 갱신 경로와 동일한 규칙을 재사용해, 켜질
때는 즉시 제안이 준비되고 꺼질 때는 기존 `autoCompletionDataSource`가 남지 않게 맞춰주세요.
- Around line 141-146: The toggleRow view currently renders Text(title) next to
Switch, but the switch has no explicit accessibility label, so VoiceOver may not
announce the option name. Update the toggleRow(_:_: ) helper in TextFieldPreview
to attach an appropriate accessibilityLabel to the Switch using the title value,
and add a suitable accessibilityHint if needed so the control is properly
described for VoiceOver support.

In `@Sources/Montage/1` Components/3 Selection And Input/TextArea.swift:
- Around line 124-133: 아이콘 전용 세그먼트에 VoiceOver용 접근성 정보를 추가해야 합니다. `TextArea`의
`segmentedControl` API와 이를 렌더링하는 `BottomSegmentedControl`에서 세그먼트별
`accessibilityLabel`을 받을 수 있는 모델을 도입하고, 각 아이콘에 해당 label을 연결하세요. 또한 `Image` +
`onTapGesture`만 쓰는 현재 구현을 수정해 세그먼트에 button trait와 적절한 `accessibilityHint`를 부여해
조작 가능함을 명확히 표시하세요.
- Around line 217-219: `TextArea.maxLength(_:)` should normalize negative limits
at the entry point so `updateUIView` never receives an invalid value that can
crash via `String.prefix(_:)`. Update the `maxLength(_:)` method to clamp any
non-nil `limit` to zero or greater before storing it in `self.maxLength`, and
keep the fix localized to the `TextArea` configuration path.

---

Outside diff comments:
In `@Sources/Montage/1` Components/3 Selection And Input/TextField.swift:
- Around line 372-382: The TextField accessibility setup currently hides the
field purpose and overwrites the value with status text; update the
SwiftUI.TextField in TextField.swift so its purpose is exposed via
accessibilityLabel and the status message from accessibilityStatusDescription is
moved to accessibilityHint instead of accessibilityValue. Keep the actual text
value available to assistive technologies, and use the existing TextField
initializer and modifiers near focused(_:) and accessibilityValue(...) to make
the change cleanly.
🪄 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: 804ff47f-056c-4cf0-babb-dad146f767fa

📥 Commits

Reviewing files that changed from the base of the PR and between 9322aa7 and 97b16b3.

📒 Files selected for processing (7)
  • Sources/Blueprint/Sources/Scene/Previews/TextAreaPreview.swift
  • Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextArea.swift
  • Sources/Montage/1 Components/3 Selection And Input/TextField.swift
  • documentation/components/selection and input/textarea/ios.md
  • documentation/components/selection and input/textfield/ios.md
  • packages/montage-mcp/data/components.json

Comment thread Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift
Comment thread Sources/Blueprint/Sources/Scene/Previews/TextFieldPreview.swift Outdated
Comment thread Sources/Montage/1 Components/3 Selection And Input/TextArea.swift
Comment thread Sources/Montage/1 Components/3 Selection And Input/TextArea.swift Outdated
- TextArea.maxLength: 음수 입력을 0 이상으로 정규화해 String.prefix 트랩 방지
- TextArea 세그먼트 컨트롤: accessibilityLabels 추가 + 버튼/선택 trait 부여
- TextField: 실제 텍스트 노출 유지, 상태 문구를 value 대신 hint로 이동, placeholder를 label로
- Blueprint 프리뷰: Switch 접근성 라벨 추가, autoComplete 토글 시 자동완성 동기화

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

knine79 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

CodeRabbit 리뷰 코멘트 반영 완료 (f94afa7)

  • TextArea maxLength: 음수 입력을 0 이상으로 정규화 → String.prefix(_:) 런타임 트랩 방지
  • TextArea 세그먼트 컨트롤: accessibilityLabels 파라미터 추가 + 세그먼트별 라벨/.isButton/.isSelected trait 부여
  • TextField 접근성 (outside-diff): accessibilityValue로 실제 텍스트를 덮어쓰던 부분 제거 → placeholder를 accessibilityLabel(용도), 상태 문구를 accessibilityHint로 이동, 텍스트 값은 보조 기술에 그대로 노출
  • Blueprint 프리뷰: Switch accessibilityLabel 추가, autoComplete 토글 시 자동완성 동기화

Montage·Blueprint 빌드 통과, make 문서 재생성 완료.

@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
@github-actions github-actions Bot added in review This issue requires a review. and removed accepted This issue has been reviewed. labels Jun 29, 2026
resize 변경 시 높이 갱신용으로 text에 공백을 주입하던 꼼수가
placeholder 표시 조건(text.isEmpty)을 깨뜨려 Limit에서 placeholder가
사라졌다. text를 건드리는 대신 .id(resize)로 뷰를 재생성해 높이를
재계산하도록 변경한다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KWRQ1qMUwA73goLx5eHnhP
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