[FEAT] 홈 투두 생성 모달 하위 태스크/텍스트 입력 UX 개선 - #158
Conversation
- 메모 필드에 최대 300자 zod 검증을 추가했습니다
- 입력 시 scrollHeight 기준으로 자동 높이 조절 기능을 추가했습니다 - 뷰포트의 40%를 넘으면 내부 스크롤되도록 max-height와 overflow 처리를 추가했습니다
- 하위 태스크 입력 중 Enter 시 최대 10개까지 필드를 추가하고, 빈 값에서 Backspace 시 필드를 삭제하도록 구현했습니다 - 투두명/하위 태스크 텍스트 폭 초과 시 자동 줄바꿈되도록 textarea 기반으로 변경했습니다 - 투두명 길이를 한국어 20자/영어 30자 기준 가중치로 제한하고, 계산 로직을 유틸로 분리했습니다
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Walkthrough투두 생성 모달의 제목·메모·하위 태스크 입력에 길이 제한과 textarea 자동 높이 조절을 적용했습니다. 하위 태스크는 최대 10개까지 Enter로 추가하고, 빈 필드에서 Backspace로 삭제할 수 있도록 폼 훅과 제어형 필드 구조를 도입했습니다. Changes투두 입력 UX
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CreateTodoModalContent
participant CreateTodoTaskFields
participant useTitleField
participant useSubtaskField
CreateTodoModalContent->>useTitleField: 제목 필드 연결
CreateTodoModalContent->>useSubtaskField: 하위 태스크 필드 연결
CreateTodoModalContent->>CreateTodoTaskFields: 제어형 입력 props 전달
CreateTodoTaskFields->>useTitleField: 제목 변경 전달
CreateTodoTaskFields->>useSubtaskField: 하위 태스크 변경·키보드 이벤트 전달
useSubtaskField->>CreateTodoModalContent: 폼 subtasks 값 동기화
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
- 완료된 투두/하위 태스크의 체크박스가 비활성화되어 다시 되돌릴 수 없던 문제를 수정했습니다
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/timo-web/api/todo/todo-schema.ts (1)
33-46: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick wintitle/subtasks 길이·개수 제한이 스키마에는 빠져있어요.
memo는 스키마 레벨에서.max(300)으로 강제되는데,title(가중 길이 30)과subtasks(최대 10개)는 UI 훅(truncateToWeightedLength,MAX_SUBTASK_COUNT)에서만 제한되고 스키마에는 상한이 없습니다.zodResolver를 통해 제출 시점에도 재검증되는 지점이므로, 클라이언트 로직이 우회되거나 추후 변경돼도 방어선이 없는 상태예요.
getWeightedLength를 재사용해.refine()으로 title을,subtasks엔.max(10)을 추가하면 memo와 동일한 수준의 데이터 정합성을 확보할 수 있습니다.🛡️ 제안 diff
+import { getWeightedLength, TITLE_MAX_WEIGHTED_LENGTH } from "`@/app/`[locale]/(main)/(with-time-sidebar)/home/_utils/text-length"; + export const createTodoRequestSchema = z .object({ icon: todoIconSchema.nullable(), - title: z.string().trim().min(1), - subtasks: z.array(z.string().trim().min(1)).nullable(), + title: z + .string() + .trim() + .min(1) + .refine((value) => getWeightedLength(value) <= TITLE_MAX_WEIGHTED_LENGTH, { + message: "제목이 너무 길어요.", + }), + subtasks: z.array(z.string().trim().min(1)).max(10).nullable(),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/api/todo/todo-schema.ts` around lines 33 - 46, Update createTodoRequestSchema to enforce the UI constraints at schema validation: apply a getWeightedLength-based refine to title with the weighted maximum of 30, and add a maximum of 10 items to subtasks while preserving its nullable behavior. Reuse the existing getWeightedLength symbol and keep the current per-item string validation intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoTaskFields.tsx:
- Around line 54-57: Replace index-based keys for the dynamic subtask list
rendered in CreateTodoTaskFields with stable unique IDs. Update useSubtaskField
to store each entry as an object containing id and value, generate IDs for new
entries, and adjust change, add, delete, reset, and form-sync logic to use the
object shape while syncing trimmed values. Use each entry’s id as the rendered
key and preserve pending-focus behavior.
---
Outside diff comments:
In `@apps/timo-web/api/todo/todo-schema.ts`:
- Around line 33-46: Update createTodoRequestSchema to enforce the UI
constraints at schema validation: apply a getWeightedLength-based refine to
title with the weighted maximum of 30, and add a maximum of 10 items to subtasks
while preserving its nullable behavior. Reuse the existing getWeightedLength
symbol and keep the current per-item string validation intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 20ace676-ebea-4aba-a751-c0dfe8864778
📒 Files selected for processing (7)
apps/timo-web/api/todo/todo-schema.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoTaskFields.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-subtask-field.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-title-field.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/text-length.ts
- 하위 태스크를 {id, value} 객체로 관리하도록 변경했습니다
- index 대신 고유 id를 리스트 key로 사용해 중간 항목 삭제 시 엉뚱한 DOM 노드가 재사용되는 문제를 방지했습니다
- 하위 태스크 입력 필드에서 Enter로 추가, Backspace로 병합·삭제하는 로직을 순수 함수(addSubtaskInputOnEnter, removeSubtaskInputOnBackspace)로 분리했습니다 - 도메인 종속 유틸이 아닌 공통 유틸(apps/timo-web/utils/todo)로 이동해 재사용 가능하도록 구조화했습니다 - 관련 타입 import 경로를 새 유틸 위치로 갱신했습니다
yumin-kim2
left a comment
There was a problem hiding this comment.
공통 유틸 함수로 빼주셔서 재사용할 수 있는 부분이 많겠네요!. (감사합니다 🫰🏻)
로직도 깔끔하게 잘 구현하신 것 같아요 수고하셨씁니다~~!!!
ISSUE 🔗
close #157
What is this PR? 🔍
홈 투두 생성 모달의 입력 UX를 개선했습니다. 하위 태스크를 여러 개 입력/삭제할 수 있도록 하고, 텍스트 길이 제한과 줄바꿈/스크롤 처리를 통해 모달 레이아웃이 깨지지 않도록 했습니다.
배경
<input>이라 텍스트가 길어지면 줄바꿈 없이 잘렸습니다.textarea기반 자동 줄바꿈/높이 조절 구조로 통일했습니다. 길이 제한이 필요한 필드에는 명시적 상한(글자 수 또는 가중치 길이)을 두었습니다.하위 태스크 입력
use-subtask-field.ts훅에서subtaskInputs: string[]로컬 상태를 관리하고,react-hook-form의subtasks필드에는 trim 후 빈 문자열을 제외한 값만 동기화합니다. Enter는 마지막 필드에 값이 있고 개수가 10개 미만일 때만 새 빈 필드를 추가하며, 추가된 필드로 포커스를 옮깁니다. Backspace는 값이 완전히 빈 필드이고 첫 번째 필드가 아닐 때만 해당 필드를 제거하고, 바로 위 필드 끝으로 포커스를 옮깁니다. 포커스 이동은pendingFocusIndexref와subtaskInputs.length에 의존하는useEffect로 처리합니다.텍스트 줄바꿈 및 길이 제한
<input>에서 자동 높이 조절되는<textarea>로 변경해 폭 초과 시 자동 줄바꿈되도록 했고, 투두명은 한국어 20자/영어 30자 기준으로 길이를 제한했습니다.<input>은 텍스트가 길어져도 줄바꿈 없이 잘려, 긴 제목/하위 태스크를 온전히 확인할 수 없었습니다. 또한 투두명 길이 제한이 없어 카드 UI에서 텍스트가 과도하게 길어질 수 있었습니다.resizeTextarea헬퍼로scrollHeight기준 높이를 매 입력마다 갱신합니다. 투두명 길이 제한은_utils/text-length.ts의getWeightedLength/truncateToWeightedLength로 계산하며, 한글 문자는 가중치 1.5, 그 외 문자는 1로 계산해 최대 가중 길이 30(한국어 20자 또는 영어 30자와 동일)을 넘지 않도록 입력 시마다 잘라냅니다.use-title-field.ts에서useController로 title 필드를 controlled로 전환해 이 truncate 로직을 적용했습니다.메모 필드
maxLength={300}네이티브 속성으로 입력 자체를 제한하고, zod 스키마(todo-schema.ts)의memo필드에도.max(300)검증을 추가해 이중으로 방어했습니다.resizeTextarea로 자동 높이를 조절하되max-h-[40vh]+overflow-y-auto를 함께 적용해, 높이가 뷰포트의 40%를 넘어서면 모달이 계속 늘어나는 대신 메모 박스 내부에서 스크롤되도록 했습니다.To Reviewers
하위 태스크 배열 상태(
subtaskInputs)와react-hook-form의subtasks필드 값이 분리되어 있는 구조라, 폼 제출 시점에 trim/필터링된 값만 반영되는지 확인 부탁드립니다. 포커스 이동 로직(pendingFocusIndex)은 index 기반 key 재사용에 의존하고 있어, 추후 하위 태스크 순서 변경(드래그 등) 기능이 추가되면 재검토가 필요합니다.Screenshot 📷
Test Checklist ✔
pnpm exec tsc --noEmit(apps/timo-web) 통과pnpm --filter timo-web lint통과