docs(chat): 다중행 입력 요구사항과 검증을 기록한다

This commit is contained in:
Yu Sung
2026-10-19 11:20:06 +09:00
parent 14b415e163
commit a89cf9f120
60 changed files with 2312 additions and 0 deletions
@@ -0,0 +1,23 @@
# 다중행 입력 native 증거
기준 소스 commit은 `b0cabf9`다. before/after의 입력창·입력/전송 가드·sendText 블록은 실제 `UserCreatorChatRoomView.swift`에서 그대로 추출했다. provenance.json의 SHA-256 및 추출본으로 확인한다. 주변 VStack, 연결 상태, 전송 sink, 일부 컴파일용 문자열은 임시 하네스이며 제품 코드에 추가하지 않았다.
- 환경: iPhone SE3, iOS18.3.1, 375pt 폭 / 750×1334px PNG.
- before/after 각각22장, 총44장. 시스템 권한 팝업이나 실제 사용자 데이터 없이 `simctl io screenshot`으로 촬영했다.
- after/matrix.json: 실제 native 편집기 bounds/contentSize/contentOffset, binding/native 문자열, 전송 sink 상태. before/matrix.json은 기존 단일행 기준이다.
- verification.json: 동작·소스22개 검사와 PNG형식·크기44개 검사, 합계66개다. 비즈니스 테스트66개라는 뜻이 아니다.
- cleanup.json: 작업 소유 시뮬레이터 삭제와 하네스 프로세스 종료 영수증.
| 입력 줄 | before 높이 | after 높이 | after 내용 높이 |
|---|---:|---:|---:|
| 1 | 18.5 | 17 | 17 |
| 2 | 18.5 | 34 | 34 |
| 3 | 18.5 | 50 | 51 |
| 4 | 18.5 | 50 | 67 |
| 8 | 18.5 | 50 | 134 |
8줄 입력의 내부 offset은0→84pt로 이동하며 전체 문자열은 보존된다. Return은 native UIKeyInput으로 삽입했고, 전송 확인은 원본 sendText를 합성 sink에 직접 호출했다. 삭제·비우기·전송 후17pt로 축소된다.
**미검증:** 실제 터치 스크롤, 소프트웨어 키보드·한글 IME, 클립보드 붙여넣기, 전송 버튼 탭, 비활성 상태의 GUI 입력 차단, 서버 저장·재조회, 전체 채팅방의 키보드/목록 연동. 상태 주입을 실제 붙여넣기라고 주장하지 않으며 native isEditable 값만으로 GUI 차단을 판정하지 않는다.
독립 코드 검토 APPROVE, 최종22장 시각 검토 PASS. 캡처는 입력창 하네스 범위만 증명한다. 자세한 결과는 [리뷰 리포트](../phase1-multiline-input.md)를 따른다.
@@ -0,0 +1,51 @@
private var inputBarView: some View {
HStack(alignment: .bottom, spacing: SodaSpacing.s8) {
ZStack(alignment: .leading) {
if messageText.isEmpty {
Text(I18n.UserCreatorChat.messagePlaceholder)
.appFont(size: 14, weight: .regular)
.foregroundColor(Color.gray500)
}
TextField("", text: $messageText, axis: .vertical)
.appFont(size: 14, weight: .regular)
.foregroundColor(.white)
.accentColor(Color.soda400)
.lineLimit(1...3)
.disabled(isInputEnabled == false)
}
.padding(.horizontal, SodaSpacing.s16)
.padding(.vertical, SodaSpacing.s12)
.background(Color.gray900)
.cornerRadius(20)
Button(action: sendText) {
Image("ic_message_send")
.resizable()
.frame(width: 24, height: 24)
.opacity(isSendEnabled ? 1 : 0.4)
}
.buttonStyle(.plain)
.disabled(isSendEnabled == false)
}
.padding(.horizontal, SodaSpacing.s12)
.padding(.vertical, SodaSpacing.s12)
.frame(width: screenSize().width)
.background(Color.black)
}
private var isInputEnabled: Bool {
viewModel.socketState == .joined
}
private var isSendEnabled: Bool {
isInputEnabled && messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
}
private func sendText() {
let trimmedText = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
guard isInputEnabled, trimmedText.isEmpty == false else { return }
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
viewModel.sendText(trimmedText)
messageText = ""
}
@@ -0,0 +1,3 @@
# 변경 후
원본 inputBarView에 승인된 최소 수정이 적용된 캡처·측정값이다. 각 프레임의 상태는 matrix.json의 id와 대응한다. overflow-top/bottom은 동일 문자열의 native 편집기 내부 스크롤 전후다.
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

@@ -0,0 +1,852 @@
[
{
"action": "set",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-empty",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "1번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-1-lines",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 34,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 34,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 34,
"width": 287,
"x": 28,
"y": 609
},
"id": "after-2-lines",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 51,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-3-lines",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 67,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-4-lines",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 134,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-8-lines",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "top",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 134,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-overflow-top",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "bottom",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"contentOffset": {
"x": 0,
"y": 84
},
"contentSize": {
"height": 134,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-overflow-bottom",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. ",
"contentOffset": {
"x": 0,
"y": 34
},
"contentSize": {
"height": 301,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. ",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-wrapping",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-clear",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "첫째 줄",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "첫째 줄",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-return-seed",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "insert",
"bounds": {
"height": 34,
"width": 287
},
"boundText": "첫째 줄\n",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 34,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "첫째 줄\n",
"firstResponder": true,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 34,
"width": 287,
"x": 28,
"y": 609
},
"id": "after-return",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "insert",
"bounds": {
"height": 34,
"width": 287
},
"boundText": "첫째 줄\n둘째 줄",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 34,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "첫째 줄\n둘째 줄",
"firstResponder": true,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 34,
"width": 287,
"x": 28,
"y": 609
},
"id": "after-return-followup",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "deleteAll",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "",
"firstResponder": true,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-delete-all",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": " 첫째 줄\n둘째 줄\n셋째 줄 ",
"contentOffset": {
"x": 0,
"y": 1
},
"contentSize": {
"height": 51,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": " 첫째 줄\n둘째 줄\n셋째 줄 ",
"firstResponder": true,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-send-seed",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [],
"textContainerInset": [
0,
0
]
},
{
"action": "send",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-send",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄\n둘째 줄\n셋째 줄"
],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": " \n \n ",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 51,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": " \n \n ",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-whitespace",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄\n둘째 줄\n셋째 줄"
],
"textContainerInset": [
0,
0
]
},
{
"action": "send",
"bounds": {
"height": 50,
"width": 287
},
"boundText": " \n \n ",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 51,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": " \n \n ",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-whitespace-send",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄\n둘째 줄\n셋째 줄"
],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "연결 전 입력",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "연결 전 입력",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-disabled",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄\n둘째 줄\n셋째 줄"
],
"textContainerInset": [
0,
0
]
},
{
"action": "send",
"bounds": {
"height": 17,
"width": 287
},
"boundText": "연결 전 입력",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 17,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "연결 전 입력",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 17,
"width": 287,
"x": 28,
"y": 626
},
"id": "after-disabled-send",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄\n둘째 줄\n셋째 줄"
],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "첫째 줄 끝글자\n둘째 줄 끝글자\n셋째 줄 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 51,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "첫째 줄 끝글자\n둘째 줄 끝글자\n셋째 줄 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-light-3-lines",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [
"첫째 줄\n둘째 줄\n셋째 줄"
],
"textContainerInset": [
0,
0
]
},
{
"action": "set",
"bounds": {
"height": 50,
"width": 287
},
"boundText": "첫째 줄 끝글자\n둘째 줄 끝글자\n셋째 줄 끝글자",
"contentOffset": {
"x": 0,
"y": 0
},
"contentSize": {
"height": 51,
"width": 287
},
"editorClass": "VerticalTextView",
"editorText": "첫째 줄 끝글자\n둘째 줄 끝글자\n셋째 줄 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 50,
"width": 287,
"x": 28,
"y": 593
},
"id": "after-dark-3-lines",
"inputEnabled": true,
"pid": 65240,
"scrollEnabled": true,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [
"첫째 줄\n둘째 줄\n셋째 줄"
],
"textContainerInset": [
0,
0
]
}
]
@@ -0,0 +1,25 @@
{
"source": "/Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Main/Chat/UserCreatorChat/UserCreatorChatRoomView.swift",
"sha256": "ff5e753384a56969cf7137d73f9a6b2ddb8909e5ef94106009849a1414d64dcd",
"extractedSha256": "4dc54374643fb83bc71cb3a4b6033af717a476851fdf9f7b083b237c4ae1c79b",
"verbatimBlocks": [
"inputBarView",
"isInputEnabled",
"isSendEnabled",
"sendText"
],
"synthetic": [
"Sink viewModel/socketState",
"I18n Korean placeholder constant from source",
"LanguageOption enum shim",
"screenSize method copied from source",
"outer harness VStack/control bridge"
],
"unchangedHelpers": [
"FontModifier.swift",
"Font.swift",
"Color.swift",
"ColorExtension.swift",
"Spacing.swift"
]
}
@@ -0,0 +1,51 @@
private var inputBarView: some View {
HStack(spacing: SodaSpacing.s8) {
ZStack(alignment: .leading) {
if messageText.isEmpty {
Text(I18n.UserCreatorChat.messagePlaceholder)
.appFont(size: 14, weight: .regular)
.foregroundColor(Color.gray500)
}
TextField("", text: $messageText)
.appFont(size: 14, weight: .regular)
.foregroundColor(.white)
.accentColor(Color.soda400)
.disabled(isInputEnabled == false)
.onSubmit { sendText() }
}
.padding(.horizontal, SodaSpacing.s16)
.padding(.vertical, SodaSpacing.s12)
.background(Color.gray900)
.cornerRadius(999)
Button(action: sendText) {
Image("ic_message_send")
.resizable()
.frame(width: 24, height: 24)
.opacity(isSendEnabled ? 1 : 0.4)
}
.buttonStyle(.plain)
.disabled(isSendEnabled == false)
}
.padding(.horizontal, SodaSpacing.s12)
.padding(.vertical, SodaSpacing.s12)
.frame(width: screenSize().width)
.background(Color.black)
}
private var isInputEnabled: Bool {
viewModel.socketState == .joined
}
private var isSendEnabled: Bool {
isInputEnabled && messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
}
private func sendText() {
let trimmedText = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
guard isInputEnabled, trimmedText.isEmpty == false else { return }
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
viewModel.sendText(trimmedText)
messageText = ""
}
@@ -0,0 +1,3 @@
# 변경 전
원본 단일 행 inputBarView의 기준 캡처·측정값이다. Return이 전송으로 연결되고 여러 줄 내용도 한 줄 높이에 표시되는 상태를 기록한다.
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -0,0 +1,581 @@
[
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "",
"editorClass": "UITextField",
"editorText": "",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-empty",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자",
"editorClass": "UITextField",
"editorText": "1번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-1-lines",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자",
"editorClass": "UITextField",
"editorText": "1번째 메시지 내용 끝글자 2번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-2-lines",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자",
"editorClass": "UITextField",
"editorText": "1번째 메시지 내용 끝글자 2번째 메시지 내용 끝글자 3번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-3-lines",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자",
"editorClass": "UITextField",
"editorText": "1번째 메시지 내용 끝글자 2번째 메시지 내용 끝글자 3번째 메시지 내용 끝글자 4번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-4-lines",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"editorClass": "UITextField",
"editorText": "1번째 메시지 내용 끝글자 2번째 메시지 내용 끝글자 3번째 메시지 내용 끝글자 4번째 메시지 내용 끝글자 5번째 메시지 내용 끝글자 6번째 메시지 내용 끝글자 7번째 메시지 내용 끝글자 8번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-8-lines",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "top",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"editorClass": "UITextField",
"editorText": "1번째 메시지 내용 끝글자 2번째 메시지 내용 끝글자 3번째 메시지 내용 끝글자 4번째 메시지 내용 끝글자 5번째 메시지 내용 끝글자 6번째 메시지 내용 끝글자 7번째 메시지 내용 끝글자 8번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-overflow-top",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "bottom",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "1번째 메시지 내용 끝글자\n2번째 메시지 내용 끝글자\n3번째 메시지 내용 끝글자\n4번째 메시지 내용 끝글자\n5번째 메시지 내용 끝글자\n6번째 메시지 내용 끝글자\n7번째 메시지 내용 끝글자\n8번째 메시지 내용 끝글자",
"editorClass": "UITextField",
"editorText": "1번째 메시지 내용 끝글자 2번째 메시지 내용 끝글자 3번째 메시지 내용 끝글자 4번째 메시지 내용 끝글자 5번째 메시지 내용 끝글자 6번째 메시지 내용 끝글자 7번째 메시지 내용 끝글자 8번째 메시지 내용 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-overflow-bottom",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. ",
"editorClass": "UITextField",
"editorText": "안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. 안녕하세요 긴 메시지를 작성하면서 앞뒤 내용을 확인합니다. 마지막 글자까지 모두 보존되어야 합니다. ",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-wrapping",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "",
"editorClass": "UITextField",
"editorText": "",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-clear",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": []
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "첫째 줄",
"editorClass": "UITextField",
"editorText": "첫째 줄",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-return-seed",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": []
},
{
"action": "insert",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "",
"editorClass": "UITextField",
"editorText": "",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-return",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄"
]
},
{
"action": "insert",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "둘째 줄",
"editorClass": "UITextField",
"editorText": "둘째 줄",
"firstResponder": true,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-return-followup",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [
"첫째 줄"
]
},
{
"action": "deleteAll",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "",
"editorClass": "UITextField",
"editorText": "",
"firstResponder": true,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-delete-all",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄"
]
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": " 첫째 줄\n둘째 줄\n셋째 줄 ",
"editorClass": "UITextField",
"editorText": " 첫째 줄 둘째 줄 셋째 줄 ",
"firstResponder": true,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-send-seed",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [
"첫째 줄"
]
},
{
"action": "send",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "",
"editorClass": "UITextField",
"editorText": "",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-send",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄",
"첫째 줄\n둘째 줄\n셋째 줄"
]
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": " \n \n ",
"editorClass": "UITextField",
"editorText": " ",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-whitespace",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄",
"첫째 줄\n둘째 줄\n셋째 줄"
]
},
{
"action": "send",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": " \n \n ",
"editorClass": "UITextField",
"editorText": " ",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-whitespace-send",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄",
"첫째 줄\n둘째 줄\n셋째 줄"
]
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "연결 전 입력",
"editorClass": "UITextField",
"editorText": "연결 전 입력",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-disabled",
"inputEnabled": false,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄",
"첫째 줄\n둘째 줄\n셋째 줄"
]
},
{
"action": "send",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "연결 전 입력",
"editorClass": "UITextField",
"editorText": "연결 전 입력",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-disabled-send",
"inputEnabled": false,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": false,
"sent": [
"첫째 줄",
"첫째 줄\n둘째 줄\n셋째 줄"
]
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "첫째 줄 끝글자\n둘째 줄 끝글자\n셋째 줄 끝글자",
"editorClass": "UITextField",
"editorText": "첫째 줄 끝글자 둘째 줄 끝글자 셋째 줄 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-light-3-lines",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [
"첫째 줄",
"첫째 줄\n둘째 줄\n셋째 줄"
]
},
{
"action": "set",
"bounds": {
"height": 18.5,
"width": 287
},
"boundText": "첫째 줄 끝글자\n둘째 줄 끝글자\n셋째 줄 끝글자",
"editorClass": "UITextField",
"editorText": "첫째 줄 끝글자 둘째 줄 끝글자 셋째 줄 끝글자",
"firstResponder": false,
"font": "Pretendard-Regular",
"fontLineHeight": 16.70703125,
"frame": {
"height": 18.5,
"width": 287,
"x": 28,
"y": 624.5
},
"id": "before-dark-3-lines",
"inputEnabled": true,
"pid": 64504,
"sendAssetLoaded": true,
"sendEnabled": true,
"sent": [
"첫째 줄",
"첫째 줄\n둘째 줄\n셋째 줄"
]
}
]
@@ -0,0 +1,25 @@
{
"source": "/Users/klaus/Develop/sodalive/iOS/SodaLive/SodaLive/Sources/V2/Main/Chat/UserCreatorChat/UserCreatorChatRoomView.swift",
"sha256": "6f2f84963cc18fe3afeedf5100f5d9838630f3ce95109f1bb6c9f33613d7bb16",
"extractedSha256": "a668c26729345cbd78cd702f71cfaa1c43dc3c27757f6215c034dfa605236e36",
"verbatimBlocks": [
"inputBarView",
"isInputEnabled",
"isSendEnabled",
"sendText"
],
"synthetic": [
"Sink viewModel/socketState",
"I18n Korean placeholder constant from source",
"LanguageOption enum shim",
"screenSize method copied from source",
"outer harness VStack/control bridge"
],
"unchangedHelpers": [
"FontModifier.swift",
"Font.swift",
"Color.swift",
"ColorExtension.swift",
"Spacing.swift"
]
}
@@ -0,0 +1,13 @@
{
"deviceUDID": "7A3A7C1D-CA68-4E0F-AFD6-73EFD891A17E",
"ownedDeviceSet": "/private/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/dm-input.MBW85o/native.vyi84m/devices",
"deleted": true,
"appTerminatedAfterEachMatrix": true,
"harnessPIDs": [
64504,
65240
],
"livePIDs": [],
"parentRetained": true,
"evidenceRetained": true
}
@@ -0,0 +1,191 @@
{
"command": "image-diff",
"dimensionsMatch": true,
"reference": {
"width": 750,
"height": 1334
},
"actual": {
"width": 750,
"height": 1334
},
"totalPixels": 1000500,
"diffPixels": 54474,
"diffRatio": 0.0544,
"similarityScore": 95,
"alphaChannelIntact": true,
"hotspots": [
{
"gridX": 1,
"gridY": 7,
"x": 93,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4813
},
{
"gridX": 2,
"gridY": 7,
"x": 187,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4715
},
{
"gridX": 3,
"gridY": 7,
"x": 281,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4358
},
{
"gridX": 4,
"gridY": 7,
"x": 375,
"y": 1167,
"width": 93,
"height": 167,
"diffRatio": 0.4012
},
{
"gridX": 5,
"gridY": 7,
"x": 468,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4006
},
{
"gridX": 6,
"gridY": 7,
"x": 562,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.3889
},
{
"gridX": 0,
"gridY": 7,
"x": 0,
"y": 1167,
"width": 93,
"height": 167,
"diffRatio": 0.3408
},
{
"gridX": 4,
"gridY": 0,
"x": 375,
"y": 0,
"width": 93,
"height": 166,
"diffRatio": 0.1187
},
{
"gridX": 3,
"gridY": 0,
"x": 281,
"y": 0,
"width": 94,
"height": 166,
"diffRatio": 0.1144
},
{
"gridX": 7,
"gridY": 7,
"x": 656,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.0926
},
{
"gridX": 1,
"gridY": 6,
"x": 93,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 2,
"gridY": 6,
"x": 187,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 3,
"gridY": 6,
"x": 281,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 4,
"gridY": 6,
"x": 375,
"y": 1000,
"width": 93,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 5,
"gridY": 6,
"x": 468,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 6,
"gridY": 6,
"x": 562,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0282
},
{
"gridX": 0,
"gridY": 6,
"x": 0,
"y": 1000,
"width": 93,
"height": 167,
"diffRatio": 0.0171
},
{
"gridX": 2,
"gridY": 0,
"x": 187,
"y": 0,
"width": 94,
"height": 166,
"diffRatio": 0.0136
},
{
"gridX": 5,
"gridY": 0,
"x": 468,
"y": 0,
"width": 94,
"height": 166,
"diffRatio": 0.0109
}
],
"summary": "95/100 similarity; 54474/1000500 pixels differ; 19 hotspot region(s)."
}
@@ -0,0 +1,191 @@
{
"command": "image-diff",
"dimensionsMatch": true,
"reference": {
"width": 750,
"height": 1334
},
"actual": {
"width": 750,
"height": 1334
},
"totalPixels": 1000500,
"diffPixels": 55051,
"diffRatio": 0.055,
"similarityScore": 94,
"alphaChannelIntact": true,
"hotspots": [
{
"gridX": 2,
"gridY": 7,
"x": 187,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4905
},
{
"gridX": 1,
"gridY": 7,
"x": 93,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4821
},
{
"gridX": 3,
"gridY": 7,
"x": 281,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4521
},
{
"gridX": 4,
"gridY": 7,
"x": 375,
"y": 1167,
"width": 93,
"height": 167,
"diffRatio": 0.4012
},
{
"gridX": 5,
"gridY": 7,
"x": 468,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.4006
},
{
"gridX": 6,
"gridY": 7,
"x": 562,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.3889
},
{
"gridX": 0,
"gridY": 7,
"x": 0,
"y": 1167,
"width": 93,
"height": 167,
"diffRatio": 0.3459
},
{
"gridX": 4,
"gridY": 0,
"x": 375,
"y": 0,
"width": 93,
"height": 166,
"diffRatio": 0.1162
},
{
"gridX": 3,
"gridY": 0,
"x": 281,
"y": 0,
"width": 94,
"height": 166,
"diffRatio": 0.1128
},
{
"gridX": 7,
"gridY": 7,
"x": 656,
"y": 1167,
"width": 94,
"height": 167,
"diffRatio": 0.0926
},
{
"gridX": 1,
"gridY": 6,
"x": 93,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 2,
"gridY": 6,
"x": 187,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 3,
"gridY": 6,
"x": 281,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 4,
"gridY": 6,
"x": 375,
"y": 1000,
"width": 93,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 5,
"gridY": 6,
"x": 468,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0359
},
{
"gridX": 6,
"gridY": 6,
"x": 562,
"y": 1000,
"width": 94,
"height": 167,
"diffRatio": 0.0282
},
{
"gridX": 0,
"gridY": 6,
"x": 0,
"y": 1000,
"width": 93,
"height": 167,
"diffRatio": 0.0171
},
{
"gridX": 2,
"gridY": 0,
"x": 187,
"y": 0,
"width": 94,
"height": 166,
"diffRatio": 0.0136
},
{
"gridX": 5,
"gridY": 0,
"x": 468,
"y": 0,
"width": 94,
"height": 166,
"diffRatio": 0.0109
}
],
"summary": "94/100 similarity; 55051/1000500 pixels differ; 19 hotspot region(s)."
}
@@ -0,0 +1,81 @@
{
"checksPassed": 66,
"checks": [
"baseline 1/2/3/4/8 lines stay single-height",
"baseline Return submits",
"native 1/2/3-line growth and 4/8 cap",
"1 lines preserved in native control and binding",
"2 lines preserved in native control and binding",
"3 lines preserved in native control and binding",
"4 lines preserved in native control and binding",
"8 lines preserved in native control and binding",
"overflow scrolls internally",
"overflow scroll preserves all text",
"long wrapping text retained",
"native Return inserts newline without submitting",
"native continued typing preserves newline",
"clear shrinks to one line",
"delete-all shrinks to one line",
"send shrinks to one line",
"existing send action trims edges and preserves internal newlines",
"whitespace send guard unchanged",
"disconnected send guard unchanged",
"original registered font and send asset loaded",
"final native evidence is from current product source",
"send guards and action byte-for-byte unchanged",
"before-overflow-top.png is correctly sized native PNG",
"before-delete-all.png is correctly sized native PNG",
"before-return-seed.png is correctly sized native PNG",
"before-return.png is correctly sized native PNG",
"before-whitespace-send.png is correctly sized native PNG",
"before-1-lines.png is correctly sized native PNG",
"before-dark-3-lines.png is correctly sized native PNG",
"before-send-seed.png is correctly sized native PNG",
"before-whitespace.png is correctly sized native PNG",
"before-2-lines.png is correctly sized native PNG",
"before-return-followup.png is correctly sized native PNG",
"before-overflow-bottom.png is correctly sized native PNG",
"before-4-lines.png is correctly sized native PNG",
"before-empty.png is correctly sized native PNG",
"before-clear.png is correctly sized native PNG",
"before-3-lines.png is correctly sized native PNG",
"before-light-3-lines.png is correctly sized native PNG",
"before-send.png is correctly sized native PNG",
"before-disabled.png is correctly sized native PNG",
"before-disabled-send.png is correctly sized native PNG",
"before-wrapping.png is correctly sized native PNG",
"before-8-lines.png is correctly sized native PNG",
"after-empty.png is correctly sized native PNG",
"after-clear.png is correctly sized native PNG",
"after-4-lines.png is correctly sized native PNG",
"after-send-seed.png is correctly sized native PNG",
"after-delete-all.png is correctly sized native PNG",
"after-return.png is correctly sized native PNG",
"after-2-lines.png is correctly sized native PNG",
"after-8-lines.png is correctly sized native PNG",
"after-overflow-bottom.png is correctly sized native PNG",
"after-return-followup.png is correctly sized native PNG",
"after-send.png is correctly sized native PNG",
"after-3-lines.png is correctly sized native PNG",
"after-whitespace.png is correctly sized native PNG",
"after-dark-3-lines.png is correctly sized native PNG",
"after-whitespace-send.png is correctly sized native PNG",
"after-disabled-send.png is correctly sized native PNG",
"after-return-seed.png is correctly sized native PNG",
"after-1-lines.png is correctly sized native PNG",
"after-wrapping.png is correctly sized native PNG",
"after-overflow-top.png is correctly sized native PNG",
"after-disabled.png is correctly sized native PNG",
"after-light-3-lines.png is correctly sized native PNG"
],
"screenshots": 44,
"limitations": [
"Programmatic native interaction, not human touch/IME",
"Send closure directly invokes unchanged sendText action with synthetic sink; no network or button tap",
"State seeding stands in for multiline paste",
"SwiftUI disabled UITextView still reports isEditable=true; this does not prove GUI hit-testing. Disabled modifier unchanged; actual disabled user interaction not exercised.",
"System software keyboard not displayed; no keyboard/message-list integration tested",
"375pt SE3 width only, light/dark style samples; no full-device matrix",
"No LSP tool exposed; no full app xcodebuild; parent handles full build and independent review"
]
}
@@ -0,0 +1,76 @@
# Phase 1 다중행 입력 검증·리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 기준 | main, 시작 b0cabf9 / 구현 14b415e |
| 요구사항 | [PRD](../prd.md), INPUT-001~005 |
| 계획 | [plan-task.md](../plan-task.md), P1-T1/P1-GATE |
| 상태 | 코드·하네스 시각 검토 승인 / 실제 DM 통합 QA 미실행 |
## 2. 범위와 판정 기준
유저-크리에이터 DM의 inputBarView만 검토한다. 단일→최대3줄 확장, 초과 내용 내부 스크롤, Return 줄바꿈, 기존 버튼 전송·빈 입력 가드·비연결 입력 가드 보존이 기준이다. 네트워크·서버·날짜 목록·다른 입력창은 변경 범위 밖이다.
## 3. 검증 근거
- 기존 커뮤니티 입력창이 vertical TextField + lineLimit1...3을 사용한다.
- 메시지 입력 UI의 기존 XCTest seam은 없다. 새 source-string 검사를 만들어 UI 기능 테스트처럼 주장하지 않고 native 전후 동작을 확인한다.
- 현재 노출 도구에 LSP diagnostics가 없어 native compiler/build로 검증하고, LSP 실행 여부와 구분한다.
| 검사 | 결과 |
|---|---|
| 운영/개발 타깃 최종 빌드 | 모두 exit0, 전후 RoomView SHA-256 일치 |
| 기존 검사12개 스크립트/지원 모드19회 | 모두 exit0, 날짜113 assertions 포함 |
| 네이티브 입력 UI 전후 비교 | 동작·소스22검사 + PNG44검사 통과, 부모 재실행 exit0 |
| 독립 코드·시각 리뷰 | 코드 APPROVE, 최종22장 시각 PASS, 필수 수정 없음 |
## 4. 발견 사항과 외부 조건
확정 발견 사항 없음. 실제 로그인 DM 및 서버의 줄바꿈 저장·재조회는 승인된 계정·roomId 부재로 검증하지 않았다. 하네스의 합성 전송 sink는 실제 서버 전송을 증명하지 않는다.
## 5. 종료 판정과 후속 기록
최종 코드·실행 증거·리뷰 결과를 이 문서에 누적하며, 확정 수정이 필요하면 계획에 신규 Task를 등록한 뒤 수정한다. 임시 native 앱·시뮬레이터·컴파일 캐시는 작업 소유 자원만 제거한다.
### 확인한 동작과 증거
| 입력 줄 수 | 기준 높이 | 변경 후 높이 | 변경 후 contentSize |
|---|---:|---:|---:|
| 1 | 18.5pt | 17pt | 17pt |
| 2 | 18.5pt | 34pt | 34pt |
| 3 | 18.5pt | 50pt | 51pt |
| 4 | 18.5pt | 50pt | 67pt |
| 8 | 18.5pt | 50pt | 134pt |
- 8줄 입력의 native offset0→84pt 이동으로 첫1~3줄과 마지막6~8줄을 확인했고 전체 binding/native 문자열은 같았다. 긴 한글 wrapping은 content301pt,표시50pt로 유지됐다.
- native UIKeyInput의 Return 삽입: 기준은 자동 전송·초기화, 변경 후는 `첫째 줄\n`이며 전송 없음. 후속 삽입 결과는 `첫째 줄\n둘째 줄`로 보존됐다.
- native 전체 삭제와 상태 비우기, 원본 sendText 직접 호출 뒤 한 줄17pt로 축소됐다. 원본 전송 handler는 앞뒤 공백 제거·중간 줄바꿈 보존·합성sink 수신·입력 초기화·포커스 해제를 유지했다.
- 입력창·isInputEnabled·isSendEnabled·sendText를 제품 소스에서 그대로 추출했다. 기존 폰트·색상·간격·send asset을 사용했다. 주변화면·연결상태·일부 문자열·전송sink는 임시 하네스다. [provenance와 캡처](evidence/README.md)를 따른다.
- `verification.json`의66 checks는 동작·소스22개와44 PNG형식/크기 확인의 합계다. 비즈니스 테스트66개 또는 실제 서버66시나리오로 해석하지 않는다.
### 실행 명령과 결과
- `sh work/tests/check-user-creator-chat-date-presentation.sh`:113 assertions, exit0.
- work/tests/check-*.sh 12개 기본 실행 + received-gift-detail의 foundation/recipient-form/received-states + gift-phase11의 profile/policy/button + gift-address-search의 baseline, 총19회 모두 exit0.
- `xcodebuild -quiet -workspace SodaLive.xcworkspace -scheme SodaLive -configuration Debug -destination 'generic/platform=iOS' CODE_SIGNING_ALLOWED=NO build`:최종 exit0. SodaLive-dev 동일 명령도 exit0. 기존 프로젝트 관리 DerivedData를 재사용했으며 삭제하지 않는다.
- `plutil -lint SodaLive.xcodeproj/project.pbxproj`:OK. 제품 파일 Swift parse 및 `git diff --check`도 exit0. LSP 도구는 없어 미실행이다.
- 임시 native하네스의 swiftc·actool·설치·실행·simctl screenshot은 모두 exit0. 전후22장씩44개 PNG가750×1334px이며, 최종 소스와 after스냅샷의 일치를 확인했다. 부모도 verify.py를 실행해 native측정·본문·provenance·이미지형식 검사를 재확인했다.
### 독립 리뷰 판정
- Oracle 코드/기능 검토: APPROVE. 사용자 요구범위의4줄 추가·4줄 삭제이며 차단 결함 없음. 원본 코드 추출과 native측정 경로를 검토했다.
- 시각 검토: 최종after22장과 기준3장을 직접 열어 PASS. 3줄의 끝글자·첫/마지막줄 잘림 없음, 내부 스크롤 전후 표시 일치, 둥근 모서리와 전송버튼 겹침 없음, 팝업 가림 없음. 50pt높이/51ptcontent 차이만으로 결함을 주장하지 않고 실제 글리프를 확인했다.
- 실제 터치·IME·클립보드·전송버튼탭·비연결 GUI입력차단·소프트웨어키보드·전체대화목록·서버·VoiceOver·다른기기/OS는 미검증이다. 이번 승인을 해당 범위로 확대하지 않는다. 추가 제품 코드 수정 요구는 없다.
### 정리 기록
- native검증자가 소유 simulator `7A3A7C1D-CA68-4E0F-AFD6-73EFD891A17E`를 shutdown/delete하고 하네스 PID64504/65240 종료를 확인했다. [cleanup.json](evidence/cleanup.json)에 영수증이 있다.
- 부모도 custom device set의 빈 기기 목록과 DMInputHarness 프로세스 부재를 확인했다. 증거를 보존한 뒤 작업 소유 `dm-input.MBW85o` 루트(하네스·스크립트·캐시·로그)를 제거하고 경로 부재를 확인했다. 작업 메모도 삭제했다. 공유 simulator·프로젝트 관리 DerivedData는 제거하지 않았다.
### 픽셀 비교 보조 증거
- visual-qa의 `image-diff`를 같은 단계의 before/after22쌍에 실행해 모두 exit0으로 JSON을 생성했다. 내용/높이를 의도적으로 바꾸는 작업이므로 픽셀 동일성을 통과 조건으로 사용하지 않는다.
- [3줄 비교](evidence/image-diff-three-lines.json): 크기 일치, alpha 유지, diffRatio0.055 / similarity94. 주요 변화는 하단 입력창이 세 줄로 확장된 영역이다.
- [overflow 하단 비교](evidence/image-diff-overflow.json): 크기 일치, alpha 유지, diffRatio0.0544 / similarity95. 기존 한 줄 표시 대신 마지막6~8줄이 보이는 하단 입력 영역이 변경됐다. 하네스 제목·상태바 차이도 포함된 참고 수치이며 기능 판정은 native측정과 독립 시각 검토를 따른다.