[FEAT] 기존 카드 투두 클릭 모달 구현 - #164
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
Walkthrough투두 생성·상세 모달용 입력 컴포넌트와 폼 훅을 추가하고, 공통 선택 컴포넌트에 번역 라벨 주입을 지원합니다. 홈 투두 카드의 클릭·키보드 이벤트를 상세 모달 및 날짜별 삭제 상태 갱신과 연결했습니다. Changes공통 입력 계약과 생성 모달
상세 폼 상태 훅
상세 모달 구성과 입력 UI
홈 카드와 삭제 상태 연결
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant HomeTodoCard
participant HomeTodoContainer
participant DetailTodoModalContainer
participant DetailTodoModalContent
participant useHomeTodosByDate
User->>HomeTodoCard: 카드 클릭 또는 키보드 입력
HomeTodoCard->>HomeTodoContainer: onClickTodo 호출
HomeTodoContainer->>DetailTodoModalContainer: 상세 모달 열기
DetailTodoModalContainer->>DetailTodoModalContent: todo와 삭제 콜백 전달
DetailTodoModalContent->>HomeTodoContainer: onDelete 호출
HomeTodoContainer->>useHomeTodosByDate: handleDeleteTodo 호출
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsx (1)
42-50: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
CreateTodoModalContent.tsx에frequencyHeading을 전달하세요
RepeatSelector의 기본값이"반복 일정"이라 이 호출부는 로케일과 무관하게 한글이 노출됩니다. 다른 호출부처럼t("repeatFrequencyHeading")을 넘기면 됩니다.
TypeScript optional properties: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#optional-properties🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsx` around lines 42 - 50, CreateTodoModalContent의 RepeatSelector 호출부에 로컬라이즈된 frequencyHeading을 전달하세요. 기존 번역 함수 t를 사용해 repeatFrequencyHeading 키의 값을 넘기고, RepeatSelector의 한글 기본값에 의존하지 않도록 수정하세요.apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsx (1)
190-214: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
frequencyHeading을 함께 넘겨주세요
RepeatSelector기본값이"반복 일정"이라, 이 prop이 빠지면 영어 로케일에서도 한글이 그대로 보여요. 이미 있는Home.detailModal.repeatFrequencyHeading을 재사용하면 됩니다. next-intl 네임스페이스 참고: https://next-intl.dev/docs/usage/translations🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsx around lines 190 - 214, Update the repeat configuration passed from CreateTodoModalContent to include frequencyHeading, using the existing Home.detailModal.repeatFrequencyHeading translation so RepeatSelector does not fall back to its Korean default in English locales.
🤖 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-card/HomeTodoCard.tsx:
- Around line 87-101: Update handleCardKeyDown to ignore keyboard events
originating from nested controls, matching handleCardClick’s
target.closest("button, input, label") filtering. Perform this check before
preventDefault and onClickTodo so focused checkboxes and playback controls
retain their native Enter/Space behavior while the card still responds to
keyboard activation elsewhere.
- Around line 134-148: Remove the card-level button semantics and keyboard/click
handling from the sortable container in HomeTodoCard, including role, tabIndex,
onClick, and onKeyDown tied to onClickTodo. Keep the card as a drag/display
container and add or use a separate accessible detail button for onClickTodo,
ensuring Checkbox and PlayButton remain independent controls with distinct roles
and tab order.
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoMemoField.tsx:
- Line 33: DetailTodoMemoField의 textarea에서 outline-none을 제거하고, 키보드 사용자가 포커스 상태를
확인할 수 있도록 프로젝트의 기존 포커스 스타일을 적용하세요.
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoTaskFields.tsx:
- Line 63: Update the textarea class definitions in DetailTodoTaskFields,
including both affected fields, so removing the default outline is accompanied
by a clear keyboard-only focus indicator such as a focus-visible ring or
equivalent accessible focus style; preserve the existing layout and typography
classes.
- Around line 93-103: Update the subtask textarea ref in DetailTodoTaskFields so
it continues registering the element through registerSubtaskInputRef(index) and
also calls resizeTextarea on mount. Preserve the existing change and keydown
handlers while ensuring existing multi-line subtask content is sized correctly
on initial render.
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/DetailTodoModalContent.tsx:
- Around line 66-72: Update the date header in DetailTodoModalContent to derive
the displayed day and weekday from detailTodoForm.date instead of the hardcoded
“22” and Monday label. Reuse the existing weekday mapping, such as
DETAIL_TODO_WEEKDAY_IDS, so the selected todo’s actual date and weekday are
shown.
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.ts:
- Around line 82-83: Initialize selectedTime in useDetailTodoForm to the
DETAIL_TODO_TIME_OPTIONS entry matching todo.durationSeconds converted to
minutes, instead of leaving it undefined. Keep the existing durationText
initialization and ensure the matching option is highlighted when the modal
opens, while preserving undefined only when no option matches.
- Around line 57-65: Replace the hardcoded labels in DETAIL_TODO_WEEKDAYS with
an exported ID-only collection, such as DETAIL_TODO_WEEKDAY_IDS. Update the
consuming weekday mapping in DetailTodoModalContent to call tCommon with each
ID’s Common namespace key (weekday.${id}), preserving the existing weekday order
and IDs.
- Around line 81-117: 연결된 실제 데이터로 상세 폼 초기값을 구성하세요. useDetailTodoForm의 date 상태는
하드코딩된 날짜 대신 상위 컨테이너의 선택 날짜를 인자로 받아 초기화하고, memo는 Todo API 계약의 메모 값으로 설정하세요. 반복 관련
상태인 isRepeatActive, repeatFrequency, selectedWeekdayIds, repeatDay도 todo의 API
필드에서 초기화하도록 Todo 입력 타입과 호출부를 함께 갱신하세요.
---
Outside diff comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsx:
- Around line 190-214: Update the repeat configuration passed from
CreateTodoModalContent to include frequencyHeading, using the existing
Home.detailModal.repeatFrequencyHeading translation so RepeatSelector does not
fall back to its Korean default in English locales.
In
`@packages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsx`:
- Around line 42-50: CreateTodoModalContent의 RepeatSelector 호출부에 로컬라이즈된
frequencyHeading을 전달하세요. 기존 번역 함수 t를 사용해 repeatFrequencyHeading 키의 값을 넘기고,
RepeatSelector의 한글 기본값에 의존하지 않도록 수정하세요.
🪄 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: 23235f6c-d483-4cdd-8ddb-1dd70fcd12e0
📒 Files selected for processing (16)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoMemoField.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoTaskFields.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/TodoIconField.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/DetailTodoModalContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/DetailTodoModalContent.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.tsapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonpackages/timo-design-system/src/components/priority/priority-selector/PrioritySelector.tsxpackages/timo-design-system/src/components/repeat/repeat-selector/RepeatSelector.tsxpackages/timo-design-system/src/components/tag/tag-selector/TagSelector.tsxpackages/timo-design-system/src/components/todo/todo-toolbar/TodoToolbar.tsx
| <div | ||
| ref={setNodeRef} | ||
| style={sortableStyle} | ||
| {...attributes} | ||
| {...listeners} | ||
| role={onClickTodo ? "button" : attributes.role} | ||
| tabIndex={onClickTodo ? 0 : attributes.tabIndex} | ||
| onClick={onClickTodo ? handleCardClick : undefined} | ||
| onKeyDown={onClickTodo ? handleCardKeyDown : undefined} | ||
| className={cn( | ||
| "border-timo-gray-500 flex w-full shrink-0 flex-col items-start gap-2 overflow-hidden rounded-[4px] border border-solid px-3.5 py-3", | ||
| isCompleted ? "bg-timo-gray-200" : "bg-white", | ||
| onClickTodo && "cursor-pointer", | ||
| )} | ||
| > |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the target file and nearby related components
git ls-files 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/*' \
'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/**/*todo-card*' \
'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/**/*TodoCard*'
# Show the target file with line numbers if present
target='apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx'
if [ -f "$target" ]; then
sed -n '1,260p' "$target" | cat -n
fi
# Find keyboard / click handling and interactive descendants in the home todo card area
rg -n --context 3 'role="button"|tabIndex=0|onKeyDown|onClickTodo|checkbox|button|play|aria-label' \
'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home' \
'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components' \
'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers' \
'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks' \
|| trueRepository: Team-Timo/Timo-client
Length of output: 50377
카드 전체를 버튼으로 만들기보다 내부 컨트롤과 분리해 주세요
role="button"/tabIndex=0를 카드 전체에 주면 Checkbox와 PlayButton이 같은 인터랙션 트리 안에 들어가 탭 순서와 역할이 겹칩니다. WAI-ARIA APG도 이런 중첩 패턴은 피하라고 권장합니다: https://www.w3.org/WAI/ARIA/apg/
상세 진입이 필요하면 카드 자체는 드래그/표시용으로 두고, 별도의 상세보기 버튼으로 분리하는 쪽이 안전합니다. 버튼이 많은 카드일수록 역할 구분이 또렷해야 합니다. 😄
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx
around lines 134 - 148, Remove the card-level button semantics and
keyboard/click handling from the sortable container in HomeTodoCard, including
role, tabIndex, onClick, and onKeyDown tied to onClickTodo. Keep the card as a
drag/display container and add or use a separate accessible detail button for
onClickTodo, ensuring Checkbox and PlayButton remain independent controls with
distinct roles and tab order.
| placeholder={placeholder} | ||
| maxLength={maxLength} | ||
| onChange={(event) => onChange(event.target.value)} | ||
| className="typo-body-r-14 text-timo-black placeholder:text-timo-gray-700 min-h-20 w-full resize-none overflow-hidden p-1 outline-none" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
outline-none으로 포커스 테두리가 제거되었습니다.
메모 textarea도 대체 포커스 스타일 없이 outline-none만 적용되어 있어 키보드 사용자가 포커스 위치를 알기 어렵습니다.
♿ 제안 diff
- className="typo-body-r-14 text-timo-black placeholder:text-timo-gray-700 min-h-20 w-full resize-none overflow-hidden p-1 outline-none"
+ className="typo-body-r-14 text-timo-black placeholder:text-timo-gray-700 min-h-20 w-full resize-none overflow-hidden p-1 outline-none focus-visible:ring-1 focus-visible:ring-timo-gray-500"📝 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.
| className="typo-body-r-14 text-timo-black placeholder:text-timo-gray-700 min-h-20 w-full resize-none overflow-hidden p-1 outline-none" | |
| className="typo-body-r-14 text-timo-black placeholder:text-timo-gray-700 min-h-20 w-full resize-none overflow-hidden p-1 outline-none focus-visible:ring-1 focus-visible:ring-timo-gray-500" |
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoMemoField.tsx
at line 33, DetailTodoMemoField의 textarea에서 outline-none을 제거하고, 키보드 사용자가 포커스 상태를
확인할 수 있도록 프로젝트의 기존 포커스 스타일을 적용하세요.
Source: Path instructions
| resizeTextarea(event.currentTarget); | ||
| }} | ||
| rows={1} | ||
| className="typo-headline-b-14 text-timo-black min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
outline-none으로 포커스 테두리가 제거되어 키보드 접근성이 저하됩니다.
두 textarea 모두 outline-none만 적용되어 있고 대체 포커스 스타일이 없어서, 키보드로 탐색하는 사용자는 현재 포커스가 어디에 있는지 확인할 수 없습니다. outline-none을 쓸 경우엔 focus-visible:ring 등 대체 표시를 꼭 넣어주는 게 좋아요. 관련해서 Tailwind 공식 문서에서도 "본인만의 포커스 스타일을 적용하는 것을 강력히 권장"하고 있습니다.
♿ 제안 diff
- className="typo-headline-b-14 text-timo-black min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none"
+ className="typo-headline-b-14 text-timo-black min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none focus-visible:ring-1 focus-visible:ring-timo-gray-500"Also applies to: 102-102
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoTaskFields.tsx
at line 63, Update the textarea class definitions in DetailTodoTaskFields,
including both affected fields, so removing the default outline is accompanied
by a clear keyboard-only focus indicator such as a focus-visible ring or
equivalent accessible focus style; preserve the existing layout and typography
classes.
Source: Path instructions
| <textarea | ||
| value={subtask.value} | ||
| ref={registerSubtaskInputRef(index)} | ||
| onChange={(event) => { | ||
| onSubtaskInputChange(subtask.id, event.target.value); | ||
| resizeTextarea(event.currentTarget); | ||
| }} | ||
| onKeyDown={(event) => onSubtaskInputKeyDown(index, event)} | ||
| rows={1} | ||
| className="typo-body-r-12 text-timo-gray-700 min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none" | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
서브태스크 textarea는 최초 렌더링 시 높이가 자동으로 조절되지 않습니다.
제목 textarea(Line 57)는 ref={resizeTextarea}라서 마운트 시 한 번 리사이즈되지만, 서브태스크는 registerSubtaskInputRef(index)만 연결되어 있어 resizeTextarea가 호출되지 않습니다. 기존 투두를 열었을 때 서브태스크 내용이 길어서 여러 줄이면, 사용자가 타이핑하기 전까지 1행 높이로 잘려 보이는 문제가 생겨요.
ref 콜백에서 resizeTextarea도 함께 호출해주면 해결됩니다.
📏 제안 diff
<textarea
value={subtask.value}
- ref={registerSubtaskInputRef(index)}
+ ref={(element) => {
+ registerSubtaskInputRef(index)(element);
+ resizeTextarea(element);
+ }}
onChange={(event) => {📝 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.
| <textarea | |
| value={subtask.value} | |
| ref={registerSubtaskInputRef(index)} | |
| onChange={(event) => { | |
| onSubtaskInputChange(subtask.id, event.target.value); | |
| resizeTextarea(event.currentTarget); | |
| }} | |
| onKeyDown={(event) => onSubtaskInputKeyDown(index, event)} | |
| rows={1} | |
| className="typo-body-r-12 text-timo-gray-700 min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none" | |
| /> | |
| <textarea | |
| value={subtask.value} | |
| ref={(element) => { | |
| registerSubtaskInputRef(index)(element); | |
| resizeTextarea(element); | |
| }} | |
| onChange={(event) => { | |
| onSubtaskInputChange(subtask.id, event.target.value); | |
| resizeTextarea(event.currentTarget); | |
| }} | |
| onKeyDown={(event) => onSubtaskInputKeyDown(index, event)} | |
| rows={1} | |
| className="typo-body-r-12 text-timo-gray-700 min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none" | |
| /> |
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoTaskFields.tsx
around lines 93 - 103, Update the subtask textarea ref in DetailTodoTaskFields
so it continues registering the element through registerSubtaskInputRef(index)
and also calls resizeTextarea on mount. Preserve the existing change and keydown
handlers while ensuring existing multi-line subtask content is sized correctly
on initial render.
| <div className="flex w-full flex-col gap-2"> | ||
| <div className="flex flex-col"> | ||
| <p className="typo-headline-b-30 text-timo-black">22</p> | ||
| <p className="typo-body-m-12 text-timo-gray-700"> | ||
| {tCommon("weekday.MON")} | ||
| </p> | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
날짜 표시가 실제 todo 정보와 무관하게 항상 "22일 월요일"로 고정되어 있습니다.
detailTodoForm.date가 훅에 이미 존재하는데도, 상단 날짜 영역은 22와 tCommon("weekday.MON")을 그대로 하드코딩해서 보여주고 있어요. 어떤 투두를 열어도 같은 날짜/요일이 뜨는 셈이라, "선택한 투두의 제목, 날짜, 시간 등 기존 정보 표시"라는 PR 목표와 바로 충돌하는 부분입니다.
detailTodoForm.date에서 일(day)과 요일을 계산해 넣어주면 좋겠습니다(요일 매핑은 아래 use-detail-todo-form.ts 리뷰에서 제안한 DETAIL_TODO_WEEKDAY_IDS와 연계 가능).
📅 제안 diff
+ const weekdayIds = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
...
<div className="flex flex-col">
- <p className="typo-headline-b-30 text-timo-black">22</p>
+ <p className="typo-headline-b-30 text-timo-black">
+ {detailTodoForm.date.getDate()}
+ </p>
<p className="typo-body-m-12 text-timo-gray-700">
- {tCommon("weekday.MON")}
+ {tCommon(`weekday.${weekdayIds[detailTodoForm.date.getDay()]}`)}
</p>
</div>📝 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.
| <div className="flex w-full flex-col gap-2"> | |
| <div className="flex flex-col"> | |
| <p className="typo-headline-b-30 text-timo-black">22</p> | |
| <p className="typo-body-m-12 text-timo-gray-700"> | |
| {tCommon("weekday.MON")} | |
| </p> | |
| </div> | |
| const weekdayIds = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; | |
| ... | |
| <div className="flex w-full flex-col gap-2"> | |
| <div className="flex flex-col"> | |
| <p className="typo-headline-b-30 text-timo-black"> | |
| {detailTodoForm.date.getDate()} | |
| </p> | |
| <p className="typo-body-m-12 text-timo-gray-700"> | |
| {tCommon(`weekday.${weekdayIds[detailTodoForm.date.getDay()]}`)} | |
| </p> | |
| </div> |
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/DetailTodoModalContent.tsx
around lines 66 - 72, Update the date header in DetailTodoModalContent to derive
the displayed day and weekday from detailTodoForm.date instead of the hardcoded
“22” and Monday label. Reuse the existing weekday mapping, such as
DETAIL_TODO_WEEKDAY_IDS, so the selected todo’s actual date and weekday are
shown.
| export const DETAIL_TODO_WEEKDAYS = [ | ||
| { id: "MON", label: "월" }, | ||
| { id: "TUE", label: "화" }, | ||
| { id: "WED", label: "수" }, | ||
| { id: "THU", label: "목" }, | ||
| { id: "FRI", label: "금" }, | ||
| { id: "SAT", label: "토" }, | ||
| { id: "SUN", label: "일" }, | ||
| ]; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
요일 라벨이 한글로 하드코딩되어 있어 다국어 지원이 깨집니다.
DETAIL_TODO_WEEKDAYS의 label이 "월", "화"... 로 고정되어 있어, 영어 로케일에서도 한글 요일이 노출됩니다. 반면 같은 파일 세트의 DetailTodoModalContent.tsx에서는 이미 tCommon("weekday.MON")으로 Common 네임스페이스의 다국어 키를 쓰고 있으니(apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/DetailTodoModalContent.tsx Line 70), 동일한 키를 재사용하는 편이 일관성 있습니다.
id 배열만 export하고, 라벨은 소비하는 쪽에서 tCommon으로 매핑하는 방식을 제안합니다.
🌐 제안 diff
-export const DETAIL_TODO_WEEKDAYS = [
- { id: "MON", label: "월" },
- { id: "TUE", label: "화" },
- { id: "WED", label: "수" },
- { id: "THU", label: "목" },
- { id: "FRI", label: "금" },
- { id: "SAT", label: "토" },
- { id: "SUN", label: "일" },
-];
+export const DETAIL_TODO_WEEKDAY_IDS = [
+ "MON",
+ "TUE",
+ "WED",
+ "THU",
+ "FRI",
+ "SAT",
+ "SUN",
+] as const;소비 측(DetailTodoModalContent.tsx)에서는:
weekly: {
weekdays: DETAIL_TODO_WEEKDAY_IDS.map((id) => ({
id,
label: tCommon(`weekday.${id}`),
})),
...
}📝 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.
| export const DETAIL_TODO_WEEKDAYS = [ | |
| { id: "MON", label: "월" }, | |
| { id: "TUE", label: "화" }, | |
| { id: "WED", label: "수" }, | |
| { id: "THU", label: "목" }, | |
| { id: "FRI", label: "금" }, | |
| { id: "SAT", label: "토" }, | |
| { id: "SUN", label: "일" }, | |
| ]; | |
| export const DETAIL_TODO_WEEKDAY_IDS = [ | |
| "MON", | |
| "TUE", | |
| "WED", | |
| "THU", | |
| "FRI", | |
| "SAT", | |
| "SUN", | |
| ] as const; |
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.ts
around lines 57 - 65, Replace the hardcoded labels in DETAIL_TODO_WEEKDAYS with
an exported ID-only collection, such as DETAIL_TODO_WEEKDAY_IDS. Update the
consuming weekday mapping in DetailTodoModalContent to call tCommon with each
ID’s Common namespace key (weekday.${id}), preserving the existing weekday order
and IDs.
| const [date, setDate] = useState(new Date(2026, 6, 1)); | ||
| const [time, setTime] = useState(durationText); | ||
| const [selectedTime, setSelectedTime] = useState<TimeSelection>(); | ||
| const [priority, setPriority] = useState<PriorityLevel>(todo.priority); | ||
| const [selectedTag, setSelectedTag] = useState(tagLabel); | ||
| const [isRepeatActive, setIsRepeatActive] = useState(todo.isRepeated); | ||
| const [repeatFrequency, setRepeatFrequency] = | ||
| useState<RepeatFrequency>("DAILY"); | ||
| const [selectedWeekdayIds, setSelectedWeekdayIds] = useState<string[]>([]); | ||
| const [repeatDay, setRepeatDay] = useState(""); | ||
| const [isCompleted, setIsCompleted] = useState(todo.completed); | ||
| const [title, setTitle] = useState(todo.title); | ||
| const nextSubtaskInputId = useRef(0); | ||
| const createSubtaskInput = (value = ""): DetailTodoSubtaskInput => ({ | ||
| id: nextSubtaskInputId.current++, | ||
| subtaskId: null, | ||
| completed: false, | ||
| value, | ||
| }); | ||
| const [subtaskInputs, setSubtaskInputs] = useState<DetailTodoSubtaskInput[]>( | ||
| () => | ||
| todo.subtasks.length > 0 | ||
| ? todo.subtasks.map((subtask) => ({ | ||
| id: nextSubtaskInputId.current++, | ||
| subtaskId: subtask.subtaskId, | ||
| completed: subtask.completed, | ||
| value: subtask.content, | ||
| })) | ||
| : [createSubtaskInput()], | ||
| ); | ||
| const subtaskInputRefs = useRef<Array<HTMLTextAreaElement | null>>([]); | ||
| const pendingSubtaskFocusIndex = useRef<number | null>(null); | ||
| const [memo, setMemo] = useState(""); | ||
| const [icon, setIcon] = useState<TodoIconValue | null>( | ||
| isTodoIconValue(todo.icon) ? todo.icon : null, | ||
| ); | ||
| const [isIconPanelOpen, setIsIconPanelOpen] = useState(false); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Todo 타입 정의를 찾아 date/memo 관련 필드 존재 여부 확인
fd todo-type.ts --exec cat {}Repository: Team-Timo/Timo-client
Length of output: 1241
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the hook and related modal usage around the referenced lines
printf '\n## use-detail-todo-form.ts\n'
sed -n '1,260p' apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.ts
printf '\n## DetailTodoModalContent.tsx\n'
sed -n '1,220p' apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoModalContent.tsx
printf '\n## Search for hasMemo / memo usage\n'
rg -n "hasMemo|memo" apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home -g '!**/*.map'Repository: Team-Timo/Timo-client
Length of output: 240
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the hook and related modal usage around the referenced lines
printf '\n## use-detail-todo-form.ts\n'
sed -n '1,260p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.ts'
printf '\n## DetailTodoModalContent.tsx\n'
sed -n '1,220p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/DetailTodoModalContent.tsx'
printf '\n## Search for hasMemo / memo usage\n'
rg -n "hasMemo|memo" 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home' -g '!**/*.map'Repository: Team-Timo/Timo-client
Length of output: 8148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the modal component and any memo/date-related wiring
fd 'DetailTodoModalContent.tsx' apps/timo-web/app
printf '\n## search results\n'
rg -n "useDetailTodoForm|setMemo|memo|hasMemo|selectedTime|repeatFrequency|selectedWeekdayIds|repeatDay|new Date\\(2026, 6, 1\\)" 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home' -g '!**/*.map'Repository: Team-Timo/Timo-client
Length of output: 7817
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the detail modal container and the related create-time hook for initialization patterns
printf '\n## DetailTodoModalContent.tsx\n'
sed -n '1,240p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/DetailTodoModalContent.tsx'
printf '\n## use-time-field.ts\n'
sed -n '1,220p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field.ts'
printf '\n## use-repeat-field.ts\n'
sed -n '1,220p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field.ts'
printf '\n## use-detail-todo-form.ts setDate usage\n'
rg -n "setDate|date," 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.ts'Repository: Team-Timo/Timo-client
Length of output: 10102
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check whether the detail modal persists memo/date/repeat data anywhere else
rg -n "useDetailTodoForm|DetailTodoMemoField|onChange={detailTodoForm.setMemo}|selectedTime|repeatDayOfMonth|repeatWeekdays|repeatType|hasMemo" 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home' -g '!**/*.map'
printf '\n## todo schema\n'
sed -n '1,220p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type.ts'Repository: Team-Timo/Timo-client
Length of output: 425
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for any repeat/date fields on the todo model or in detail modal save paths
printf '\n## todo / detail modal search\n'
rg -n "createdAt|date|memo|repeatType|repeatWeekdays|repeatDayOfMonth|durationSeconds|hasMemo" 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home' -g '!**/*.map'
printf '\n## todo type\n'
sed -n '1,220p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type.ts'Repository: Team-Timo/Timo-client
Length of output: 23691
상세 모달 초기값을 실제 데이터 소스와 연결해 주세요.
Todo 타입에는 date/memo가 없어서 useDetailTodoForm이 new Date(2026, 6, 1)과 "" 같은 placeholder로 시작합니다. 지금 상태로는 날짜가 항상 고정값이고, 메모/반복 값도 기존 정보를 보여주지 못합니다. 날짜는 상위 컨테이너의 선택 날짜를 넘기고, 메모와 반복 상태는 API 계약에 포함된 값으로 초기화해야 합니다.
React useState 참고: https://react.dev/reference/react/useState
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.ts
around lines 81 - 117, 연결된 실제 데이터로 상세 폼 초기값을 구성하세요. useDetailTodoForm의 date 상태는
하드코딩된 날짜 대신 상위 컨테이너의 선택 날짜를 인자로 받아 초기화하고, memo는 Todo API 계약의 메모 값으로 설정하세요. 반복 관련
상태인 isRepeatActive, repeatFrequency, selectedWeekdayIds, repeatDay도 todo의 API
필드에서 초기화하도록 Todo 입력 타입과 호출부를 함께 갱신하세요.
| const [time, setTime] = useState(durationText); | ||
| const [selectedTime, setSelectedTime] = useState<TimeSelection>(); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
selectedTime 초기값이 기존 소요 시간과 매칭되지 않습니다.
time은 convertDurationToTimeText(todo.durationSeconds)로 텍스트만 채워지지만, selectedTime(Line 83)은 undefined로 시작해 DETAIL_TODO_TIME_OPTIONS 중 어떤 항목도 선택 표시되지 않습니다. 모달을 처음 열었을 때 시간 텍스트는 맞는데 선택된 옵션 하이라이트만 비어있는 시각적 불일치가 생겨요.
todo.durationSeconds(분 단위 환산)로 매칭되는 옵션을 찾아 초기값으로 넣어주면 좋겠습니다.
⏱️ 제안 diff
- const [selectedTime, setSelectedTime] = useState<TimeSelection>();
+ const initialMinute = Math.round(todo.durationSeconds / 60);
+ const [selectedTime, setSelectedTime] = useState<TimeSelection | undefined>(
+ DETAIL_TODO_TIME_OPTIONS.find((option) => option.minute === initialMinute)
+ ?.minute,
+ );Also applies to: 197-209
🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-detail-todo-form.ts
around lines 82 - 83, Initialize selectedTime in useDetailTodoForm to the
DETAIL_TODO_TIME_OPTIONS entry matching todo.durationSeconds converted to
minutes, instead of leaving it undefined. Keep the existing durationText
initialization and ensure the matching option is highlighted when the modal
opens, while preserving undefined only when no option matches.
kimminna
left a comment
There was a problem hiding this comment.
고생했어요~ 코멘트 확인해서 수정해 주세용
전체적으로 모달과 관련된 컴포넌트 & 컨테이너들을 전체 공용 폴더로 분리하는 작업 진행하고, 공용 유틸 손보는 작업도 필요할 것 같습니다!
|
폴더 구조에 관한건 PR에 작성해뒀으니 한 번씩 읽어주세용 ~! |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts (1)
109-115: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
handleDeleteTodo가 아직 서버에 반영되지 않아요 (TODO 인지 중).로컬 상태만 갱신하고 있어서 새로고침하면 삭제된 투두가 다시 보일 거예요. 다른 핸들러들처럼 mutation +
onSuccess: invalidateHomeView+onError롤백 패턴을 적용하면 일관성 있게 마무리될 것 같습니다. 삭제 API가 준비되면 제가 mutation 배선 초안을 도와드려도 될까요, 아니면 별도 이슈로 추적해 둘까요?🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts around lines 109 - 115, Update handleDeleteTodo to persist deletion through the available delete mutation rather than only modifying local state. Follow the existing handlers’ mutation pattern, including invalidateHomeView in onSuccess and restoring the previous todosByDate state in onError, while preserving the current dateKey/todoId targeting.apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx (1)
74-79: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win상세 모달 헤더 날짜를 선택된 TODO 값으로 바꿔 주세요
apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx:74-79의22/weekday.MON이 하드코딩되어 있어, 어떤 TODO를 열어도 상단 날짜가 같습니다.TodoToolbar처럼detailTodoForm.date를 사용해 날짜와 요일을 렌더링하면 됩니다.Date/Intl.DateTimeFormat문서도 같이 보면 좋아요. 😉🤖 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/components/todo-modal/detail/DetailTodoModalContent.tsx` around lines 74 - 79, Update the date header in DetailTodoModalContent to derive the displayed day and weekday from detailTodoForm.date instead of the hardcoded “22” and weekday.MON values. Follow the existing TodoToolbar date-rendering approach, using Date/Intl.DateTimeFormat while preserving the current styling and translation behavior.apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts (2)
231-242: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winBackspace 분기가 최신 상태 대신 클로저로 캡처된
subtaskInputs를 직접 참조합니다.바로 위 Enter 분기(217-227)는
setSubtaskInputs((prev) => ...)함수형 업데이트로 최신 상태를 보장하는데, Backspace 분기는 렌더 시점에 캡처된subtaskInputs를 그대로removeSubtaskInputOnBackspace에 넘기고 있어 비일관적입니다. 같은 이벤트 루프 내에서 상태 갱신이 겹치는 경우 stale 데이터로 잘못된 항목이 삭제될 위험이 있습니다.하단 컨솔리데이션 코멘트에서
use-subtask-field.ts의 동일 패턴과 함께 다뤘습니다.🤖 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/hooks/todo-modal/use-detail-todo-form.ts` around lines 231 - 242, Update the Backspace branch in the subtask input handler to use a functional setSubtaskInputs update, passing the previous state to removeSubtaskInputOnBackspace instead of the closure-captured subtaskInputs. Preserve the existing focusIndex handling, preventDefault behavior, and pendingSubtaskFocusIndex update.
139-176: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift서브태스크 입력 관리 로직이
use-subtask-field.ts와 거의 동일하게 중복 구현되어 있습니다.ref 배열/포커스 이동
useEffect/Enter·Backspace 처리 흐름이use-subtask-field.ts와 구조적으로 동일합니다.DetailTodoSubtaskInput이subtaskId/completed를 추가로 갖는다는 차이는 있지만, 제네릭 타입 파라미터로 처리 가능한 범위입니다. 두 파일을 하나의 공용 훅으로 통합하면 향후 버그 수정을 한 곳에서만 하면 되어 유지보수 부담이 줄어듭니다.하단 컨솔리데이션 코멘트에서
use-subtask-field.ts와 함께 다뤘습니다.Also applies to: 189-243
🤖 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/hooks/todo-modal/use-detail-todo-form.ts` around lines 139 - 176, Consolidate the duplicated subtask input management in the detail todo form with the shared logic from use-subtask-field.ts, including ref tracking, pending focus handling, and Enter/Backspace behavior. Extend the shared hook with a generic input type or configuration so DetailTodoSubtaskInput retains its subtaskId and completed fields while both consumers use the same implementation.
🤖 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/components/todo-modal/common/TodoIconField.tsx`:
- Around line 28-36: Update TodoIconField’s selected-icon button to use a
separate state-appropriate accessibility label instead of addIconLabel. Add or
reuse a prop for the existing-icon action (such as changing or removing the
icon), pass it to the button’s aria-label when icon is present, and preserve
addIconLabel for the no-icon add action.
In `@apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx`:
- Around line 21-26: CreateTodoMemoField의 textarea 높이 조정 로직에 value 변경을 감시하는
useEffect를 추가하세요. 외부에서 value가 리셋되거나 프로그래밍 방식으로 변경될 때 resizeTextarea를 호출해 높이를 다시
계산하고, 기존 ref 및 onInput 동작은 유지하세요.
- Around line 3-7: Extract the duplicated resizeTextarea logic into a shared
helper under the todo-modal common utilities, export it, and update
CreateTodoMemoField, CreateTodoTaskFields, and DetailTodoTaskFields to import
and reuse that helper instead of defining local copies.
In `@apps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsx`:
- Line 41: Update the Checkbox usage in CreateTodoTaskFields so the fixed
unchecked state is explicitly non-interactive by adding its disabled handling.
Preserve the default unchecked appearance while preventing clicks, keyboard
interaction, and misleading hover/cursor behavior.
- Line 51: Update both textarea className declarations in CreateTodoTaskFields
to preserve a visible keyboard focus indicator: remove outline-none or replace
it with the design-system’s focus-visible ring styling, using the established
timo-blue-300 ring pattern. Apply the same treatment to both fields identified
by the textarea className usages.
In `@apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts`:
- Around line 88-103: Update the useDetailTodoForm default-value flow to source
date, repeat settings, and memo from TodoDetailResponse or explicitly injected
detail fields instead of fixed DAILY, empty, and current-date values. Preserve
existing todo-derived fields, and call react-hook-form reset with the newly
computed defaults when the modal receives a different todo/detail response.
In `@apps/timo-web/hooks/todo-modal/use-subtask-field.ts`:
- Around line 88-99: Update the Backspace handling in the subtask input handler
to use a functional setSubtaskInputs update, deriving entries and focusIndex
from the latest inputs state rather than the closure-captured subtaskInputs;
preserve the existing preventDefault and pendingFocusIndex behavior.
- Around line 1-114: Consolidate the duplicated subtask state and
keyboard-handling logic between useSubtaskField and use-detail-todo-form.ts.
Reuse a shared hook or extracted utility for createEntry, synchronization, input
changes, Enter/Backspace handling, focus management, and reset, while preserving
the existing subtask behavior and MAX_SUBTASK_COUNT constraint.
---
Outside diff comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts:
- Around line 109-115: Update handleDeleteTodo to persist deletion through the
available delete mutation rather than only modifying local state. Follow the
existing handlers’ mutation pattern, including invalidateHomeView in onSuccess
and restoring the previous todosByDate state in onError, while preserving the
current dateKey/todoId targeting.
In `@apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx`:
- Around line 74-79: Update the date header in DetailTodoModalContent to derive
the displayed day and weekday from detailTodoForm.date instead of the hardcoded
“22” and weekday.MON values. Follow the existing TodoToolbar date-rendering
approach, using Date/Intl.DateTimeFormat while preserving the current styling
and translation behavior.
In `@apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts`:
- Around line 231-242: Update the Backspace branch in the subtask input handler
to use a functional setSubtaskInputs update, passing the previous state to
removeSubtaskInputOnBackspace instead of the closure-captured subtaskInputs.
Preserve the existing focusIndex handling, preventDefault behavior, and
pendingSubtaskFocusIndex update.
- Around line 139-176: Consolidate the duplicated subtask input management in
the detail todo form with the shared logic from use-subtask-field.ts, including
ref tracking, pending focus handling, and Enter/Backspace behavior. Extend the
shared hook with a generic input type or configuration so DetailTodoSubtaskInput
retains its subtaskId and completed fields while both consumers use the same
implementation.
🪄 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: bb0acbd4-6a1c-40fb-aa2b-cc1a788fca91
📒 Files selected for processing (24)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-card/HomeDayHeaderContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/todo-modal/CreateTodoModalContainer.tsxapps/timo-web/components/todo-modal/common/TodoIconField.tsxapps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsxapps/timo-web/components/todo-modal/create/CreateTodoModalContainer.tsxapps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsxapps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsxapps/timo-web/components/todo-modal/detail/DetailTodoMemoField.tsxapps/timo-web/components/todo-modal/detail/DetailTodoModalContainer.tsxapps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsxapps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsxapps/timo-web/hooks/todo-modal/use-create-todo-submit.tsapps/timo-web/hooks/todo-modal/use-detail-todo-form.tsapps/timo-web/hooks/todo-modal/use-subtask-field.tsapps/timo-web/hooks/todo-modal/use-title-field.tsapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonapps/timo-web/utils/todo/tag-label.tsapps/timo-web/utils/todo/text-length.tsapps/timo-web/utils/todo/todo-time.tspackages/timo-design-system/src/components/todo/todo-toolbar/TodoToolbar.tsx
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts (1)
109-115: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
handleDeleteTodo가 아직 서버에 반영되지 않아요 (TODO 인지 중).로컬 상태만 갱신하고 있어서 새로고침하면 삭제된 투두가 다시 보일 거예요. 다른 핸들러들처럼 mutation +
onSuccess: invalidateHomeView+onError롤백 패턴을 적용하면 일관성 있게 마무리될 것 같습니다. 삭제 API가 준비되면 제가 mutation 배선 초안을 도와드려도 될까요, 아니면 별도 이슈로 추적해 둘까요?🤖 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/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts around lines 109 - 115, Update handleDeleteTodo to persist deletion through the available delete mutation rather than only modifying local state. Follow the existing handlers’ mutation pattern, including invalidateHomeView in onSuccess and restoring the previous todosByDate state in onError, while preserving the current dateKey/todoId targeting.apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx (1)
74-79: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win상세 모달 헤더 날짜를 선택된 TODO 값으로 바꿔 주세요
apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx:74-79의22/weekday.MON이 하드코딩되어 있어, 어떤 TODO를 열어도 상단 날짜가 같습니다.TodoToolbar처럼detailTodoForm.date를 사용해 날짜와 요일을 렌더링하면 됩니다.Date/Intl.DateTimeFormat문서도 같이 보면 좋아요. 😉🤖 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/components/todo-modal/detail/DetailTodoModalContent.tsx` around lines 74 - 79, Update the date header in DetailTodoModalContent to derive the displayed day and weekday from detailTodoForm.date instead of the hardcoded “22” and weekday.MON values. Follow the existing TodoToolbar date-rendering approach, using Date/Intl.DateTimeFormat while preserving the current styling and translation behavior.apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts (2)
231-242: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winBackspace 분기가 최신 상태 대신 클로저로 캡처된
subtaskInputs를 직접 참조합니다.바로 위 Enter 분기(217-227)는
setSubtaskInputs((prev) => ...)함수형 업데이트로 최신 상태를 보장하는데, Backspace 분기는 렌더 시점에 캡처된subtaskInputs를 그대로removeSubtaskInputOnBackspace에 넘기고 있어 비일관적입니다. 같은 이벤트 루프 내에서 상태 갱신이 겹치는 경우 stale 데이터로 잘못된 항목이 삭제될 위험이 있습니다.하단 컨솔리데이션 코멘트에서
use-subtask-field.ts의 동일 패턴과 함께 다뤘습니다.🤖 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/hooks/todo-modal/use-detail-todo-form.ts` around lines 231 - 242, Update the Backspace branch in the subtask input handler to use a functional setSubtaskInputs update, passing the previous state to removeSubtaskInputOnBackspace instead of the closure-captured subtaskInputs. Preserve the existing focusIndex handling, preventDefault behavior, and pendingSubtaskFocusIndex update.
139-176: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift서브태스크 입력 관리 로직이
use-subtask-field.ts와 거의 동일하게 중복 구현되어 있습니다.ref 배열/포커스 이동
useEffect/Enter·Backspace 처리 흐름이use-subtask-field.ts와 구조적으로 동일합니다.DetailTodoSubtaskInput이subtaskId/completed를 추가로 갖는다는 차이는 있지만, 제네릭 타입 파라미터로 처리 가능한 범위입니다. 두 파일을 하나의 공용 훅으로 통합하면 향후 버그 수정을 한 곳에서만 하면 되어 유지보수 부담이 줄어듭니다.하단 컨솔리데이션 코멘트에서
use-subtask-field.ts와 함께 다뤘습니다.Also applies to: 189-243
🤖 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/hooks/todo-modal/use-detail-todo-form.ts` around lines 139 - 176, Consolidate the duplicated subtask input management in the detail todo form with the shared logic from use-subtask-field.ts, including ref tracking, pending focus handling, and Enter/Backspace behavior. Extend the shared hook with a generic input type or configuration so DetailTodoSubtaskInput retains its subtaskId and completed fields while both consumers use the same implementation.
🤖 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/components/todo-modal/common/TodoIconField.tsx`:
- Around line 28-36: Update TodoIconField’s selected-icon button to use a
separate state-appropriate accessibility label instead of addIconLabel. Add or
reuse a prop for the existing-icon action (such as changing or removing the
icon), pass it to the button’s aria-label when icon is present, and preserve
addIconLabel for the no-icon add action.
In `@apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx`:
- Around line 21-26: CreateTodoMemoField의 textarea 높이 조정 로직에 value 변경을 감시하는
useEffect를 추가하세요. 외부에서 value가 리셋되거나 프로그래밍 방식으로 변경될 때 resizeTextarea를 호출해 높이를 다시
계산하고, 기존 ref 및 onInput 동작은 유지하세요.
- Around line 3-7: Extract the duplicated resizeTextarea logic into a shared
helper under the todo-modal common utilities, export it, and update
CreateTodoMemoField, CreateTodoTaskFields, and DetailTodoTaskFields to import
and reuse that helper instead of defining local copies.
In `@apps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsx`:
- Line 41: Update the Checkbox usage in CreateTodoTaskFields so the fixed
unchecked state is explicitly non-interactive by adding its disabled handling.
Preserve the default unchecked appearance while preventing clicks, keyboard
interaction, and misleading hover/cursor behavior.
- Line 51: Update both textarea className declarations in CreateTodoTaskFields
to preserve a visible keyboard focus indicator: remove outline-none or replace
it with the design-system’s focus-visible ring styling, using the established
timo-blue-300 ring pattern. Apply the same treatment to both fields identified
by the textarea className usages.
In `@apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts`:
- Around line 88-103: Update the useDetailTodoForm default-value flow to source
date, repeat settings, and memo from TodoDetailResponse or explicitly injected
detail fields instead of fixed DAILY, empty, and current-date values. Preserve
existing todo-derived fields, and call react-hook-form reset with the newly
computed defaults when the modal receives a different todo/detail response.
In `@apps/timo-web/hooks/todo-modal/use-subtask-field.ts`:
- Around line 88-99: Update the Backspace handling in the subtask input handler
to use a functional setSubtaskInputs update, deriving entries and focusIndex
from the latest inputs state rather than the closure-captured subtaskInputs;
preserve the existing preventDefault and pendingFocusIndex behavior.
- Around line 1-114: Consolidate the duplicated subtask state and
keyboard-handling logic between useSubtaskField and use-detail-todo-form.ts.
Reuse a shared hook or extracted utility for createEntry, synchronization, input
changes, Enter/Backspace handling, focus management, and reset, while preserving
the existing subtask behavior and MAX_SUBTASK_COUNT constraint.
---
Outside diff comments:
In
`@apps/timo-web/app/`[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts:
- Around line 109-115: Update handleDeleteTodo to persist deletion through the
available delete mutation rather than only modifying local state. Follow the
existing handlers’ mutation pattern, including invalidateHomeView in onSuccess
and restoring the previous todosByDate state in onError, while preserving the
current dateKey/todoId targeting.
In `@apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx`:
- Around line 74-79: Update the date header in DetailTodoModalContent to derive
the displayed day and weekday from detailTodoForm.date instead of the hardcoded
“22” and weekday.MON values. Follow the existing TodoToolbar date-rendering
approach, using Date/Intl.DateTimeFormat while preserving the current styling
and translation behavior.
In `@apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts`:
- Around line 231-242: Update the Backspace branch in the subtask input handler
to use a functional setSubtaskInputs update, passing the previous state to
removeSubtaskInputOnBackspace instead of the closure-captured subtaskInputs.
Preserve the existing focusIndex handling, preventDefault behavior, and
pendingSubtaskFocusIndex update.
- Around line 139-176: Consolidate the duplicated subtask input management in
the detail todo form with the shared logic from use-subtask-field.ts, including
ref tracking, pending focus handling, and Enter/Backspace behavior. Extend the
shared hook with a generic input type or configuration so DetailTodoSubtaskInput
retains its subtaskId and completed fields while both consumers use the same
implementation.
🪄 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: bb0acbd4-6a1c-40fb-aa2b-cc1a788fca91
📒 Files selected for processing (24)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-card/HomeDayHeaderContainer.tsxapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.tsapps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/todo-modal/CreateTodoModalContainer.tsxapps/timo-web/components/todo-modal/common/TodoIconField.tsxapps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsxapps/timo-web/components/todo-modal/create/CreateTodoModalContainer.tsxapps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsxapps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsxapps/timo-web/components/todo-modal/detail/DetailTodoMemoField.tsxapps/timo-web/components/todo-modal/detail/DetailTodoModalContainer.tsxapps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsxapps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsxapps/timo-web/hooks/todo-modal/use-create-todo-submit.tsapps/timo-web/hooks/todo-modal/use-detail-todo-form.tsapps/timo-web/hooks/todo-modal/use-subtask-field.tsapps/timo-web/hooks/todo-modal/use-title-field.tsapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonapps/timo-web/utils/todo/tag-label.tsapps/timo-web/utils/todo/text-length.tsapps/timo-web/utils/todo/todo-time.tspackages/timo-design-system/src/components/todo/todo-toolbar/TodoToolbar.tsx
🛑 Comments failed to post (8)
apps/timo-web/components/todo-modal/common/TodoIconField.tsx (1)
28-36: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
아이콘이 이미 있을 때도 "추가" 라벨이 쓰여요.
이미 아이콘이 선택된 상태에서 나오는 축약 버튼(라인 28-36)도
addIconLabel을 그대로aria-label에 사용하고 있어요. 실제로는 패널을 열어 아이콘을 바꾸거나 지우는 동작이라, 스크린리더 사용자에게는 "아이콘 추가"라는 안내가 실제 동작과 어긋날 수 있습니다. 상태별로 다른 라벨(예: "아이콘 변경")을 주입할 수 있게 props를 분리하면 좋을 것 같아요. 참고: WAI-ARIA Authoring Practices - aria-label♿ 제안 diff
export interface TodoIconFieldProps { icon: TodoIconValue | null; isIconPanelOpen: boolean; addIconLabel: string; + editIconLabel?: string; onOpenPanel: () => void; ... } ... - aria-label={addIconLabel} + aria-label={editIconLabel ?? addIconLabel}📝 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.{!isIconPanelOpen && icon ? ( <button type="button" onClick={onOpenPanel} aria-label={editIconLabel ?? addIconLabel} className="flex size-6 shrink-0 items-center justify-center rounded-[4px]" > <IconGraphic icon={icon} /> </button>🤖 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/components/todo-modal/common/TodoIconField.tsx` around lines 28 - 36, Update TodoIconField’s selected-icon button to use a separate state-appropriate accessibility label instead of addIconLabel. Add or reuse a prop for the existing-icon action (such as changing or removing the icon), pass it to the button’s aria-label when icon is present, and preserve addIconLabel for the no-icon add action.apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx (2)
3-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # resizeTextarea가 다른 파일에 중복 정의되어 있는지 확인 rg -n 'resizeTextarea' --type=tsx --type=ts -C2Repository: Team-Timo/Timo-client
Length of output: 187
🏁 Script executed:
#!/bin/bash set -euo pipefail # 1) Read the review skill instructions first skill_file=".agents/skills/quality/timo-review/SKILL.md" if [[ -f "$skill_file" ]]; then echo "=== $skill_file ===" cat -n "$skill_file" else echo "Missing $skill_file" fi # 2) Locate resizeTextarea usages/definitions in the repo echo "=== resizeTextarea matches ===" rg -n "resizeTextarea" apps/timo-web -g '*.ts' -g '*.tsx' -C 2 || trueRepository: Team-Timo/Timo-client
Length of output: 7797
🏁 Script executed:
#!/bin/bash set -euo pipefail # Read the target file and related files if they exist to inspect context around resizeTextarea. for f in \ "apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx" \ "apps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsx" \ "apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx" do if [[ -f "$f" ]]; then echo "=== $f (outline) ===" ast-grep outline "$f" --view expanded || true echo "=== $f (relevant lines) ===" nl -ba "$f" | sed -n '1,220p' else echo "Missing $f" fi doneRepository: Team-Timo/Timo-client
Length of output: 710
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "=== locate resizeTextarea definitions/usages ===" rg -n "resizeTextarea" apps/timo-web -g '*.ts' -g '*.tsx' -C 2 || true echo echo "=== file line counts ===" for f in \ "apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx" \ "apps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsx" \ "apps/timo-web/components/todo-modal/detail/DetailTodoTaskFields.tsx" do if [[ -f "$f" ]]; then wc -l "$f" fi done echo echo "=== CreateTodoMemoField.tsx excerpt ===" nl -ba "apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx" | sed -n '1,220p'Repository: Team-Timo/Timo-client
Length of output: 5684
resizeTextarea를 공용 헬퍼로 묶어주세요.
이 파일의 로직이CreateTodoTaskFields.tsx와DetailTodoTaskFields.tsx에도 그대로 반복됩니다. 지금처럼 각 컴포넌트에 흩어두면 높이 계산 규칙을 바꿀 때 3곳을 동시에 수정해야 해서 금방 어긋납니다.apps/timo-web/components/todo-modal/common/같은 곳으로 빼서 재사용하면 유지보수가 훨씬 편해집니다.
참고: MDN Modules https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules🤖 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/components/todo-modal/create/CreateTodoMemoField.tsx` around lines 3 - 7, Extract the duplicated resizeTextarea logic into a shared helper under the todo-modal common utilities, export it, and update CreateTodoMemoField, CreateTodoTaskFields, and DetailTodoTaskFields to import and reuse that helper instead of defining local copies.
21-26: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
폼 리셋 등 외부
value변경 시 textarea 높이가 줄어들지 않습니다.
ref={resizeTextarea}는 마운트 시 한 번만 호출되고, 이후value가 부모에서 프로그래밍적으로 바뀌어도(예: 제출 후 메모 필드 초기화) React는 ref 콜백을 재호출하지 않습니다.onInput은 사용자가 직접 타이핑할 때만 발생하므로, 리셋 직후에는 이전에 늘어난 높이가 그대로 남아 다음 타이핑 전까지 보일 수 있습니다.
value가 바뀔 때도 높이를 재계산하도록useEffect로 보강하는 걸 추천합니다. React의 ref 콜백 동작은 react.dev의 ref 문서를 참고해보세요.💡 제안 diff
+import { useEffect, useRef } from "react"; + export const CreateTodoMemoField = ({ value, placeholder, onChange, }: CreateTodoMemoFieldProps) => { + const textareaRef = useRef<HTMLTextAreaElement | null>(null); + + useEffect(() => { + resizeTextarea(textareaRef.current); + }, [value]); + return ( <textarea value={value} - ref={resizeTextarea} + ref={textareaRef} onChange={(event) => onChange(event.target.value)} onInput={(event) => resizeTextarea(event.currentTarget)}📝 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.import { useEffect, useRef } from "react"; export const CreateTodoMemoField = ({ value, placeholder, onChange, }: CreateTodoMemoFieldProps) => { const textareaRef = useRef<HTMLTextAreaElement | null>(null); useEffect(() => { resizeTextarea(textareaRef.current); }, [value]); return ( <textarea value={value} ref={textareaRef} onChange={(event) => onChange(event.target.value)} onInput={(event) => resizeTextarea(event.currentTarget)} placeholder={placeholder}🤖 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/components/todo-modal/create/CreateTodoMemoField.tsx` around lines 21 - 26, CreateTodoMemoField의 textarea 높이 조정 로직에 value 변경을 감시하는 useEffect를 추가하세요. 외부에서 value가 리셋되거나 프로그래밍 방식으로 변경될 때 resizeTextarea를 호출해 높이를 다시 계산하고, 기존 ref 및 onInput 동작은 유지하세요.apps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsx (2)
41-41: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
체크박스가 클릭 가능해 보이지만 아무 동작도 하지 않아요 —
disabled처리가 필요해요
checked={false}+onChange={() => {}}조합은 시각적으로는 클릭 가능한 체크박스지만, 사용자가 클릭해도 아무 일도 일어나지 않습니다.Checkbox컴포넌트가cursor-pointer와 hover 스타일을 제공하기 때문에 사용자는 앱이 고장난 것으로 인식할 수 있습니다.생성 모달에서는 미완료 상태가 기본이므로 체크박스가 비활성 상태임이 명확해야 합니다.
disabledprop을 추가하거나, 디자인상 활성 상태처럼 보여야 한다면pointer-events: none으로 상호작용을 차단하세요.참고: WAI-ARIA — Non-functional controls
🔧 제안 diff
- <Checkbox checked={false} onChange={() => {}} /> + <Checkbox checked={false} onChange={() => {}} disabled />- <Checkbox checked={false} onChange={() => {}} /> + <Checkbox checked={false} onChange={() => {}} disabled />Also applies to: 60-60
🤖 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/components/todo-modal/create/CreateTodoTaskFields.tsx` at line 41, Update the Checkbox usage in CreateTodoTaskFields so the fixed unchecked state is explicitly non-interactive by adding its disabled handling. Preserve the default unchecked appearance while preventing clicks, keyboard interaction, and misleading hover/cursor behavior.
51-51: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
outline-none사용 시 포커스 표시 대체 스타일이 빠졌어요 — 접근성 위반이에요두 textarea 모두
outline-none클래스로 기본 포커스 아웃라인을 제거했지만,focus:또는focus-visible:커스텀 포커스 스타일이 없습니다. 키보드 사용자가 어느 필드에 포커스가 있는지 알 수 없어 입력 폼에서 작업 완료가 불가능한 접근성 차단 문제입니다.디자인 시스템의
Checkbox컴포넌트는peer-focus-visible:ring-2 peer-focus-visible:ring-timo-blue-300패턴을 사용하므로, 동일한focus-visible:ring스타일을 적용해 일관성을 맞추는 것을 권장합니다.As per coding guidelines,
outline: none으로 포커스 테두리 제거 금지 규칙을 따라야 합니다.♿ 제안 diff — focus-visible 링 추가
- className="typo-headline-b-14 text-timo-black min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none" + className="typo-headline-b-14 text-timo-black min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none focus-visible:ring-2 focus-visible:ring-timo-blue-300 focus-visible:ring-offset-1 rounded-[2px]"- className="typo-body-r-12 text-timo-gray-800 min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none" + className="typo-body-r-12 text-timo-gray-800 min-w-0 flex-1 resize-none overflow-hidden wrap-break-word outline-none focus-visible:ring-2 focus-visible:ring-timo-blue-300 focus-visible:ring-offset-1 rounded-[2px]"Also applies to: 71-71
🤖 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/components/todo-modal/create/CreateTodoTaskFields.tsx` at line 51, Update both textarea className declarations in CreateTodoTaskFields to preserve a visible keyboard focus indicator: remove outline-none or replace it with the design-system’s focus-visible ring styling, using the established timo-blue-300 ring pattern. Apply the same treatment to both fields identified by the textarea className usages.Source: Coding guidelines
apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts (1)
88-103: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # Todo 타입에 date/repeatFrequency/memo 관련 필드가 있는지 확인 ast-grep run --pattern 'interface Todo { $$$ }' --lang typescript apps/timo-web rg -n 'interface Todo\b' -A 30 apps/timo-webRepository: Team-Timo/Timo-client
Length of output: 159
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== files ==" git ls-files 'apps/timo-web/**' | rg 'use-detail-todo-form|todo|Todo|detail.*form|schema|types' echo echo "== hook outline ==" ast-grep outline apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts --view expanded || true echo echo "== hook excerpt ==" sed -n '1,240p' apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts | cat -n echo echo "== Todo identifiers search ==" rg -n '\b(Todo|DetailTodoFormValues|repeatFrequency|selectedWeekdayIds|repeatDay|memo)\b' apps/timo-web -g'*.ts' -g'*.tsx' -g'*.json' | sed -n '1,220p'Repository: Team-Timo/Timo-client
Length of output: 21958
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== todo type ==" sed -n '1,220p' apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type.ts | cat -n echo echo "== detail modal content ==" sed -n '1,260p' apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx | cat -n echo echo "== create modal content (for comparison) ==" sed -n '1,260p' apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx | cat -n echo echo "== generated detail response model ==" sed -n '1,220p' apps/timo-web/api/generated/models/todoDetailResponse.ts | cat -n echo echo "== todo schema ==" sed -n '1,220p' apps/timo-web/api/todo/todo-schema.ts | cat -nRepository: Team-Timo/Timo-client
Length of output: 228
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== todo type ==" sed -n '1,220p' 'apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type.ts' | cat -n echo echo "== detail modal content ==" sed -n '1,260p' 'apps/timo-web/components/todo-modal/detail/DetailTodoModalContent.tsx' | cat -n echo echo "== create modal content (for comparison) ==" sed -n '1,260p' 'apps/timo-web/components/todo-modal/create/CreateTodoModalContent.tsx' | cat -n echo echo "== generated detail response model ==" sed -n '1,220p' 'apps/timo-web/api/generated/models/todoDetailResponse.ts' | cat -n echo echo "== todo schema ==" sed -n '1,220p' 'apps/timo-web/api/todo/todo-schema.ts' | cat -nRepository: Team-Timo/Timo-client
Length of output: 23773
🏁 Script executed:
#!/bin/bash set -euo pipefail sed -n '240,360p' 'apps/timo-web/hooks/todo-modal/use-detail-todo-form.ts' | cat -nRepository: Team-Timo/Timo-client
Length of output: 2635
상세 투두 기본값을 상세 응답에서 채워주세요
훅 분리는 깔끔합니다. 다만 현재Todo에는date/repeat/memo가 없어서defaultValues의 고정값으로는 기존 상세 정보를 복원할 수 없습니다.TodoDetailResponse처럼 상세 응답을 쓰거나 필요한 필드를 별도로 주입하고, 모달 재사용 시에는reset(nextDefaults)로 다시 맞춰주세요. react-hook-form의defaultValues/reset문서도 같이 보면 좋습니다.🤖 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/hooks/todo-modal/use-detail-todo-form.ts` around lines 88 - 103, Update the useDetailTodoForm default-value flow to source date, repeat settings, and memo from TodoDetailResponse or explicitly injected detail fields instead of fixed DAILY, empty, and current-date values. Preserve existing todo-derived fields, and call react-hook-form reset with the newly computed defaults when the modal receives a different todo/detail response.apps/timo-web/hooks/todo-modal/use-subtask-field.ts (2)
1-114: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
전체 구조는 좋으나,
use-detail-todo-form.ts의 서브태스크 로직과 중복됩니다.하단 컨솔리데이션 코멘트에서 함께 다뤘습니다.
🤖 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/hooks/todo-modal/use-subtask-field.ts` around lines 1 - 114, Consolidate the duplicated subtask state and keyboard-handling logic between useSubtaskField and use-detail-todo-form.ts. Reuse a shared hook or extracted utility for createEntry, synchronization, input changes, Enter/Backspace handling, focus management, and reset, while preserving the existing subtask behavior and MAX_SUBTASK_COUNT constraint.
88-99: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Backspace 분기가 클로저로 캡처된
subtaskInputs를 직접 참조합니다.Enter 분기(74-84)는 함수형 업데이트를 쓰는데 Backspace 분기는 그렇지 않아 일관성이 떨어지고, stale 상태를 참조할 잠재적 위험이 있습니다.
use-detail-todo-form.ts의 동일 패턴과 함께 하단 컨솔리데이션 코멘트에서 다뤘습니다.🤖 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/hooks/todo-modal/use-subtask-field.ts` around lines 88 - 99, Update the Backspace handling in the subtask input handler to use a functional setSubtaskInputs update, deriving entries and focusIndex from the latest inputs state rather than the closure-captured subtaskInputs; preserve the existing preventDefault and pendingFocusIndex behavior.
ISSUE 🔗
close #151
What is this PR? 🔍
기존 투두 카드를 클릭했을 때 확인할 수 있는 상세 조회 모달을 구현했습니다.
상세 모달에서는 기존 투두의 제목, 하위 태스크, 아이콘, 날짜/시간, 우선순위, 태그, 반복, 메모 정보를 한 화면에서 확인할 수 있으며, 일부 값은 모달 내부에서 바로 수정할 수 있도록 연결했습니다.
주요 작업
1. 기존 투두 카드 클릭 시 상세 모달 진입
기존 홈 화면의 투두 카드를 클릭하면 상세 투두 모달이 열리도록 연결했습니다.
처음에는 기존 홈 컨테이너 안에 모달 상태와 UI를 바로 추가하는 방식도 가능했지만, 그렇게 하면 홈 화면의 책임이 너무 커질 수 있다고 판단했습니다.
그래서 상세 모달은 별도 컨테이너와 콘텐츠 컴포넌트로 분리해, 홈 화면은 “어떤 투두를 열지”만 관리하고 모달 내부 UI와 상태는 모달 쪽에서 담당하도록 구성했습니다.
2. 상세 투두 모달 레이아웃 구성
화면 설계서를 기준으로 상세 모달의 큰 구조를 먼저 잡았습니다.
각 영역은 이후 수정 기능이 붙을 수 있도록 단순 마크업이 아니라 상태와 이벤트를 받을 수 있는 구조로 만들었습니다.
특히 제목과 하위 태스크는 단순 텍스트가 아니라 입력 가능한 필드로 구성했습니다.
하위 태스크가 여러 개로 늘어나는 케이스도 고려해 리스트 형태로 렌더링하고, 각 항목은
subtaskId를 key로 사용하도록 했습니다.3. 기존 생성 모달 컴포넌트 재사용
상세 모달에서도 생성 모달과 동일하게 사용하는 영역이 있어, 중복 구현 대신 기존 컴포넌트를 재사용할 수 있도록 정리했습니다.
대표적으로 아이콘 추가 영역은 생성 모달과 상세 모달에서 동일한 UI와 동작을 가지기 때문에
TodoIconField형태로 공통화했습니다.이 방식으로 선택한 이유는 다음과 같습니다.
4. 상세 투두 전용 form 상태 훅 분리
상세 모달 내부 상태가 많아지면서 컴포넌트 안에서 모든 상태를 직접 관리하면 가독성이 떨어질 수 있다고 판단했습니다.
그래서 상세 투두의 로컬 상태는
useDetailTodoForm훅으로 분리했습니다.이 훅에서는 다음 값을 관리합니다.
컴포넌트는 UI를 그리는 역할에 집중하고, 상태 변경 로직은 훅에서 관리하도록 나누었습니다.
이렇게 구성하면 DetailTodoTaskFields는 화면을 그리는 역할에 집중하고, 실제 상태 변경 방식은 useDetailTodoForm에서 관리할 수 있습니다.
5. 제목 / 하위 태스크 수정 연결
상세 모달에서 투두 제목과 하위 태스크를 수정할 수 있도록 입력 필드로 연결했습니다.
이때 생성 모달에서 이미 사용 중인 입력 유틸을 재사용했습니다.
이미 검증된 로직을 다시 사용하는 방식이므로, 새로 구현하면서 생길 수 있는 동작 차이를 줄일 수 있었습니다.
6. 실행 버튼 동작 연결
상세 모달의 실행 버튼을 클릭하면 현재 투두의 타이머 / 타임박스 실행이 시작되도록 연결했습니다.
동작 규칙에 따라 실행 버튼 클릭 후에는 상세 모달이 닫히도록 처리했습니다.
7. 투두 삭제 동작 연결
툴바 옆 삭제 버튼을 클릭하면 현재 상세 모달에서 보고 있는 투두가 삭제되도록 연결했습니다.
삭제 후에는 모달이 닫히며, 홈 화면의 투두 목록에서도 해당 투두가 제거됩니다.
8. 다국어 지원 적용
상세 모달에 남아 있던 하드코딩 문구를
next-intl기반 번역 문구로 교체했습니다.적용한 영역은 다음과 같습니다.
특히 태그 / 반복 / 우선순위 드롭다운은 디자인시스템 컴포넌트 내부에 한국어가 고정되어 있던 부분이 있었습니다.
이 부분은 디자인시스템 컴포넌트에 번역 함수를 직접 넣는 대신, 외부에서 라벨을 props로 주입받도록 확장했습니다.
이 부분은 디자인시스템 컴포넌트에 번역 함수를 직접 넣는 대신, 외부에서 라벨을 props로 주입받도록 확장했습니다.
앱 레이어에서는 locale에 맞는 번역 값을 만들어 TodoToolbar에 전달합니다.
이 방식을 선택한 이유는 다음과 같습니다.
고민한 지점
모달 내부를 어디까지 분리할 것인가
처음에는 기존 홈 화면 파일에 모달 코드를 바로 추가할 수도 있었습니다.
하지만 상세 모달은 단순 조회 UI가 아니라 제목 수정, 하위 태스크 수정, 툴바 상태 변경, 삭제, 실행, 메모 입력까지 포함하게 되므로 하나의 파일에 모두 넣으면 책임이 너무 커질 수 있다고 판단했습니다.
그래서 다음 기준으로 분리했습니다.
px 고정 대신 내용에 따라 자연스럽게 늘어나는 구조
하위 태스크나 메모는 개수가 늘어나거나 입력 내용이 길어질 수 있습니다.
그래서 모달 크기를 무조건 고정하기보다는, 내부 콘텐츠가 늘어날 수 있는 구조로 잡았습니다.
다만 화면 설계서에서 명확한 간격이 있는 부분은
mt,gap,py등을 사용해 의도된 spacing을 유지했습니다.공통 컴포넌트 수정 범위
디자인시스템 컴포넌트를 수정할 때 기존 사용처에 영향을 줄 수 있기 때문에, 기존 동작을 바꾸는 방식은 피했습니다.
대신 optional prop을 추가하고 기본값은 기존 한국어 문구로 유지했습니다.
예를 들어:
PrioritySelector는labelsprop을 받을 수 있음TagSelector는addLabelprop을 받을 수 있음RepeatSelector는frequencyHeadingprop을 받을 수 있음TodoToolbar는 해당 라벨들을 하위 컴포넌트로 전달함이렇게 하면 기존 화면은 그대로 동작하고, 상세/생성 모달처럼 다국어가 필요한 곳에서만 번역 문구를 넘길 수 있습니다.
To Reviewers
리뷰 반영으로 바뀐 점: 폴더 구조
생성 TODO 모달과 상세 TODO 모달에서 함께 사용하는 로직/컴포넌트가
home내부에만 묶여 있으면, 다른 화면에서 재사용하기 어려울 수 있다고 판단했습니다.그래서 TODO 모달 관련 파일을 홈 전용 경로에서 공용 경로로 이동했습니다.
components/todo-modal/createCreateTodoModalContentCreateTodoModalContainerCreateTodoTaskFieldsCreateTodoMemoFieldcomponents/todo-modal/detailDetailTodoModalContentDetailTodoModalContainerDetailTodoTaskFieldsDetailTodoMemoFieldcomponents/todo-modal/commonTodoIconFieldhooks/todo-modalutils/todo이렇게 나눈 이유는 “홈 화면에서만 쓰는 코드”와 “TODO 모달 도메인에서 재사용 가능한 코드”를 분리하기 위해서입니다.
특히
TodoIconField는 생성 모달과 상세 모달에서 동일하게 사용되므로common폴더로 분리했고, 생성/상세 각각에만 필요한 컴포넌트는create,detail폴더에 따로 두었습니다.Screenshot 📷
2026-07-13.4.15.41.mov
2026-07-13.4.17.03.mov
Test Checklist ✔