Skip to content

[FEAT] 홈 투두 생성 모달 하위 태스크/텍스트 입력 UX 개선 - #158

Merged
kimminna merged 6 commits into
developfrom
feat/web/157-home-todo-modal-input-ux
Jul 12, 2026
Merged

[FEAT] 홈 투두 생성 모달 하위 태스크/텍스트 입력 UX 개선#158
kimminna merged 6 commits into
developfrom
feat/web/157-home-todo-modal-input-ux

Conversation

@kimminna

@kimminna kimminna commented Jul 12, 2026

Copy link
Copy Markdown
Member

ISSUE 🔗

close #157



What is this PR? 🔍

홈 투두 생성 모달의 입력 UX를 개선했습니다. 하위 태스크를 여러 개 입력/삭제할 수 있도록 하고, 텍스트 길이 제한과 줄바꿈/스크롤 처리를 통해 모달 레이아웃이 깨지지 않도록 했습니다.

배경

  • 기존 구조: 하위 태스크는 입력 필드 하나만 존재해 여러 개를 등록할 수 없었고, 투두명/하위 태스크는 <input>이라 텍스트가 길어지면 줄바꿈 없이 잘렸습니다.
  • 발생 문제: 하위 태스크를 여러 개 남기고 싶어도 등록할 방법이 없었고, 메모는 길이 제한 없이 계속 입력할 수 있어 모달이 무한정 늘어날 위험이 있었습니다.
  • 해결 방향: 하위 태스크를 배열 기반 다중 입력으로 바꾸고, 텍스트 입력 요소를 textarea 기반 자동 줄바꿈/높이 조절 구조로 통일했습니다. 길이 제한이 필요한 필드에는 명시적 상한(글자 수 또는 가중치 길이)을 두었습니다.



하위 태스크 입력

  • 변경 요약: 하위 태스크 입력 중 Enter를 누르면 새 입력 필드가 추가(최대 10개)되고, 빈 필드에서 Backspace를 누르면 해당 필드가 삭제되도록 구현했습니다.
  • 이유: 기존에는 하위 태스크 입력 필드가 하나뿐이라 여러 개의 하위 태스크를 나눠 등록할 수 없었습니다.
  • 구현 방식: use-subtask-field.ts 훅에서 subtaskInputs: string[] 로컬 상태를 관리하고, react-hook-formsubtasks 필드에는 trim 후 빈 문자열을 제외한 값만 동기화합니다. Enter는 마지막 필드에 값이 있고 개수가 10개 미만일 때만 새 빈 필드를 추가하며, 추가된 필드로 포커스를 옮깁니다. Backspace는 값이 완전히 빈 필드이고 첫 번째 필드가 아닐 때만 해당 필드를 제거하고, 바로 위 필드 끝으로 포커스를 옮깁니다. 포커스 이동은 pendingFocusIndex ref와 subtaskInputs.length에 의존하는 useEffect로 처리합니다.
  • 경계 · 제약: 첫 번째 하위 태스크 필드는 병합할 대상이 없어 Backspace로 삭제되지 않도록 의도적으로 제외했습니다.

텍스트 줄바꿈 및 길이 제한

  • 변경 요약: 투두명/하위 태스크 입력을 <input>에서 자동 높이 조절되는 <textarea>로 변경해 폭 초과 시 자동 줄바꿈되도록 했고, 투두명은 한국어 20자/영어 30자 기준으로 길이를 제한했습니다.
  • 이유: <input>은 텍스트가 길어져도 줄바꿈 없이 잘려, 긴 제목/하위 태스크를 온전히 확인할 수 없었습니다. 또한 투두명 길이 제한이 없어 카드 UI에서 텍스트가 과도하게 길어질 수 있었습니다.
  • 구현 방식: resizeTextarea 헬퍼로 scrollHeight 기준 높이를 매 입력마다 갱신합니다. 투두명 길이 제한은 _utils/text-length.tsgetWeightedLength/truncateToWeightedLength로 계산하며, 한글 문자는 가중치 1.5, 그 외 문자는 1로 계산해 최대 가중 길이 30(한국어 20자 또는 영어 30자와 동일)을 넘지 않도록 입력 시마다 잘라냅니다. use-title-field.ts에서 useController로 title 필드를 controlled로 전환해 이 truncate 로직을 적용했습니다.
  • 경계 · 제약: 가중치 기반 길이 제한은 투두명에만 적용했고, 하위 태스크에는 별도 글자 수 제한을 추가하지 않았습니다.

메모 필드

  • 변경 요약: 메모 입력에 300자 제한을 추가하고, 자동 높이 조절과 뷰포트 40% 초과 시 내부 스크롤 처리를 추가했습니다.
  • 이유: 메모 입력에 길이 제한이 없어 매우 긴 메모를 입력하면 모달이 뷰포트를 벗어나거나 레이아웃이 깨질 위험이 있었습니다.
  • 구현 방식: maxLength={300} 네이티브 속성으로 입력 자체를 제한하고, zod 스키마(todo-schema.ts)의 memo 필드에도 .max(300) 검증을 추가해 이중으로 방어했습니다. resizeTextarea로 자동 높이를 조절하되 max-h-[40vh] + overflow-y-auto를 함께 적용해, 높이가 뷰포트의 40%를 넘어서면 모달이 계속 늘어나는 대신 메모 박스 내부에서 스크롤되도록 했습니다.



To Reviewers

하위 태스크 배열 상태(subtaskInputs)와 react-hook-formsubtasks 필드 값이 분리되어 있는 구조라, 폼 제출 시점에 trim/필터링된 값만 반영되는지 확인 부탁드립니다. 포커스 이동 로직(pendingFocusIndex)은 index 기반 key 재사용에 의존하고 있어, 추후 하위 태스크 순서 변경(드래그 등) 기능이 추가되면 재검토가 필요합니다.



Screenshot 📷

Animation



Test Checklist ✔

  • pnpm exec tsc --noEmit (apps/timo-web) 통과
  • pnpm --filter timo-web lint 통과
  • 브라우저에서 하위 태스크 Enter/Backspace 동작 확인 — 미실행
  • 메모 300자 제한 및 스크롤 동작 확인 — 미실행
  • 투두명 한국어/영어 글자 수 제한 동작 확인 — 미실행

kimminna added 3 commits July 13, 2026 00:08
- 메모 필드에 최대 300자 zod 검증을 추가했습니다
- 입력 시 scrollHeight 기준으로 자동 높이 조절 기능을 추가했습니다
- 뷰포트의 40%를 넘으면 내부 스크롤되도록 max-height와 overflow 처리를 추가했습니다
- 하위 태스크 입력 중 Enter 시 최대 10개까지 필드를 추가하고, 빈 값에서 Backspace 시 필드를 삭제하도록 구현했습니다
- 투두명/하위 태스크 텍스트 폭 초과 시 자동 줄바꿈되도록 textarea 기반으로 변경했습니다
- 투두명 길이를 한국어 20자/영어 30자 기준 가중치로 제한하고, 계산 로직을 유틸로 분리했습니다
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 12, 2026 6:13pm

Request Review

@github-actions github-actions Bot added ⏰ Timo-web Timo 웹 서비스 ✨ Feature 새로운 기능(기능성) 구현 ♦️ 민아 민아상 labels Jul 12, 2026
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kimminna, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8cec802e-0ebb-4ea5-a29d-cb9c6467bd65

📥 Commits

Reviewing files that changed from the base of the PR and between 994540d and 8cc09da.

📒 Files selected for processing (4)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoTaskFields.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-subtask-field.ts
  • apps/timo-web/utils/todo/subtask-input-list.ts

Walkthrough

투두 생성 모달의 제목·메모·하위 태스크 입력에 길이 제한과 textarea 자동 높이 조절을 적용했습니다. 하위 태스크는 최대 10개까지 Enter로 추가하고, 빈 필드에서 Backspace로 삭제할 수 있도록 폼 훅과 제어형 필드 구조를 도입했습니다.

Changes

투두 입력 UX

Layer / File(s) Summary
텍스트 길이 계약과 메모 입력
apps/timo-web/.../todo-schema.ts, apps/timo-web/.../CreateTodoMemoField.tsx, apps/timo-web/.../use-title-field.ts, apps/timo-web/.../text-length.ts
투두명은 한글 1.5, 기타 문자 1의 가중치로 최대 30까지 잘리며, 메모는 최대 300자로 검증·제한됩니다. 제목과 메모 textarea에는 자동 높이 조절 및 최대 높이·스크롤 동작이 추가되었습니다.
하위 태스크 입력 상태와 키보드 동작
apps/timo-web/.../use-subtask-field.ts
하위 태스크 배열과 DOM ref를 관리하고, 입력값을 폼과 동기화합니다. 마지막 입력에서 Enter로 항목을 추가하고, 빈 입력에서 Backspace로 이전 항목에 포커스를 이동하며 현재 항목을 삭제합니다.
제어형 textarea 필드 렌더링
apps/timo-web/.../CreateTodoTaskFields.tsx
기존 등록 기반 입력을 제어형 textarea로 전환하고, 제목과 여러 하위 태스크의 값·변경·키보드 이벤트·동적 ref를 연결합니다.
생성 모달 폼 통합
apps/timo-web/.../CreateTodoModalContent.tsx
모달이 제목·하위 태스크 전용 훅을 사용하도록 변경되었으며, 제출 후 각 필드 상태를 초기화하고 생성 버튼의 비활성 조건을 새 제목 상태에 맞게 갱신했습니다.

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 값 동기화
Loading

Possibly related PRs

  • Team-Timo/Timo-client#138: 동일한 투두 생성 모달과 요청 스키마의 메모 입력 흐름을 추가한 변경입니다.

Suggested reviewers: yumin-kim2, jjangminii, ehye1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed #157의 하위 태스크/텍스트 UX, 메모 제한, 가중 길이 유틸 분리 요구가 모두 반영됐습니다.
Out of Scope Changes check ✅ Passed 요약된 수정은 모두 모달 입력 UX 개선 범위 안이며 무관한 변경은 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 홈 투두 생성 모달의 하위 태스크 입력과 텍스트 UX 개선을 정확히 요약합니다.
Description check ✅ Passed 설명이 하위 태스크 다중 입력, textarea 자동 높이, 길이 제한 등 변경 사항과 잘 맞습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/157-home-todo-modal-input-ux

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

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

@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale]/home 194.75 kB 🔴 400.59 kB
/[locale]/today 154.53 kB 🔴 360.37 kB
/[locale]/focus 151.47 kB 🔴 357.31 kB
/[locale]/settings/account 0 B 🟡 205.84 kB
/[locale]/settings 161.46 kB 🔴 367.29 kB
/[locale]/statistics 150.01 kB 🔴 355.84 kB
/[locale]/[...rest] 0 B 🟡 205.84 kB
/[locale]/login 116.84 kB 🟡 322.67 kB
/[locale]/oauth/callback 116.86 kB 🟡 322.70 kB
/[locale]/onboarding 228.99 kB 🔴 434.83 kB
/[locale] 0 B 🟡 205.84 kB

공유 번들: 205.84 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web
URL Perf A11y LCP CLS TBT
/en/home 🔴 57 🟢 95 🔴 15.7s 🟢 0.000 🔴 729ms
/en/today 🔴 67 🟢 95 🔴 15.3s 🟢 0.000 🟡 350ms
/en/focus 🔴 61 🟢 95 🔴 15.2s 🟢 0.000 🟡 550ms
/en/statistics 🔴 68 🟢 95 🔴 15.2s 🟢 0.000 🟡 298ms

Perf ≥ 70 / A11y ≥ 85 목표
LCP 🟢 < 2.5s 🟡 < 4s 🔴 ≥ 4s  |  CLS 🟢 < 0.1 🟡 < 0.25 🔴 ≥ 0.25  |  TBT 🟢 < 200ms 🟡 < 600ms 🔴 ≥ 600ms

Image Optimization — timo-web
파일 크기 포맷 상태
images/google-calendar.png 36.20 kB PNG ⚠️ 🟢
images/google-logo.png 26.79 kB PNG ⚠️ 🟢

총 2개 · 63.00 kB  |  🟢 < 200KB  |  🟡 < 500KB  |  🔴 ≥ 500KB
⚠️ 2개 파일 WebP/AVIF 변환 권장

측정 커밋: a51883f

- 완료된 투두/하위 태스크의 체크박스가 비활성화되어 다시 되돌릴 수 없던 문제를 수정했습니다

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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 win

title/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

📥 Commits

Reviewing files that changed from the base of the PR and between 39a6dc6 and 994540d.

📒 Files selected for processing (7)
  • apps/timo-web/api/todo/todo-schema.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoTaskFields.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsx
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-subtask-field.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-title-field.ts
  • apps/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 yumin-kim2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

공통 유틸 함수로 빼주셔서 재사용할 수 있는 부분이 많겠네요!. (감사합니다 🫰🏻)
로직도 깔끔하게 잘 구현하신 것 같아요 수고하셨씁니다~~!!!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😍

@kimminna
kimminna merged commit 8b39b01 into develop Jul 12, 2026
12 checks passed
@kimminna
kimminna deleted the feat/web/157-home-todo-modal-input-ux branch July 12, 2026 18:15
@kimminna kimminna mentioned this pull request Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 홈 투두 생성 모달 하위 태스크/텍스트 입력 UX 개선

2 participants