[FEAT] 타이머 종료·완료·연장·중단 모달 연결 및 다국어 지원 - #140
Conversation
- 타이머 컨트롤의 종료·시간추가 버튼을 Modal.Trigger로 전환했습니다 - 종료 모달에서 완료/연장 단계로 전환되는 TimerSessionControls를 추가했습니다 - 종료·완료·연장 각 단계의 모달 콘텐츠를 Figma 디자인대로 구현했습니다 - FocusSessionContainer와 홈 사이드바 TimerPanel에 모달 플로우를 연결했습니다 - 분 단위 변환 유틸(convertDurationToMinutes)을 추가했습니다
- 타이머가 실행 중일 때 종료를 누르면 뜨는 "지금 멈출까요?" 중단 모달을 추가했습니다 - 종료·완료·연장 모달과 타이머 컨트롤 라벨을 next-intl Focus 네임스페이스로 옮겨 한/영 전환되게 했습니다 - 연장 모달의 프리셋·직접입력 단위를 언어별(분/시간 vs M/H)로 분리했습니다 - 모달 버튼·타이머 컨트롤·연장 모달의 포커스링을 그림자(ring) 대신 버튼 테두리 색상 전환 방식으로 교체했습니다
- 타이머 재생 중 상위 체크박스를 누르면 완료 처리 대신 타이머 중단 모달을 띄우도록 연결했습니다 - Modal.Trigger가 ref를 받을 수 있도록 수정해 외부에서 정지 모달을 프로그래밍적으로 열 수 있게 했습니다 - 완료·중단 모달의 소요 시간을 60분 이상일 때 시/분(h m) 단위로 표기하고, 중단 모달의 시간 값을 파란색으로 강조했습니다 - 포커스 화면 상단의 요일 라벨을 next-intl Common.weekday로 교체해 영문 버전에서도 표시되게 했습니다
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 54 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 (2)
Walkthrough포커스 타이머가 직접 콜백 방식에서 종료·중지·연장·완료 모달을 포함한 세션 제어 방식으로 전환되었습니다. 요일·지속시간 표시를 번역 및 공통 시간 유틸리티와 연결하고, 포커스 화면과 사이드바에 새 세션 컨트롤을 적용했습니다. Changes포커스 타이머 모달 흐름
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant FocusSessionContainer
participant TimerSessionControls
participant TimerEndModalPanel
participant TimerExtendModalPanel
participant TimerCompleteModalPanel
FocusSessionContainer->>TimerSessionControls: 타이머 세션 props 전달
TimerSessionControls->>TimerEndModalPanel: 종료 모달 표시
TimerEndModalPanel->>TimerSessionControls: 계속 또는 완료 선택
TimerSessionControls->>TimerExtendModalPanel: 연장 모달 표시
TimerExtendModalPanel->>TimerSessionControls: 연장 시간 제출
TimerSessionControls->>TimerCompleteModalPanel: 완료 모달 표시
TimerCompleteModalPanel->>FocusSessionContainer: 완료 콜백 호출
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 |
…to feat/web/136-connect-modal # Conflicts: # apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 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)/focus/_containers/FocusSessionContainer.tsx:
- Around line 125-126: FocusSessionContainer의 actualMinutes에 plannedMinutes를
전달하지 말고, 실제 경과 시간 필드가 추가될 때까지 0을 임시값으로 사용하거나 명확한 TODO를 추가하세요.
TimerCompleteModalPanel과 TimerStopModalPanel에 계획 시간이 실제 시간으로 표시되지 않도록 수정하고,
TimerPanel의 PLANNED_MINUTES 전달부에도 동일한 처리를 적용하세요.
- Line 27: Remove the duplicated SECONDS_PER_MINUTE declaration from
FocusSessionContainer and import the shared exported constant from duration.ts,
updating duration.ts to export it if necessary.
In `@apps/timo-web/components/layout/sidebar/time/TimerPanel.tsx`:
- Around line 34-35: TimerPanel의 FocusSessionContainer 호출에서 actualMinutes에 계획값인
PLANNED_MINUTES를 전달하는 문제를 수정하세요. 실제 집중 세션 시간을 추적하는 상태나 계산값을 사용해 actualMinutes를
전달하고, plannedMinutes에는 계속 PLANNED_MINUTES를 유지하여 완료 모달에 계획 시간과 실제 시간이 올바르게 표시되도록
하세요.
- Line 18: TimerPanel의 handleExtendTimer가 no-op 상태이므로, FocusSessionContainer의 동일
핸들러 동작을 참고해 연장 시 durationSeconds를 업데이트하고 필요한 TODO 주석을 추가하세요.
In `@apps/timo-web/components/timer/TimerEndModalPanel.tsx`:
- Around line 61-97: The TimerEndModalPanel currently uses TimerEndIcon
directly; replace it with the finalized SVG component once available, remove the
related TODO comment, and track any pending asset work through the appropriate
issue rather than leaving inline TODO text.
- Around line 4-59: TimerEndIcon과 TimerStopModalPanel의 TimerStopIcon에 동일한 SVG
마크업이 중복되어 있습니다. SVG를 공용 아이콘 컴포넌트로 추출한 뒤 두 컴포넌트가 해당 공용 컴포넌트를 렌더링하도록 변경하고, 기존 아이콘과
동일한 크기·속성·외형을 유지하세요.
In `@apps/timo-web/components/timer/TimerExtendModalPanel.tsx`:
- Around line 73-89: Replace the input’s outline-none class in the custom
minutes field with an accessible focus-visible treatment, such as
focus-visible:outline and an appropriate focus-visible outline color, or use
outline-hidden if the parent focus-within styling is sufficient. Update the
input element in TimerExtendModalPanel’s custom minutes control while preserving
the existing parent focus-within indicator.
In `@apps/timo-web/components/timer/TimerSessionControls.tsx`:
- Around line 49-54: TimerSessionControls의 openStopModal에서 숨겨진 트리거의
stopModalTriggerRef.current?.click() 호출을 제거하고, 모달 컨텍스트가 제공하는 open 함수를 직접 사용해 모달을
열도록 변경하세요. setStep("stop")과 모달 열기 동작을 기존 순서대로 유지하되, 트리거 ref 및 관련 의존 코드는 정리하고 동일한
방식이 적용된 다른 구간도 함께 수정하세요.
In `@apps/timo-web/messages/ko.json`:
- Line 75: Update the completeModal.title Korean translation from “테스크가 완료
되었어요!” to the correctly spaced and spelled “태스크가 완료되었어요!”.
In `@packages/timo-design-system/src/components/layout/modal/Modal.tsx`:
- Around line 75-77: ModalTriggerProps를 type 교차 타입 대신 interface로 선언하도록 변경하세요.
ButtonHTMLAttributes<HTMLButtonElement>를 extends하고 기존 ref?:
Ref<HTMLButtonElement> 속성을 유지하며, ModalTriggerProps를 사용하는 코드가 동일하게 동작하는지 확인하세요.
🪄 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: 20e3b7cc-9e49-406d-88fc-105f236ba739
📒 Files selected for processing (16)
apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsxapps/timo-web/app/[locale]/(main)/focus/_utils/date.tsapps/timo-web/app/[locale]/(main)/focus/_utils/duration.tsapps/timo-web/components/layout/sidebar/time/TimerPanel.tsxapps/timo-web/components/timer/TimerCompleteModalPanel.tsxapps/timo-web/components/timer/TimerControlButton.tsxapps/timo-web/components/timer/TimerControls.tsxapps/timo-web/components/timer/TimerEndModalPanel.tsxapps/timo-web/components/timer/TimerExtendModalPanel.tsxapps/timo-web/components/timer/TimerSessionControls.tsxapps/timo-web/components/timer/TimerStopModalPanel.tsxapps/timo-web/components/timer/durationLabel.tsapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonpackages/timo-design-system/src/components/button/modal-button/ModalButton.tsxpackages/timo-design-system/src/components/layout/modal/Modal.tsx
| plannedMinutes={plannedMinutes} | ||
| actualMinutes={plannedMinutes} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
actualMinutes에 plannedMinutes를 그대로 전달하여 "계획"과 "실제"가 항상 동일하게 표시됩니다
FocusTask 인터페이스에 경과 시간 필드(elapsedSeconds 등)가 없어 현재 데이터 모델로는 실제 시간 추적이 불가능합니다. 하지만 actualMinutes={plannedMinutes}로 전달하면:
TimerCompleteModalPanel의 "계획"과 "실제"가 항상 같은 값을 보여줌 (예: 둘 다 "2h")TimerStopModalPanel의 "지금까지 수행한 {minutes}"에 계획 시간이 표시됨
경과 시간 추적이 구현될 때까지 TODO 주석 추가 또는 actualMinutes={0} 임시값 사용을 고려하세요. TimerPanel.tsx의 PLANNED_MINUTES 전달에도 동일한 패턴이 나타납니다.
🛡️ proposed fix: TODO 주석 추가
<TimerSessionControls
ref={timerSessionControlsRef}
isRunning={task.isRunning}
onTogglePlay={handleTogglePlay}
plannedMinutes={plannedMinutes}
- actualMinutes={plannedMinutes}
+ // TODO: actualMinutes에 실제 경과 시간 연결 필요 (FocusTask에 elapsedSeconds 필드 추가)
+ actualMinutes={0}
onExtend={handleExtendTimer}
onComplete={handleCompleteTimer}
/>🤖 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)/focus/_containers/FocusSessionContainer.tsx
around lines 125 - 126, FocusSessionContainer의 actualMinutes에 plannedMinutes를
전달하지 말고, 실제 경과 시간 필드가 추가될 때까지 0을 임시값으로 사용하거나 명확한 TODO를 추가하세요.
TimerCompleteModalPanel과 TimerStopModalPanel에 계획 시간이 실제 시간으로 표시되지 않도록 수정하고,
TimerPanel의 PLANNED_MINUTES 전달부에도 동일한 처리를 적용하세요.
| setStatus((prev) => (prev === "RUNNING" ? "PAUSED" : "RUNNING")); | ||
| const handleEnd = () => setStatus("PAUSED"); | ||
| const handleAddTime = () => {}; | ||
| const handleExtendTimer = () => {}; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
handleExtendTimer가 빈 구현입니다
FocusSessionContainer의 동일 핸들러는 durationSeconds 업데이트와 TODO 주석을 포함하지만, TimerPanel의 handleExtendTimer는 no-op이고 TODO도 없습니다. 연장 모달은 표시되지만 "시간 추가하기" 버튼이 아무 동작도 수행하지 않습니다.
✏️ proposed fix: TODO 주석 추가
- const handleExtendTimer = () => {};
+ // TODO: 사이드바 타이머 시간 연장 로직 구현 필요
+ const handleExtendTimer = () => {};🤖 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/layout/sidebar/time/TimerPanel.tsx` at line 18,
TimerPanel의 handleExtendTimer가 no-op 상태이므로, FocusSessionContainer의 동일 핸들러 동작을
참고해 연장 시 durationSeconds를 업데이트하고 필요한 TODO 주석을 추가하세요.
| plannedMinutes={PLANNED_MINUTES} | ||
| actualMinutes={PLANNED_MINUTES} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
actualMinutes에 PLANNED_MINUTES를 전달하여 "계획"과 "실제"가 동일하게 표시됩니다
FocusSessionContainer에서 지적한 것과 동일한 패턴입니다. actualMinutes={PLANNED_MINUTES}로 인해 완료 모달의 "계획"과 "실제"가 항상 같은 값을 보여줍니다.
🛡️ proposed fix: TODO 주석 추가
<TimerSessionControls
isRunning={status === "RUNNING"}
onTogglePlay={handleTogglePlay}
plannedMinutes={PLANNED_MINUTES}
- actualMinutes={PLANNED_MINUTES}
+ // TODO: actualMinutes에 실제 경과 시간 연결 필요
+ actualMinutes={0}
onExtend={handleExtendTimer}
onComplete={handleCompleteTimer}
/>🤖 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/layout/sidebar/time/TimerPanel.tsx` around lines 34
- 35, TimerPanel의 FocusSessionContainer 호출에서 actualMinutes에 계획값인
PLANNED_MINUTES를 전달하는 문제를 수정하세요. 실제 집중 세션 시간을 추적하는 상태나 계산값을 사용해 actualMinutes를
전달하고, plannedMinutes에는 계속 PLANNED_MINUTES를 유지하여 완료 모달에 계획 시간과 실제 시간이 올바르게 표시되도록
하세요.
| <input | ||
| type="text" | ||
| inputMode="numeric" | ||
| value={customMinutes} | ||
| onChange={(e) => { | ||
| const digitsOnly = e.target.value.replace(/\D/g, ""); | ||
| const clamped = | ||
| digitsOnly === "" | ||
| ? "" | ||
| : String(Math.min(Number(digitsOnly), MAX_CUSTOM_MINUTES)); | ||
|
|
||
| onCustomMinutesChange(clamped); | ||
| }} | ||
| aria-label={t("customInputLabel")} | ||
| style={{ width: `${Math.max(customMinutes.length, 1)}ch` }} | ||
| className="typo-headline-r-14 text-timo-black shrink-0 text-center outline-none" | ||
| /> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
입력 필드에 outline-none이 직접적인 focus-visible 대체 스타일 없이 사용되었습니다.
부모 div의 focus-within:border-timo-blue-300로 간접적으로 포커스를 표시하지만, 컨벤션에서는 outline: none으로 포커스 테두리 제거를 금지하고 있습니다. 입력 요소 자체에 focus-visible 스타일을 추가하거나, outline-none 대신 outline-hidden 사용을 고려하세요.
🛡️ 제안하는 변경
className="typo-headline-r-14 text-timo-black shrink-0 text-center outline-none"
+ // focus-visible 스타일 추가 또는 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/components/timer/TimerExtendModalPanel.tsx` around lines 73 -
89, Replace the input’s outline-none class in the custom minutes field with an
accessible focus-visible treatment, such as focus-visible:outline and an
appropriate focus-visible outline color, or use outline-hidden if the parent
focus-within styling is sufficient. Update the input element in
TimerExtendModalPanel’s custom minutes control while preserving the existing
parent focus-within indicator.
Source: Path instructions
| useImperativeHandle(ref, () => ({ | ||
| openStopModal: () => { | ||
| setStep("stop"); | ||
| stopModalTriggerRef.current?.click(); | ||
| }, | ||
| })); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
히든 트리거 .click() 패턴이 동작은 하지만 fragile합니다.
openStopModal에서 setStep("stop") 직후 stopModalTriggerRef.current?.click()로 모달을 여는 패턴은 React 배칭 덕분에 동작하지만, 모달 컨텍스트의 open 함수를 직접 호출할 수 있는 방법을 고려해 보세요. 향후 Modal API가 변경되면 .click() 가정이 깨질 수 있습니다.
Also applies to: 91-96
🤖 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/timer/TimerSessionControls.tsx` around lines 49 -
54, TimerSessionControls의 openStopModal에서 숨겨진 트리거의
stopModalTriggerRef.current?.click() 호출을 제거하고, 모달 컨텍스트가 제공하는 open 함수를 직접 사용해 모달을
열도록 변경하세요. setStep("stop")과 모달 열기 동작을 기존 순서대로 유지하되, 트리거 ref 및 관련 의존 코드는 정리하고 동일한
방식이 적용된 다른 구간도 함께 수정하세요.
| export type ModalTriggerProps = ButtonHTMLAttributes<HTMLButtonElement> & { | ||
| ref?: Ref<HTMLButtonElement>; | ||
| }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
type → interface 변경을 권장합니다.
ModalTriggerProps는 ButtonHTMLAttributes와 교차 타입으로 구성된 Props입니다. 컨벤션에 따라 Props 접미사가 붙은 타입은 interface로 선언하는 것이 권장됩니다.
♻️ 제안하는 변경
-export type ModalTriggerProps = ButtonHTMLAttributes<HTMLButtonElement> & {
- ref?: Ref<HTMLButtonElement>;
-};
+export interface ModalTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
+ ref?: Ref<HTMLButtonElement>;
+}📝 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 type ModalTriggerProps = ButtonHTMLAttributes<HTMLButtonElement> & { | |
| ref?: Ref<HTMLButtonElement>; | |
| }; | |
| export interface ModalTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> { | |
| ref?: Ref<HTMLButtonElement>; | |
| } |
🤖 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/layout/modal/Modal.tsx` around
lines 75 - 77, ModalTriggerProps를 type 교차 타입 대신 interface로 선언하도록 변경하세요.
ButtonHTMLAttributes<HTMLButtonElement>를 extends하고 기존 ref?:
Ref<HTMLButtonElement> 속성을 유지하며, ModalTriggerProps를 사용하는 코드가 동일하게 동작하는지 확인하세요.
Source: Path instructions
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
- "테스크"를 "태스크"로, "완료 되었어요"를 "완료되었어요"로 맞춤법과 띄어쓰기를 수정했습니다
- 여러 파일에 중복 정의돼 있던 SECONDS_PER_MINUTE/SECONDS_PER_HOUR/MINUTES_PER_HOUR를 constants/time.ts로 통합했습니다 - convertDurationToMinutes를 공통 유틸(utils/convert-duration-to-minutes.ts)로 옮기고 JSDoc을 추가했습니다
- 순수 함수인 formatDurationLabel을 components/timer에서 utils/format-duration-label.ts로 옮기고, 다른 유틸 파일과 동일한 kebab-case 네이밍으로 맞췄습니다
…to feat/web/136-connect-modal
- 인라인 placeholder SVG(TimerEndIcon/TimerStopIcon)를 제거하고 timo-timer.svg 로고 에셋을 사용하도록 했습니다
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/messages/ko.json (1)
170-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value번역 키 구성이 컴포넌트 사용 패턴과 일치합니다
Focus네임스페이스의 모든 키(controls,endModal,stopModal,completeModal,extendModal,duration)가 컴포넌트에서 사용하는useTranslations호출과 정확히 매칭됩니다. 이전 리뷰에서 지적된 "태스크" 오탈자도 수정되었습니다. 👍한 가지 사소한 제안:
stopModal.description의 "지금까지 수행한 {minutes}이 타임박스에 기록됩니다."에서 "수행한 30분"이라는 표현이 다소 어색할 수 있습니다. "지금까지 {minutes}이(가) 타임박스에 기록됩니다." 또는 "지금까지 수행한 시간 {minutes}이 타임박스에 기록됩니다."가 더 자연스러울 수 있습니다.🤖 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/messages/ko.json` around lines 170 - 211, Focus.stopModal.description의 한국어 문구를 수정해 “수행한 30분”처럼 어색하게 읽히지 않도록 하세요. {minutes} 변수와 <blue> 마크업은 유지하고, 경과 시간이 타임박스에 기록된다는 의미가 자연스럽게 전달되도록 표현을 다듬으세요.
♻️ Duplicate comments (1)
apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx (1)
118-125: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
actualMinutes에 여전히plannedMinutes를 전달하고 있습니다이전 리뷰에서 지적된 문제가 현재 코드에도 그대로 남아 있습니다.
actualMinutes={plannedMinutes}로 전달하면TimerCompleteModalPanel의 "계획"과 "실제"가 항상 동일한 값을 표시합니다. 실제 경과 시간 추적이 구현될 때까지actualMinutes={0}임시값 사용이나 TODO 주석 추가를 권장합니다.🤖 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)/focus/_containers/FocusSessionContainer.tsx around lines 118 - 125, Update the TimerSessionControls invocation in FocusSessionContainer so actualMinutes no longer receives plannedMinutes; pass the temporary value 0 instead, optionally documenting with a TODO that actual elapsed-time tracking should replace it later.
🤖 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/utils/convert-duration-to-time-text.ts`:
- Around line 1-20: Review the existing convertDurationToTimeText implementation
in todo-time.ts and replace it with the shared utility from this diff if the
padded "HH:MM" format is acceptable there. Remove the duplicate implementation
and update its imports or call sites as needed; if the unpadded format is
required, distinguish the APIs or add an explicit formatting option instead of
keeping identical function names with different output.
---
Outside diff comments:
In `@apps/timo-web/messages/ko.json`:
- Around line 170-211: Focus.stopModal.description의 한국어 문구를 수정해 “수행한 30분”처럼 어색하게
읽히지 않도록 하세요. {minutes} 변수와 <blue> 마크업은 유지하고, 경과 시간이 타임박스에 기록된다는 의미가 자연스럽게 전달되도록
표현을 다듬으세요.
---
Duplicate comments:
In
`@apps/timo-web/app/`[locale]/(main)/focus/_containers/FocusSessionContainer.tsx:
- Around line 118-125: Update the TimerSessionControls invocation in
FocusSessionContainer so actualMinutes no longer receives plannedMinutes; pass
the temporary value 0 instead, optionally documenting with a TODO that actual
elapsed-time tracking should replace it later.
🪄 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: e3e98085-dd37-4cdb-b68d-fcd2a2ddcc1b
📒 Files selected for processing (11)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time.tsapps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsxapps/timo-web/app/[locale]/(main)/focus/_utils/duration.tsapps/timo-web/components/timer/TimerCompleteModalPanel.tsxapps/timo-web/components/timer/TimerStopModalPanel.tsxapps/timo-web/constants/time.tsapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonapps/timo-web/utils/convert-duration-to-minutes.tsapps/timo-web/utils/convert-duration-to-time-text.tsapps/timo-web/utils/format-duration-label.ts
💤 Files with no reviewable changes (1)
- apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts
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: 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/messages/ko.json (1)
170-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value번역 키 구성이 컴포넌트 사용 패턴과 일치합니다
Focus네임스페이스의 모든 키(controls,endModal,stopModal,completeModal,extendModal,duration)가 컴포넌트에서 사용하는useTranslations호출과 정확히 매칭됩니다. 이전 리뷰에서 지적된 "태스크" 오탈자도 수정되었습니다. 👍한 가지 사소한 제안:
stopModal.description의 "지금까지 수행한 {minutes}이 타임박스에 기록됩니다."에서 "수행한 30분"이라는 표현이 다소 어색할 수 있습니다. "지금까지 {minutes}이(가) 타임박스에 기록됩니다." 또는 "지금까지 수행한 시간 {minutes}이 타임박스에 기록됩니다."가 더 자연스러울 수 있습니다.🤖 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/messages/ko.json` around lines 170 - 211, Focus.stopModal.description의 한국어 문구를 수정해 “수행한 30분”처럼 어색하게 읽히지 않도록 하세요. {minutes} 변수와 <blue> 마크업은 유지하고, 경과 시간이 타임박스에 기록된다는 의미가 자연스럽게 전달되도록 표현을 다듬으세요.
♻️ Duplicate comments (1)
apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx (1)
118-125: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
actualMinutes에 여전히plannedMinutes를 전달하고 있습니다이전 리뷰에서 지적된 문제가 현재 코드에도 그대로 남아 있습니다.
actualMinutes={plannedMinutes}로 전달하면TimerCompleteModalPanel의 "계획"과 "실제"가 항상 동일한 값을 표시합니다. 실제 경과 시간 추적이 구현될 때까지actualMinutes={0}임시값 사용이나 TODO 주석 추가를 권장합니다.🤖 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)/focus/_containers/FocusSessionContainer.tsx around lines 118 - 125, Update the TimerSessionControls invocation in FocusSessionContainer so actualMinutes no longer receives plannedMinutes; pass the temporary value 0 instead, optionally documenting with a TODO that actual elapsed-time tracking should replace it later.
🤖 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/utils/convert-duration-to-time-text.ts`:
- Around line 1-20: Review the existing convertDurationToTimeText implementation
in todo-time.ts and replace it with the shared utility from this diff if the
padded "HH:MM" format is acceptable there. Remove the duplicate implementation
and update its imports or call sites as needed; if the unpadded format is
required, distinguish the APIs or add an explicit formatting option instead of
keeping identical function names with different output.
---
Outside diff comments:
In `@apps/timo-web/messages/ko.json`:
- Around line 170-211: Focus.stopModal.description의 한국어 문구를 수정해 “수행한 30분”처럼 어색하게
읽히지 않도록 하세요. {minutes} 변수와 <blue> 마크업은 유지하고, 경과 시간이 타임박스에 기록된다는 의미가 자연스럽게 전달되도록
표현을 다듬으세요.
---
Duplicate comments:
In
`@apps/timo-web/app/`[locale]/(main)/focus/_containers/FocusSessionContainer.tsx:
- Around line 118-125: Update the TimerSessionControls invocation in
FocusSessionContainer so actualMinutes no longer receives plannedMinutes; pass
the temporary value 0 instead, optionally documenting with a TODO that actual
elapsed-time tracking should replace it later.
🪄 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: e3e98085-dd37-4cdb-b68d-fcd2a2ddcc1b
📒 Files selected for processing (11)
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time.tsapps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsxapps/timo-web/app/[locale]/(main)/focus/_utils/duration.tsapps/timo-web/components/timer/TimerCompleteModalPanel.tsxapps/timo-web/components/timer/TimerStopModalPanel.tsxapps/timo-web/constants/time.tsapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonapps/timo-web/utils/convert-duration-to-minutes.tsapps/timo-web/utils/convert-duration-to-time-text.tsapps/timo-web/utils/format-duration-label.ts
💤 Files with no reviewable changes (1)
- apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts
🛑 Comments failed to post (1)
apps/timo-web/utils/convert-duration-to-time-text.ts (1)
1-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
todo-time.ts의convertDurationToTimeText와 중복 및 포맷 불일치새로 추가된 이 함수는 시간을 2자리 패딩(
01:30)하지만,todo-time.ts8-14줄에 이미 동일 이름의 함수가 존재하며 시간 패딩 없이(1:30) 반환합니다. 두 함수가 다른 컨텍스트에서 의도적으로 다른 포맷을 사용하는 것이 아니라면, 중복을 제거하고 하나의 구현으로 통일하는 것이 좋습니다.
todo-time.ts의 기존 함수를 이 새 유틸리티로 교체할 수 있는지 확인해 보시겠어요?♻️ 중복 제거 제안
// todo-time.ts에서 기존 convertDurationToTimeText 제거 후 import로 대체 - export const convertDurationToTimeText = (durationSeconds: number): string => { - const hours = Math.floor(durationSeconds / SECONDS_PER_HOUR); - const minutes = Math.floor( - (durationSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE, - ); - return `${hours}:${minutes.toString().padStart(2, "0")}`; - }; + export { convertDurationToTimeText } from "`@/utils/convert-duration-to-time-text`";만약 패딩 없는 포맷이 의도된 것이라면, 함수명을 구분하거나 옵션 파라미터를 추가하는 방법도 고려할 수 있습니다.
🤖 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/utils/convert-duration-to-time-text.ts` around lines 1 - 20, Review the existing convertDurationToTimeText implementation in todo-time.ts and replace it with the shared utility from this diff if the padded "HH:MM" format is acceptable there. Remove the duplicate implementation and update its imports or call sites as needed; if the unpadded format is required, distinguish the APIs or add an explicit formatting option instead of keeping identical function names with different output.
yumin-kim2
left a comment
There was a problem hiding this comment.
고생 많으셨서요! 모달 흐름과 i18n까지 같이 정리되어 있어서 이후 실제 타이머 API 연결할 때 기반이 잘 잡힌 PR이라고 느껴졌어요.👍👍 최공
There was a problem hiding this comment.
TimerSessionControls에 버튼 제어와 모달 렌더링 로직이 함께 모여 있어 살짝 커 보였습니다. TimerSessionModal처럼 모달 영역만 분리하면 역할이 더 명확해질 것 같은데, 어떻게 생각하시나용!
ISSUE 🔗
close #136
What is this PR? 🔍
Focus 뷰와 홈 사이드바 타이머에 종료·완료·연장·중단(재생 중 조기 종료) 4가지 확인 모달을 연결하고, 관련 텍스트 전체를
next-intl로 옮겨 한/영을 지원하도록 했습니다.배경
packages/timo-design-system의Modal컴포넌트는 이미 구현돼 있었지만 실제 타이머 화면(Focus 뷰, 홈 사이드바)에는 전혀 연결돼 있지 않았고, 종료 버튼을 눌러도 아무 동작이 없었습니다.Modal인스턴스 안에서 step으로 분기하는 구조로 구현하고, 텍스트는next-intl로 옮겨 한/영을 지원하도록 했습니다.타이머 모달 (TimerSessionControls 외)
Modal컨트롤러(TimerSessionControls)로 통합해 신규 구현했습니다.Modal은Modal.Trigger클릭으로만 열 수 있는 비제어(uncontrolled) 컴포넌트라, 여러 확인 단계(종료→연장/완료)를 하나의 열린 모달 안에서 전환하려면 버튼이 아닌 지점(체크박스)에서도 여는 방법이 필요했습니다.Modal에 controlled prop을 추가하는 대신 기존 API를 유지하는 방향으로 진행했습니다.TimerSessionControls내부에서stepstate(end | stop | extend | complete)로 어떤 패널을 보여줄지 분기합니다.TimerControls의 종료/연장 버튼은Modal.Trigger를 직접 사용해 클릭 시step을 먼저 세팅한 뒤 모달이 열립니다. 상위 체크박스처럼 버튼이 아닌 지점에서 모달을 열어야 할 때를 위해TimerSessionControls에openStopModalimperative handle을 추가하고, 내부에 숨긴Modal.Trigger를 ref로 프로그래밍적으로 클릭하는 방식을 택했습니다. 이를 위해Modal.Trigger가ref를 받을 수 있도록 최소 수정했습니다(React 19라forwardRef없이ref를 prop으로 추가).isRunning기준으로 분기했습니다. 실시간 카운트다운 로직 자체는 이번 범위 밖이라 "실제 소요 시간"은 계획 시간과 동일한 값을 임시로 사용합니다.다국어(i18n)
next-intl의Focus/Common.weekday네임스페이스로 옮겼습니다.토요일등)도 로케일과 무관하게 고정 배열에서 가져오고 있었습니다.messages/{ko,en}.json에Focus.controls,Focus.endModal,Focus.stopModal,Focus.extendModal,Focus.completeModal,Focus.duration키를 추가했습니다. 소요 시간은 60분 미만이면{m}분/{m}m, 정각이면{h}시간/{h}h, 그 외엔{h}시간 {m}분/{h}h {m}m로 변환하는formatDurationLabel유틸을 만들어 완료·중단 모달에서 공유합니다. 중단 모달의 소요 시간 값은t.rich()+<blue>태그로 강조색을 적용했습니다(기존TagLimitToastContainer와 동일한 패턴). 연장 모달의 프리셋 단위는 한국어 "분/시간", 영어 "M/H"로 Figma 원본 표기를 그대로 따라 언어별 문자열 자체를 다르게 관리합니다.Settings네임스페이스처럼 이미 있던 다른 미완성 i18n 갭은 이번 PR 범위에 포함하지 않았습니다.포커스 접근성
ring(box-shadow) 방식에서 버튼 자체border색상 전환 방식으로 바꿨습니다.ring-2방식은 흰/회색 배경 버튼에서 테두리 바깥에 별도 그림자 링이 겹쳐 보여 실제 버튼 경계와 시각적으로 불일치했습니다.ModalButton의bordervariant, 연장 모달 직접입력 인풋 컨테이너)는focus-visible:border-timo-blue-300으로 색상만 바꿉니다. 테두리가 없는 요소(연장 모달 프리셋 칩, 타이머 컨트롤 원형 버튼)는border-2 border-transparent를 기본값으로 깔아두고 포커스 시 색만 바꿔 레이아웃 흔들림 없이 표시합니다. 파란 배경의 fill 버튼(완료하기류)은 테두리 색을 바꿔도 배경과 구분이 안 되어 기존ring-2+ring-offset-2방식을 유지했습니다.상위 체크박스 연동
FocusSessionContainer의handleToggleCompleted에서completed && task.isRunning이면 상태를 바꾸지 않고timerSessionControlsRef.current?.openStopModal()만 호출합니다.Checkbox가 controlled 컴포넌트라 상태를 업데이트하지 않으면 체크 표시도 그대로 유지됩니다.To Reviewers
Modal.Trigger에ref를 추가한 부분이Modal컴포넌트의 기존 API 방향과 맞는지 봐주세요. controlled prop 추가는 피했지만, "숨은 트리거를 프로그래밍적으로 클릭"하는 방식이 다소 우회적이라 더 나은 대안이 있다면 의견 부탁드립니다."실제 소요 시간"이 실시간 카운트다운 없이 계획 시간과 동일하게 표시되는 부분은 미리 알고 계셔야 할 것 같아 남깁니다. 실제 타이머 엔진 구현은 이번 PR 범위 밖입니다.
완료 모달의 버튼 개수(현재 1개)와 관련해, Figma 파일 자체가 한국어 프레임(1개 버튼)과 영문 프레임(2개, Close+Finish)이 서로 다른 상태였습니다. 우선 기존 1개 구조를 유지했고, 어느 쪽이 최신 의도인지 확인되면 후속 처리하겠습니다.
Screenshot 📷
로컬 Playwright로
/ko/focus,/en/focus에서 아래 상태들을 직접 클릭해 확인했습니다 (이미지 업로드 환경이 아니라 표는 생략합니다).2시간/2h로 표시2h 30m형식으로 표시25분/25M) 시 포커스링이 버튼 테두리 색 전환으로 표시/en/focus에서Saturday로 표시Test Checklist ✔
pnpm --filter timo-web lint통과pnpm --filter @repo/timo-design-system lint통과pnpm --filter @repo/timo-design-system exec tsc --noEmit통과pnpm --filter timo-web exec tsc --noEmit— 이번 브랜치가 건드리지 않은onboarding아이콘 export 누락 에러 4건이develop에도 이미 있어 통과로 표시하지 않았습니다 (별도 확인 필요)pnpm build— 미실행