Skip to content

Feature/#30 feat timetable name limit - #45

Merged
lluke0 merged 5 commits into
developfrom
feature/#30-feat-timetable-name-limit
Aug 30, 2025
Merged

Feature/#30 feat timetable name limit#45
lluke0 merged 5 commits into
developfrom
feature/#30-feat-timetable-name-limit

Conversation

@BEEEAM-J

@BEEEAM-J BEEEAM-J commented Aug 25, 2025

Copy link
Copy Markdown
Member

📌 PR 요약

🌱 작업한 내용

  • 시간표 이름 20자 제한 기능 추가
  • CchAppBarWithTitle "뒤로 가기" 버튼 영역 수정

🌱 PR 포인트

  • 시간표 이름 20자 제한 기능 -> 시간표 이름 입력 화면, 시간표 수정 화면 적용

📸 스크린샷

스크린샷
1
2

📮 관련 이슈

RCA 룰을 사용하여 코드 리뷰를 해주세요

R (Request Changes) : 적극적으로 반영을 고려해주세요
C (Comment) : 웬만하면 반영해주세요
A (Approve) : 반영해도 좋고, 넘어가도 좋습니다. 사소한 의견입니다.

Summary by CodeRabbit

  • 신기능

    • 시간표 이름이 20자를 초과하면 텍스트 필드에 에러 상태와 안내 문구를 표시
    • 텍스트 필드가 에러 상태 표시(isError)를 지원
  • 버그 수정

    • 뒤로가기 버튼의 터치 동작 일관성 개선
    • 시간표 이름 입력/편집 화면의 버튼 활성화 로직이 검증 규칙을 따르도록 수정
    • 시간표 목록의 빈 상태 문구와 배치(패딩) 조정으로 가시성 향상

- `Image`에 `cchClickable` Modifier를 적용하여 클릭 영역을 명확히 함
- 시간표 리스트가 비어있을 때 표시되는 문구를 "등록된 시간표가 없어요!"로 변경
- 문구의 상단 패딩 값을 150.dp에서 324.dp로 변경
- 시간표 이름이 20자를 초과하는 경우 에러 상태로 표시
- 텍스트 필드에 `isError` 파라미터 추가하여 에러 상태 시 테두리 색상 변경
- 에러 메시지를 표시하여 사용자에게 입력 제한을 알림
- 시간표 이름이 20자를 초과하는 경우 에러 상태로 표시
- 텍스트 필드에 `isError` 파라미터 추가하여 에러 상태 시 테두리 색상 변경
- 에러 메시지를 표시하여 사용자에게 입력 제한을 알림
@BEEEAM-J BEEEAM-J self-assigned this Aug 25, 2025
@BEEEAM-J BEEEAM-J linked an issue Aug 25, 2025 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Aug 25, 2025

Copy link
Copy Markdown

Walkthrough

시간표 이름 입력/편집에 최대 20자 검증을 추가하고 오류 상태를 텍스트필드(isError)와 에러 메시지로 표시하도록 변경. CchRegularTextField 시그니처에 isError 추가. 앱바 뒤로가기 클릭을 Image로 이동. 시간표 리스트 빈 상태의 패딩과 문구 조정.

Changes

Cohort / File(s) Summary
TextField 오류 상태 추가
composeApp/.../designsystem/component/textfield/CchRegularTextField.kt
isError: Boolean 파라미터 추가 및 활성(border) 색상에 오류(Red300) 반영.
이름 길이 검증 및 UI 반영
composeApp/.../timetablenameinput/TimetableNameInputContract.kt, composeApp/.../timetablenameinput/TimetableNameInputScreen.kt, composeApp/.../timetableeditor/TimetableEditorContract.kt, composeApp/.../timetableeditor/TimetableEditorScreen.kt, composeApp/.../common/extension/TimetableNameLimit.kt
최대 길이(20자) 상수와 검사 함수 추가(TIMETABLE_NAME_LIMIT, checkOverTimetableNameLimit, checkTimetableNameRule). 버튼 활성화 로직을 규칙 기반으로 변경하고, 화면에서 isError 전달 및 길이 초과 시 에러 메시지 표시.
앱바 뒤로가기 클릭 처리 변경
composeApp/.../designsystem/component/appbar/CchAppBarWithTitle.kt
Row의 clickable 제거, 뒤로가기 Image에 cchClickable(onClick = onClickBackButton) 적용 및 CircleShape로 클립.
시간표 리스트 빈 상태 UI 조정
composeApp/.../timetablelist/TimetableListScreen.kt
빈 상태 패딩 150.dp → 324.dp로 증가, 문자열 리소스 → 하드코딩 "등록된 시간표가 없어요!"로 변경.

Sequence Diagram(s)

sequenceDiagram
  actor User as 사용자
  participant Screen as TimetableName Screen
  participant TF as CchRegularTextField
  participant Ext as TimetableNameLimit (utils)
  participant VM as Contract/State

  User->>TF: 이름 입력
  TF->>VM: onValueChanged(name)
  VM->>Ext: checkTimetableNameRule(name)
  Ext-->>VM: valid:Boolean
  VM-->>Screen: uiState(name, buttonEnabled)
  Screen->>TF: isError = checkOverTimetableNameLimit(name)
  alt name.length > 20
    Screen-->>User: 에러 텍스트 표시 (Red300)
    Screen-->>User: 저장 버튼 비활성
  else
    Screen-->>User: 에러 숨김, 버튼 상태 반영
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
시간표 이름 최대 20자 제한 적용 (입력/설정 화면) [#30]

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
빈 상태 문구 하드코딩 및 패딩 변경 (composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt) 이슈 #30은 이름 길이 제한만 명시되어 있으며 리스트 빈 상태 UI 변경은 요구 사항에 포함되지 않음.
앱바 뒤로가기 클릭 처리 이동 및 이미지 클리핑 (composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt) 네비게이션 클릭 위치 및 시각적 클립 변경은 이슈 #30의 범위(이름 길이 제한)와 관련 없음.

Possibly related PRs

Suggested reviewers

  • jinukeu

Poem

토끼가 말하네, 글자 셈은 내 일! 🥕
스무 자에 딱 멈추고, 넘치면 빨갛게 반짝.
뒤로가기 동그랗게 톡, 화면은 깔끔히 춤추네.
코드밭에서 깡충깡충, 버그는 당근으로 퇴치!

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#30-feat-timetable-name-limit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@BEEEAM-J
BEEEAM-J requested a review from lluke0 August 25, 2025 13:30

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

Caution

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

⚠️ Outside diff range comments (7)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt (1)

65-71: 추가 버튼도 동일한 접근성/일관성 적용 필요

Add 아이콘도 클릭 가능 요소이므로 동일하게 48dp 터치 영역과 의미 있는 contentDescription을 제공하는 것이 바람직합니다. 디자인 시스템 관점에서도 좌/우 액션의 인터랙션 영역을 일치시키는 것이 좋습니다.

적용 제안(diff):

       Icon(
         modifier = Modifier
-          .clip(CircleShape)
+          .size(48.dp)
+          .clip(CircleShape)
           .align(Alignment.CenterEnd)
-          .cchClickable(onClick = onClickAdd),
+          .cchClickable(onClick = onClickAdd)
+          .padding(12.dp),
         painter = painterResource(Res.drawable.ic_timetable_add),
-        contentDescription = "",
+        contentDescription = stringResource(Res.string.appbar_add_content_description),
         tint = Black100,
       )

추가:

  • 문자열 리소스 예시: appbar_add_content_description = "시간표 추가"
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt (1)

104-113: 고정 패딩(324.dp) 제거 및 빈 상태 화면 중앙 정렬 적용
현재 TimetableListScreen.kt 107행의 Modifier.padding(top = 324.dp)는 디바이스 해상도, 글꼴 크기, 시스템 인셋 변화에 취약한 매직 넘버입니다. 빈 상태 UI는 Box를 활용해 가로·세로 중앙에 배치하는 방식이 반응형·접근성 측면에서 더 안전합니다.

검토 위치

  • 파일: composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt
  • 대략 104~113행

제안된 리팩터링(diff):

-            if (uiState.timetableList.isEmpty()) {
-                Text(
-                    modifier = Modifier
-                        .padding(top = 324.dp),
-                    textAlign = TextAlign.Center,
-                    text = "등록된 시간표가 없어요!",
-                    style = CchTheme.typography.bodyMd,
-                    color = Gray600,
-                )
-            }
+            if (uiState.timetableList.isEmpty()) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .padding(horizontal = 20.dp)
+                ) {
+                    Text(
+                        modifier = Modifier.align(Alignment.Center),
+                        text = stringResource(Res.string.timetable_list_screen_empty_timetable),
+                        textAlign = TextAlign.Center,
+                        style = CchTheme.typography.bodyMd,
+                        color = Gray600,
+                    )
+                }
+            } else {
                 LazyColumn(
                     modifier = Modifier.padding(vertical = 8.dp, horizontal = 20.dp),
                     verticalArrangement = Arrangement.spacedBy(12.dp),
                 ) {
                     /* ... */
                 }
             }

추가 import:

import androidx.compose.foundation.layout.Box
import androidx.compose.ui.Alignment

이 변경은 이번 PR의 핵심(시간표 이름 20자 제한)과 직접 관련이 없으므로, 별도 커밋/PR로 분리해 적용하는 것을 권장합니다.

composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt (3)

83-86: 컴파일 오류: 널이 아닌 semester에 안전 호출(?.) 사용

TimetableNameInputState.semester는 널이 아닌 타입입니다. 안전 호출은 컴파일되지 않습니다. 아래처럼 안전 호출을 제거하세요.

-              title = "${uiState.semester?.year}년 ${uiState.semester?.semester}학기",
+              title = "${uiState.semester.year}년 ${uiState.semester.semester}학기",

103-103: 196.dp 상단 패딩은 매직 넘버 — 디자인 토큰 또는 레이아웃로 대체 권장

고정 196.dp는 기기/폰트 스케일에 따라 레이아웃 왜곡을 유발합니다. 디자인 시스템의 spacing 토큰, dimension 리소스, 또는 Spacer(Modifier.weight(...))/Arrangement.spacedBy 등으로 대체를 권장합니다.

예시:

-                    modifier = Modifier.padding(top = 196.dp, start = 4.dp, end = 4.dp),
+                    modifier = Modifier.padding(top = 16.dp, start = 4.dp, end = 4.dp),

102-111: 문자열·상수 하드코딩 제거 및 입력 제한 강제화 필요

아래 위치들에서 “시간표 이름은 최대 20자까지 설정 가능합니다.” 문자열과 숫자 20이 하드코딩되어 있습니다. 리소스화 및 상수화하여 한 곳에서 관리하고, 입력 단계에서 20자 초과 입력을 강제 컷팅하도록 수정하세요.

• 문자열 하드코딩

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt:117
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt:118

• 길이 비교식 하드코딩

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt:12 (name.length <= 20)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt:107, 112 (uiState.name.length > 20)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt:108, 113 (uiState.name.length > 20)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt:16 (name.length <= 20)

제안된 수정 예시

-    isError = uiState.name.length > 20,
+    isError = uiState.name.length > TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH,
-    text = "시간표 이름은 최대 20자까지 설정 가능합니다.",
+    text = stringResource(Res.string.timetable_name_error_max_20),

ViewModel(예: TimetableNameInputViewModel.updateName)에서 입력을 강제 컷팅하도록 변경:

fun updateName(input: String) {
    val trimmed = input.trim()
    val clipped = trimmed.take(TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH)
    mviStore.update { it.copy(name = clipped) }
}
  • Res.string.timetable_name_error_max_20 등의 리소스를 추가하고, TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH 상수를 정의하여 중복을 제거하세요.
  • 입력 뷰단에서는 isError 표시만 남기고, 실제 상태 업데이트는 ViewModel에서 강제 컷팅된 값을 사용하도록 일원화해 UX 일관성을 확보해야 합니다.
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt (1)

73-75: CchRegularTextField의 클리어 버튼 주석 처리 복구 또는 파라미터 제거 필요
현재 onClickClearButton 콜백을 전달하는 화면에서도 버튼 UI가 주석 처리되어 노출되지 않습니다. 사용자 혼란을 방지하려면 다음 중 하나를 적용해주세요.

• 수정 위치

  • 파일: composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
  • decorationBox 내부, 현재 주석 처리된 if (isActive) { TextFieldClearButton(...) } 블록

• 개선안

  1. 버튼 복구
  • // if (isActive) {
  • // TextFieldClearButton(onClick = onClickClearButton)
  • // }
  •    if (isActive) {
    
  •      TextFieldClearButton(onClick = onClickClearButton)
    
  •    }
    
    - 활성화 상태(`isActive == true`)일 때만 클리어 버튼을 표시  
    - 기존 `onClickClearButton` 콜백 활용  
    2. 파라미터 제거  
    - `isActive`와 `onClickClearButton` 파라미터 삭제  
    - 호출부(예: `TimetableNameInputScreen`, `TimetableEditorScreen`)에서 관련 인자 제거  
    
    

위 변경 후, 클리어 버튼 동작 및 레이아웃을 한 번 더 확인해주세요.

composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt (1)

103-111: isActive 기준이 ‘값 변경 여부’로 되어 있어 다른 화면과 불일치 — 입력 유무 기준으로 통일 권장

NameInputScreen은 isActive = name.isNotEmpty()인데, 본 화면은 preName과의 비교로 활성 스타일이 달라집니다. 활성 스타일은 “값 존재 여부” 기준으로 일관되게 가져가는 것을 권장합니다.

적용 예시(diff):

-                    isActive = uiState.name.isNotEmpty() && uiState.name != uiState.preName,
+                    isActive = uiState.name.isNotEmpty(),
♻️ Duplicate comments (1)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt (1)

113-123: 에러 문구 하드코딩/중복 — 리소스화 및 상수 사용으로 통일(Cf. NameInputScreen 동일 코멘트)

본 코멘트는 NameInputScreen 동일 사안의 중복입니다. 문자열 리소스로 이동하고, 비교식은 공유 상수를 사용하세요.

적용 예시(diff):

-                    text = "시간표 이름은 최대 20자까지 설정 가능합니다.",
+                    text = stringResource(Res.string.timetable_name_error_max_20),
-                    isError = uiState.name.length > 20,
+                    isError = uiState.name.length > TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e6011bd and afc993a.

📒 Files selected for processing (7)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt (1 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt (2 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt (1 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt (3 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt (1 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt (1 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.kt: Use the Kotlin official code style
Use immutable state with data classes

Files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/**/*.kt: When creating new UI components, use CCHaksaTheme for new features
Prefer typography styles over hardcoded text styles

Files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

Organize UI components and themes in the designsystem directory

Files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
🧠 Learnings (6)
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/**/*.kt : Preview components in the `preview/designsystem/` package

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/**/*.kt : Organize UI components and themes in the designsystem directory

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/**/*.kt : Prefer typography styles over hardcoded text styles

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/**/*.kt : When creating new UI components, use `CCHaksaTheme` for new features

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/**/*.kt : Organize previews for UI components in the preview/designsystem directory

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/theme/**/*.kt : Follow existing color naming conventions (100-600 scale) when creating new colors

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt (1)

31-39: isError 파라미터 추가는 API 확장으로 타 호출처에 안전 — 기본값 제공 OK

기본값이 있어 기존 호출처 호환성 문제가 없고, 에러 연동도 명확합니다. 👍

Comment on lines 44 to 47
modifier = Modifier
.wrapContentWidth()
.height(24.dp)
.clickable { onClickBackButton() },
.height(24.dp),
verticalAlignment = Alignment.CenterVertically,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

뒤로가기 영역 높이 24dp → 최소 48dp로 상향 필요

Row 높이가 24dp로 고정되어 있어 탭 타겟이 권장 최소(48dp)에 미달합니다. 클릭 처리를 Image로 이동한 만큼, 컨테이너(Row)도 최소 48dp 이상을 보장해야 접근성/사용성 저하가 없습니다.

적용 제안(diff):

-        .height(24.dp),
+        .heightIn(min = 48.dp),

추가: heightIn 사용을 위해 import androidx.compose.foundation.layout.heightIn가 필요합니다.

🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt
around lines 44–47, the Row for the back area is fixed to height(24.dp) which
yields a tap target below the recommended minimum; replace the fixed height with
heightIn(min = 48.dp) to guarantee at least 48dp touch target and update the
modifier accordingly, and add the import statement import
androidx.compose.foundation.layout.heightIn.

Comment on lines +51 to +53
modifier = Modifier
.clip(CircleShape)
.cchClickable(onClick = onClickBackButton),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

클릭 영역이 아이콘 크기에 한정됨 — 48dp 터치 영역을 명시적으로 부여하세요

클릭이 Image에만 걸리면서 실제 터치 영역이 아이콘(추정 24dp) 크기로 축소되었습니다. 최소 48dp의 터치 영역을 보장하도록 size(48.dp)와 내부 padding(12.dp)을 적용해 주세요. 이렇게 하면 시각적 아이콘은 24dp로 유지되면서 터치 영역은 48dp가 됩니다.

적용 제안(diff):

-        modifier = Modifier
-          .clip(CircleShape)
-          .cchClickable(onClick = onClickBackButton),
+        modifier = Modifier
+          .size(48.dp)
+          .clip(CircleShape)
+          .cchClickable(onClick = onClickBackButton)
+          .padding(12.dp),
📝 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.

Suggested change
modifier = Modifier
.clip(CircleShape)
.cchClickable(onClick = onClickBackButton),
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.cchClickable(onClick = onClickBackButton)
.padding(12.dp),
🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt
around lines 51-53, the clickable modifier is only applied to the Image
(reducing touch target to ~24dp); update the modifier chain to guarantee a 48.dp
touch target by adding size(48.dp) and padding(12.dp) so the visible icon stays
24.dp while the touch area is 48.dp, and ensure cchClickable is applied to that
48.dp area (e.g., apply size(48.dp) then cchClickable(onClick =
onClickBackButton) then padding(12.dp)/clip as needed).

.clip(CircleShape)
.cchClickable(onClick = onClickBackButton),
painter = painterResource(resource = Res.drawable.ic_appbar_arrow_left_chukchuk),
contentDescription = "",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

접근성: 비어있는 contentDescription을 의미 있는 문자열로 교체하세요

조작 가능한 뒤로가기 버튼인데 contentDescription = ""로 되어 있습니다. 스크린리더 사용자를 위해 의미 있는 문자열을 제공해야 합니다.

적용 제안(diff):

-        contentDescription = "",
+        contentDescription = stringResource(Res.string.appbar_navigate_up_content_description),

추가:

  • import org.jetbrains.compose.resources.stringResource
  • 문자열 리소스 예시: appbar_navigate_up_content_description = "뒤로가기" (플랫폼별 현지화 가능)
📝 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.

Suggested change
contentDescription = "",
contentDescription = stringResource(Res.string.appbar_navigate_up_content_description),
🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/appbar/CchAppBarWithTitle.kt
around line 55, the IconButton's contentDescription is an empty string which
breaks accessibility; replace it with a localized stringResource (e.g.
stringResource(R.string.appbar_navigate_up_content_description)) and add the
import org.jetbrains.compose.resources.stringResource, and add a string resource
key (appbar_navigate_up_content_description = "뒤로가기") to the shared resources so
screen readers get a meaningful label.

Comment on lines 40 to 44
val (borderColor, textColor, textStyle) = if(!isActive) {
Triple(Gray200, Gray400, CchTheme.typography.bodyLg)
} else {
Triple(Purple600, Black100, CchTheme.typography.bodyLgStrong)
Triple(if (isError) Red300 else Purple600, Black100, CchTheme.typography.bodyLgStrong)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 상태가 비활성(isActive=false)일 때 빨간 보더가 표시되지 않음 — 에러 우선 로직으로 정정

현재는 isActive가 false면 에러여도 회색 보더가 보여 사용자 피드백이 약합니다. 에러일 때는 활성/비활성 무관하게 Red300이 보이도록 조건식을 정리하세요.

적용 예시(diff):

-  val (borderColor, textColor, textStyle) = if(!isActive) {
-    Triple(Gray200, Gray400, CchTheme.typography.bodyLg)
-  } else {
-    Triple(if (isError) Red300 else Purple600, Black100, CchTheme.typography.bodyLgStrong)
-  }
+  val borderColor = when {
+    isError -> Red300
+    isActive -> Purple600
+    else -> Gray200
+  }
+  val textColor = if (isActive) Black100 else Gray400
+  val textStyle = if (isActive) CchTheme.typography.bodyLgStrong else CchTheme.typography.bodyLg
📝 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.

Suggested change
val (borderColor, textColor, textStyle) = if(!isActive) {
Triple(Gray200, Gray400, CchTheme.typography.bodyLg)
} else {
Triple(Purple600, Black100, CchTheme.typography.bodyLgStrong)
Triple(if (isError) Red300 else Purple600, Black100, CchTheme.typography.bodyLgStrong)
}
// Before: destructuring all three values based on isActive/error
- val (borderColor, textColor, textStyle) = if(!isActive) {
- Triple(Gray200, Gray400, CchTheme.typography.bodyLg)
- } else {
- Triple(if (isError) Red300 else Purple600, Black100, CchTheme.typography.bodyLgStrong)
- }
// After: error-first borderColor, then active/inactive; separate textColor and textStyle
val borderColor = when {
isError -> Red300
isActive -> Purple600
else -> Gray200
}
val textColor = if (isActive) Black100 else Gray400
val textStyle = if (isActive) CchTheme.typography.bodyLgStrong else CchTheme.typography.bodyLg
🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt
around lines 40 to 44, the conditional currently hides the error border when
isActive is false; change the logic to make error take precedence: compute
borderColor as if (isError) Red300 else if (!isActive) Gray200 else Purple600,
and keep textColor/textStyle determined by isActive (Gray400/bodyLg when
inactive, Black100/bodyLgStrong when active). Update the Triple construction
accordingly so error always shows Red300 regardless of isActive.

Comment on lines +109 to 112
text = "등록된 시간표가 없어요!",
style = CchTheme.typography.bodyMd,
color = Gray600,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

문자열 하드코딩으로 i18n 깨짐 — stringResource 사용으로 복구 필요 (R)

  • Line 109에서 하드코딩된 문자열은 다국어/로컬라이제이션을 깨뜨립니다. 기존처럼 stringResource를 사용해 주세요. 기획 의도가 문구 변경이라면 리소스 값 자체를 갱신하고 번역 키를 유지/추가하세요.

원하시면 리소스 키/번역 추가 PR 스캐폴딩까지 준비해 드립니다.

-                    text = "등록된 시간표가 없어요!",
+                    text = stringResource(Res.string.timetable_list_screen_empty_timetable),

추가 검증을 위해 아래 스크립트를 실행해 주세요.
해당 리소스 키의 존재 여부와 stringResource 사용 패턴을 확인합니다.


🏁 Script executed:

#!/bin/bash
# 1. 리소스 파일 검색
find . -type f -name strings.xml

# 2. 키 존재 여부 확인
rg -n 'timetable_list_screen_empty_timetable' .

# 3. TimetableListScreen.kt의 import 및 stringResource 사용 여부 확인
sed -n '1,200p' composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt

# 4. 프로젝트 전반의 stringResource 사용 패턴 검색
rg -n 'stringResource' -C2 composeApp

Length of output: 45853


하드코딩된 문자열 제거 및 stringResource 적용 필요
109행의 Text 컴포저블에 하드코딩된 "등록된 시간표가 없어요!" 문자열이 남아 있어 다국어/로컬라이제이션(i18n)이 깨집니다. 이미 timetable_list_screen_empty_timetable 리소스(commonMain/composeResources/values/strings.xml 150행)에 기본값 "시간표가 없어요"로 정의되어 있으므로, 화면에 표시할 문구가 변경된 게 목적이라면 리소스 값을 "등록된 시간표가 없어요!"로 갱신하고 stringResource로 참조해야 합니다.

• 대상 위치

  • 파일: composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt
  • 라인: 109–112 (Text 컴포저블의 text 속성)

• 확인 사항

  1. commonMain/composeResources/values/strings.xmltimetable_list_screen_empty_timetable 정의 여부 및 값(현재 "시간표가 없어요")
  2. 리소스 값을 "등록된 시간표가 없어요!"로 업데이트할지 결정
  3. 수정 후 Text 컴포저블에 stringResource 호출 적용
@@ composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt:109
-                Text(
-                    modifier = Modifier
-                        .padding(top = 324.dp),
-                    textAlign = TextAlign.Center,
-                    text = "등록된 시간표가 없어요!",
-                    style = CchTheme.typography.bodyMd,
-                    color = Gray600,
-                )
+                Text(
+                    modifier = Modifier
+                        .padding(top = 324.dp),
+                    textAlign = TextAlign.Center,
+                    text = stringResource(Res.string.timetable_list_screen_empty_timetable),
+                    style = CchTheme.typography.bodyMd,
+                    color = Gray600,
+                )

— 리소스 갱신 및 참조 적용에 어려움이 있으면 알려주세요. PR 스캐폴딩 지원 가능합니다.

📝 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.

Suggested change
text = "등록된 시간표가 없어요!",
style = CchTheme.typography.bodyMd,
color = Gray600,
)
Text(
modifier = Modifier
.padding(top = 324.dp),
textAlign = TextAlign.Center,
text = stringResource(Res.string.timetable_list_screen_empty_timetable),
style = CchTheme.typography.bodyMd,
color = Gray600,
)
🤖 Prompt for AI Agents
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt
lines 109–112: Text composable currently uses a hardcoded string "등록된 시간표가 없어요!"
which breaks localization; replace the hardcoded text with a stringResource
lookup (R or resource key) referencing timetable_list_screen_empty_timetable,
and if you intend the displayed wording to be "등록된 시간표가 없어요!" update
commonMain/composeResources/values/strings.xml entry
timetable_list_screen_empty_timetable to that value; finally rebuild to ensure
the resource key is resolved and the Text uses stringResource(...) instead of a
raw string.

val semester: Semester = Semester("", "")
) {
val buttonEnabled = name.isNotEmpty()
val buttonEnabled = name.isNotEmpty() && name.length <= 20

@coderabbitai coderabbitai Bot Aug 25, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

버튼 활성화 조건에서 공백-only 허용 및 하드코딩 상수(20) 중복 위험 — 상수화 + trim/isNotBlank 권장

현재는 공백만 입력한 경우에도 isNotEmpty()로 활성화될 수 있고, 길이 비교가 name.length로 직접 들어가 있어 다른 화면과 중복/불일치가 생길 수 있습니다. 공통 상수로 중앙화하고, 공백 제거 후 길이 체크를 권장합니다. 또한 코드포인트/이모지 고려가 필요하면 추가 확장 가능하도록 해두면 좋습니다.

적용 예시(diff):

-    val buttonEnabled = name.isNotEmpty() && name.length <= 20
+    val buttonEnabled =
+        name.isNotBlank() &&
+        name.trim().length <= TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH

공유 상수 정의 예시(별도 파일, 예: presentation/timetable/TimetableConstraints.kt):

package com.chukchukhaksa.mobile.presentation.timetable

object TimetableConstraints {
    const val MAX_TIMETABLE_NAME_LENGTH = 20
}

리포지터리 내 하드코딩된 20자 비교식 일괄 확인 스크립트:


🏁 Script executed:

#!/bin/bash
# 20자 제한 하드코딩 사용처 점검
rg -nP --type=kt '(length\s*[<>]=?\s*20|\s*>\s*20|\s*<\s*20)'

Length of output: 90


🏁 Script executed:

#!/bin/bash
# 20자 제한 하드코딩 사용처 점검 (`.kt` 파일만 검색)
rg -nP -g '*.kt' 'length\s*(?:<=|>=|<|>)\s*20'

Length of output: 1242


공통 상수화 및 공백 처리(isNotBlank, trim) 적용 필요

하드코딩된 20자 제한이 여러 파일에 중복되어 있어, 추후 변경 시 일관성 유지가 어렵습니다. 또한 isNotEmpty()만으로는 공백만 입력한 경우를 걸러낼 수 없으므로, isNotBlank()trim()을 활용해 정확히 비어 있지 않은 문자열만 처리하도록 개선해야 합니다.

수정 대상 파일 및 위치:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt (12행)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt (107·112행)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt (108·113행)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt (16행)

공통 상수 정의 예시 (presentation/timetable/TimetableConstraints.kt):

package com.chukchukhaksa.mobile.presentation.timetable

object TimetableConstraints {
    const val MAX_TIMETABLE_NAME_LENGTH = 20
}

주요 변경 예시 (Contract.kt 기준):

-    val buttonEnabled = name.isNotEmpty() && name.length <= 20
+    val buttonEnabled =
+        name.isNotBlank() &&
+        name.trim().length <= TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH

주요 변경 예시 (Screen.kt 기준):

-                    isError = uiState.name.length > 20,
+                    isError = uiState.name.trim().length > TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH,

-                if (uiState.name.length > 20) {
+                if (uiState.name.trim().length > TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH) {
  • 모든 비교 로직에서 length <= 20, length > 20 등을 TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH로 교체
  • isNotEmpty()isNotBlank()
  • 비교 전 trim() 적용으로 공백 전후 제거

위 리팩토링을 통해 상수 중복을 제거하고, 공백만 입력된 경우를 차단하며, 추후 제약 변경 시 한곳만 수정하도록 개선할 수 있습니다.

🤖 Prompt for AI Agents
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt
(line 12), plus related locations composeApp/.../TimetableNameInputScreen.kt
(lines 107,112), TimetableEditorScreen.kt (lines 108,113) and
TimetableEditorContract.kt (line 16): replace the hardcoded 20 with a shared
constant by adding presentation/timetable/TimetableConstraints.kt containing
MAX_TIMETABLE_NAME_LENGTH = 20, change all name emptiness checks from
isNotEmpty() to isNotBlank() and apply trim() before length checks (e.g., val
trimmed = name.trim(); buttonEnabled = trimmed.isNotBlank() && trimmed.length <=
TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH), and update any >20 / <=20
comparisons to use TimetableConstraints.MAX_TIMETABLE_NAME_LENGTH so all files
reference the common constraint.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

이건 반영해주시죠 @BEEEAM-J

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

코멘트 내용 반영했습니다~ @jinukeu

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

넵 확인이용 바로 머지 가시죠! @BEEEAM-J

- 시간표 이름 글자 수 제한 상수 `TIMETABLE_NAME_LIMIT` 정의
- `checkOverTimetableNameLimit`: 시간표 이름이 글자 수 제한을 초과하는지 확인
- `checkTimetableNameRule`: 시간표 이름이 유효한지(공백이 아니고 글자 수 제한 이내) 확인

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

Caution

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

⚠️ Outside diff range comments (2)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt (1)

103-111: isActive 조건도 공백-only 입력을 비활성화하도록 정합성 맞추기

버튼 활성화는 isNotBlank() 기준인데, 텍스트필드의 active 표시는 isNotEmpty()로 공백-only에서도 active로 보일 수 있습니다. 동일 규칙으로 맞추면 UX가 일관됩니다.

-                    isActive = uiState.name.isNotEmpty(),
+                    isActive = uiState.name.isNotBlank(),
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt (1)

104-112: isActive 조건 공백-only 방지 일치화

에디터에서도 텍스트필드 활성 조건을 공백-only에서 비활성화하도록 맞추면 일관됩니다.

-                    isActive = uiState.name.isNotEmpty() && uiState.name != uiState.preName,
+                    isActive = uiState.name.isNotBlank() && uiState.name != uiState.preName,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between afc993a and d649543.

📒 Files selected for processing (5)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt (1 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt (2 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt (3 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt (1 hunks)
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.kt: Use the Kotlin official code style
Use immutable state with data classes

Files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
🧠 Learnings (7)
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/util/**/*.kt : Organize utility functions in the util directory

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/**/*.kt : When creating new UI components, use `CCHaksaTheme` for new features

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/**/*.kt : Prefer typography styles over hardcoded text styles

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/**/*.kt : Organize UI components and themes in the designsystem directory

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/**/*.kt : Preview components in the `preview/designsystem/` package

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/theme/**/*.kt : Follow existing color naming conventions (100-600 scale) when creating new colors

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
📚 Learning: 2025-08-03T02:53:00.607Z
Learnt from: CR
PR: cchaksa/cchaksa-kmp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-03T02:53:00.607Z
Learning: Applies to composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/**/*.kt : Organize previews for UI components in the preview/designsystem directory

Applied to files:

  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
🧬 Code graph analysis (4)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt (1)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt (1)
  • checkTimetableNameRule (9-11)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt (1)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt (1)
  • checkTimetableNameRule (9-11)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt (1)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt (1)
  • checkOverTimetableNameLimit (5-7)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt (1)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt (1)
  • checkOverTimetableNameLimit (5-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt (1)

3-3: 버튼 활성화 규칙 공통화 적용 👍

checkTimetableNameRule(name)로 버튼 활성화 조건을 통일한 점 좋습니다. 입력이 공백-only인 경우 비활성화되고, 길이 제한도 공유 규칙으로 일치합니다. 이 변경은 타 화면(에디터)과 일관성 있게 유지됩니다.

Also applies to: 12-12

Comment on lines +3 to +11
const val TIMETABLE_NAME_LIMIT = 20

fun checkOverTimetableNameLimit(name: String): Boolean {
return name.trim().length > TIMETABLE_NAME_LIMIT
}

fun checkTimetableNameRule(name: String): Boolean {
return name.isNotBlank() && name.trim().length <= TIMETABLE_NAME_LIMIT
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

문자 길이 기준(UTF-16 length vs. 사용자 체감 글자수) 확인 필요

현재 length 기준은 UTF‑16 코드 유닛 수입니다. 이모지/조합형 문자(예: 피부톤 이모지, 합성 플래그)에서는 사용자 체감 글자수와 어긋날 수 있습니다. 요구사항이 “시각적으로 20자”를 의미한다면, KMP 환경에서의 그라페메 클러스터 카운팅 전략(플랫폼별 ICU/Regex) 검토가 필요합니다. 현 단계에서는 정책 확정 전까지는 현 구현 유지 + 명확한 스펙 주석 추가를 권장합니다.


🧹 Nitpick (assertive)

불리언 규칙 함수 네이밍과 위치 일관성 개선 제안

  • 네이밍: checkOverTimetableNameLimit/checkTimetableNameRule는 결과가 true/false인 검증기이므로 술어형(is*)이 가독성이 더 좋습니다.
    예: isTimetableNameOverLimit, isTimetableNameValid.
  • 패키지/위치: 본 파일은 확장 함수가 아닌 공통 유틸입니다. 기존 가이드(learned) 상 util 디렉터리로의 이동을 권장합니다: common/util/...로 이동하면 검색성과 응집도가 좋아집니다.

아래와 같이 네이밍을 정리하면 호출부 가독성이 개선됩니다.

-const val TIMETABLE_NAME_LIMIT = 20
-
-fun checkOverTimetableNameLimit(name: String): Boolean {
-    return name.trim().length > TIMETABLE_NAME_LIMIT
-}
-
-fun checkTimetableNameRule(name: String): Boolean {
-    return name.isNotBlank() && name.trim().length <= TIMETABLE_NAME_LIMIT
-}
+const val TIMETABLE_NAME_LIMIT = 20
+
+fun isTimetableNameOverLimit(name: String): Boolean =
+    name.trim().length > TIMETABLE_NAME_LIMIT
+
+fun isTimetableNameValid(name: String): Boolean =
+    name.isNotBlank() && name.trim().length <= TIMETABLE_NAME_LIMIT

호출부 변경은 본 PR의 두 Screen/두 Contract에서만 필요해 영향 범위가 작습니다. 원하시면 관련 호출부 diff도 함께 제안드리겠습니다.

📝 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.

Suggested change
const val TIMETABLE_NAME_LIMIT = 20
fun checkOverTimetableNameLimit(name: String): Boolean {
return name.trim().length > TIMETABLE_NAME_LIMIT
}
fun checkTimetableNameRule(name: String): Boolean {
return name.isNotBlank() && name.trim().length <= TIMETABLE_NAME_LIMIT
}
const val TIMETABLE_NAME_LIMIT = 20
fun isTimetableNameOverLimit(name: String): Boolean =
name.trim().length > TIMETABLE_NAME_LIMIT
fun isTimetableNameValid(name: String): Boolean =
name.isNotBlank() && name.trim().length <= TIMETABLE_NAME_LIMIT
🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/extension/TimetableNameLimit.kt
around lines 3-11, the boolean validator functions use non-predicate names and
live in an extensions package though they are general utilities; rename
checkOverTimetableNameLimit -> isTimetableNameOverLimit and
checkTimetableNameRule -> isTimetableNameValid, keep the same logic, move the
file into common/util (e.g.,
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/util/TimetableNameLimit.kt)
and update the two Screens/Contracts that call these functions to use the new
names and import path.

import com.chukchukhaksa.mobile.presentation.timetable.navigation.argument.TimetableEditorArgument
import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.semesterList
import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.Semester
import com.chukchukhaksa.mobile.common.extension.checkTimetableNameRule

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

에디터 버튼 활성화 규칙 공통화 적용 👍

checkTimetableNameRule(name)(= 공백 방지 + 트림 후 길이 제한)와 변경 유무 체크를 함께 적용한 구성은 명확합니다. 괄호는 중복이므로 간결화 여지는 있지만 기능상 문제는 없습니다.

-    val buttonEnabled = ((checkTimetableNameRule(name)) && (preName != name || preSelectedSemesterPosition != selectedSemesterPosition))
+    val buttonEnabled = checkTimetableNameRule(name) &&
+        (preName != name || preSelectedSemesterPosition != selectedSemesterPosition)

Also applies to: 17-17

🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt
around lines 6 and 17, there are redundant parentheses around the condition that
uses checkTimetableNameRule(name) together with the change-check; remove the
extra parentheses to simplify the boolean expression (e.g., change from
((checkTimetableNameRule(name)) && changed) or (checkTimetableNameRule(name) &&
changed) with extra surrounding parens to simply checkTimetableNameRule(name) &&
changed) while keeping the same logic and spacing.

Comment on lines +109 to 112
isError = checkOverTimetableNameLimit(uiState.name),
onValueChanged = onValueChangeTimetableName,
onClickClearButton = onClickTextFieldClearButton,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

에러 표시 조건 불일치와 하드코딩(20) 중복 — 입력 화면과 동일 이슈

입력 화면과 동일하게 isError(trim 기준)와 에러 텍스트 표시(원문 length > 20)의 기준이 달라집니다. 공통 함수/상수를 사용해 정합성을 맞춰주세요.

-                    isError = checkOverTimetableNameLimit(uiState.name),
+                    isError = checkOverTimetableNameLimit(uiState.name),
...
-                if (uiState.name.length > 20) {
+                if (checkOverTimetableNameLimit(uiState.name)) {
                   Text(
                     modifier = Modifier
                       .padding(top = 12.dp, start = 4.dp, end = 4.dp)
                       .fillMaxWidth(),
-                    text = "시간표 이름은 최대 20자까지 설정 가능합니다.",
+                    text = "시간표 이름은 최대 ${TIMETABLE_NAME_LIMIT}자까지 설정 가능합니다.",
                     textAlign = TextAlign.Start,
                     style = CchTheme.typography.bodyMd,
                     color = Red300,
                   )
                 }

필요한 import:

 import com.chukchukhaksa.mobile.common.extension.checkOverTimetableNameLimit
+import com.chukchukhaksa.mobile.common.extension.TIMETABLE_NAME_LIMIT

Also applies to: 114-124

🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
around lines 109-112 (and also apply to 114-124), the isError check uses a
different criterion than the error-text visibility and the hardcoded limit 20 is
duplicated; refactor to a shared constant (e.g., TIMETABLE_NAME_MAX_LENGTH) and
a shared trim-aware validator function (e.g., isOverTimetableNameLimit(name:
String): Boolean) imported from the common validation/constants file, then
replace the inline isError and the length > 20 checks to use that single
function/constant so both the error flag and displayed message use the same
trim-based threshold; update imports to include the constant/function and adjust
any error message formatting to reference the constant instead of the hardcoded
20.

Comment on lines +114 to +124
if (uiState.name.length > 20) {
Text(
modifier = Modifier
.padding(top = 12.dp, start = 4.dp, end = 4.dp)
.fillMaxWidth(),
text = "시간표 이름은 최대 20자까지 설정 가능합니다.",
textAlign = TextAlign.Start,
style = CchTheme.typography.bodyMd,
color = Red300,
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

문구 하드코딩 → 리소스화 고려 (동일 제안)

입력 화면과 동일하게 문자열 리소스 사용을 권장합니다. 자리표시자에 TIMETABLE_NAME_LIMIT 주입 방식으로 국제화 대비가 용이합니다.

🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt
around lines 114–124, the displayed Korean message is hardcoded; extract it into
a string resource (e.g., TIMETABLE_NAME_LIMIT) and replace the literal with a
localized lookup (use stringResource or your multiplatform resource helper and
inject the limit placeholder value). Add the new resource entry in your strings
resources (with a placeholder for the max length) and update the UI code to call
the resource with the max length (20) instead of the hardcoded text.

Comment on lines +108 to 111
isError = checkOverTimetableNameLimit(uiState.name),
onValueChanged = onValueChangeTimetableName,
onClickClearButton = onClickTextFieldClearButton,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

에러 표시 조건 불일치와 하드코딩(20) 중복 — 단일 소스 사용으로 정합성 보장 필요

  • 텍스트필드 isErrorcheckOverTimetableNameLimit(uiState.name)(trim 기준)인데, 아래 에러 문구 표시는 uiState.name.length > 20(trim 미적용)로 달라 UX 혼란이 생깁니다.
  • 메시지의 숫자 20이 하드코딩되어 유지보수성이 떨어집니다.

동일한 검증 함수와 공통 상수를 사용하도록 아래와 같이 정리해주세요.

-                    isError = checkOverTimetableNameLimit(uiState.name),
+                    isError = checkOverTimetableNameLimit(uiState.name),
...
-                if (uiState.name.length > 20) {
+                if (checkOverTimetableNameLimit(uiState.name)) {
                   Text(
                     modifier = Modifier
                       .padding(top = 12.dp, start = 4.dp, end = 4.dp)
                       .fillMaxWidth(),
-                    text = "시간표 이름은 최대 20자까지 설정 가능합니다.",
+                    text = "시간표 이름은 최대 ${TIMETABLE_NAME_LIMIT}자까지 설정 가능합니다.",
                     textAlign = TextAlign.Start,
                     style = CchTheme.typography.bodyMd,
                     color = Red300,
                   )
                 }

필요한 import 추가:

 import com.chukchukhaksa.mobile.common.extension.checkOverTimetableNameLimit
+import com.chukchukhaksa.mobile.common.extension.TIMETABLE_NAME_LIMIT

Also applies to: 113-123

Comment on lines +113 to +123
if (uiState.name.length > 20) {
Text(
modifier = Modifier
.padding(top = 12.dp, start = 4.dp, end = 4.dp)
.fillMaxWidth(),
text = "시간표 이름은 최대 20자까지 설정 가능합니다.",
textAlign = TextAlign.Start,
style = CchTheme.typography.bodyMd,
color = Red300,
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

문구 하드코딩 → 리소스화 고려

국제화/복수 플랫폼 대응을 위해 에러 메시지를 String 리소스로 이동하고 자리표시자에 상수를 주입하는 방식을 권장합니다.

예)

  • strings: timetable_name_over_limit = "시간표 이름은 최대 %1$d자까지 설정 가능합니다."
  • 코드: text = stringResource(Res.string.timetable_name_over_limit, TIMETABLE_NAME_LIMIT)

현재 PR 범위를 최소화하려면 위의 상수 보간 버전으로 반영하고, 후속 PR에서 리소스화를 진행해도 됩니다.

🤖 Prompt for AI Agents
In
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt
around lines 113–123, replace the hardcoded Korean error text with a
stringResource that injects the max-length constant; change text = "시간표 이름은 최대
20자까지 설정 가능합니다." to text = stringResource(Res.string.timetable_name_over_limit,
TIMETABLE_NAME_LIMIT) (or similar project resource identifier), ensure
TIMETABLE_NAME_LIMIT constant is referenced/defined and import
stringResource/Res.string as needed so the message uses the constant placeholder
rather than hardcoded "20".

@lluke0
lluke0 merged commit da48268 into develop Aug 30, 2025
1 of 2 checks passed
@lluke0
lluke0 deleted the feature/#30-feat-timetable-name-limit branch August 30, 2025 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 시간표 이름은 최대 20자까지 설정 가능

2 participants