Skip to content

[FEAT] 집중 모드(Focus) 타이머·태스크 상세 화면 구현#116

Merged
kimminna merged 9 commits into
developfrom
feat/web/109-focus-view
Jul 8, 2026
Merged

[FEAT] 집중 모드(Focus) 타이머·태스크 상세 화면 구현#116
kimminna merged 9 commits into
developfrom
feat/web/109-focus-view

Conversation

@jjangminii

Copy link
Copy Markdown
Contributor

ISSUE 🔗

close #109



What is this PR? 🔍

Figma 집중 모드(Focus) 디자인을 기반으로 타이머 영역과 태스크 상세 영역을 구현하고, 두 영역의 재생/완료 상태를 하나의 컨테이너에서 공유하도록 만들었습니다.

배경

  • 기존 구조: focus/page.tsxTimer/TimerControls에 빈 값(icon={undefined}, time="", size="sm")만 전달하는 플레이스홀더 상태였습니다.
  • 발생 문제: 타이머와 좌측 태스크 카드를 각각 별도 컨테이너에서 독립된 로컬 상태로 구현하면, 한쪽에서 재생을 눌러도 다른 쪽에 반영되지 않는 화면 불일치가 생깁니다.
  • 해결 방향: 두 영역을 FocusSessionContainer 하나로 통합해 단일 상태(task)를 공유하고, 표현 컴포넌트(FocusTaskCard, Timer, TimerControls)는 순수 프레젠테이션으로 유지했습니다.

타이머 영역

  • 변경 요약: focus 페이지에 원형 타이머(Timer, size="lg")와 재생/종료/시간추가 컨트롤(TimerControls)을 실제로 연결했습니다.
  • 이유: page.tsx는 Server Component라 인터랙티브한 재생 상태를 가질 수 없어 별도 client 컨테이너가 필요했습니다.
  • 구현 방식: Figma의 "재생 중" 프레임을 픽셀 단위로 대조했을 때 종료·재생·시간추가 버튼 3개 전부가 강조되는 것으로 보여 한 차례 그렇게 구현했으나, 이미 사이드바용 TimerPanel.tsx([FEAT] 타임박스 타이머 우측 사이드바 구현 #90)에서 확립된 "재생 버튼만 강조" 패턴과 어긋나 다시 되돌렸습니다. 여러 화면에서 재사용되는 공용 컴포넌트라 기존 합의를 따르는 쪽을 선택했습니다.

태스크 상세 영역

  • 변경 요약: focus 페이지 가운데 영역에 날짜, 체크박스+제목, 날짜/시간 태그, 재생 버튼, 하위 테스크 리스트, 메모를 표시하는 FocusTaskCard를 추가했습니다.
  • 이유: 기존에는 이 영역이 완전히 비어 있어 Figma 디자인(node 1465-29545)과 대응되는 화면이 없었습니다.
  • 구현 방식: FocusTaskCard는 props만 받는 순수 UI(_components)이고, FocusSessionContainer가 mock 데이터(focusTaskMock)와 상태 변경 로직을 소유합니다. 상위 체크박스를 체크하면 하위 테스크 전체가 함께 체크되지만, 하위 테스크를 체크한다고 상위 상태가 바뀌지는 않는 단방향 cascade로 구현했습니다. 날짜/시간 포맷 로직은 home 도메인의 유사 유틸과 겹치지만, 도메인 간 직접 import를 금지하는 컨벤션(docs/architecture/structure.md) 때문에 focus/_utils에 별도로 작성했습니다.
  • 경계 · 제약: 실제 API/타이머 엔진 연동은 이번 PR 범위 밖이며 모든 데이터는 focusTaskMock(단일 mock task) 기준입니다. "오늘 투두 전체 완료" / "수행할 투두 없음" empty state(Figma node 1762-43383)는 프로토타입까지 구현했으나 백엔드 API가 준비되지 않아 이번 PR에서는 롤백하고 후속 작업으로 남겼습니다.

재생 상태 동기화

  • 변경 요약: 태스크 카드의 재생 버튼과 타이머 컨트롤의 재생 버튼이 같은 상태(task.isRunning)를 공유하고, 타이머의 종료 버튼을 누르면 태스크와 하위 테스크가 모두 완료 처리되도록 연결했습니다.
  • 이유: 두 영역이 사실상 하나의 세션을 나타내는데 서로 다른 상태를 가지면 화면 간 불일치가 생깁니다.
  • 구현 방식: FocusSessionContainerhandleTogglePlay/handleEndtask 상태를 갱신하고, 이 값을 FocusTaskCard.isRunningTimerControls.isRunning에 동일하게 전달합니다.



To Reviewers

타이머/태스크 두 영역을 FocusSessionContainer 하나로 합친 구조가 적절한지 봐주세요. 실제 투두 리스트 API가 붙으면 이 컨테이너가 데이터 페칭(useQuery)까지 맡게 될 예정입니다.
FocusTaskCard의 날짜/시간 포맷 유틸을 home 도메인과 별도로 만들었는데(도메인 경계 준수 목적), 중복이 계속 늘어나면 공유 lib로 추출이 필요할 수 있습니다.
"오늘 투두 전체 완료" / "수행할 투두 없음" empty state는 API 연동 전까지 보류했으며 후속 PR로 진행할 예정입니다.



Screenshot 📷

로컬 dev 서버(/ko/focus)에서 Playwright로 직접 캡처해 정지/재생 상태, 체크박스 cascade, 긴 메모 줄바꿈 동작을 확인했습니다. 이미지 파일은 로컬 스크린샷이라 이 PR 본문에는 첨부하지 않았습니다.



Test Checklist ✔

  • pnpm check-types 통과
  • pnpm lint 통과
  • /ko/focus 로컬 dev 서버 렌더링 확인 (Playwright)
  • 상위 체크박스 → 하위 테스크 전체 cascade 동작 확인
  • 태스크 카드 재생 버튼 ↔ 타이머 컨트롤 재생 버튼 동기화 확인
  • 타이머 종료 버튼 → 체크박스/하위 테스크 자동 완료 확인
  • pnpm build — 미실행: CI에서 확인 예정
  • 실제 API 연동 후 재검증 — 후속 작업

- Figma 디자인을 기반으로 focus 페이지에 타이머(재생/일시정지/시간추가) 영역을 구현했습니다
- Figma 디자인 대조 결과 재생 중 상태에서는 재생 버튼뿐 아니라 종료·시간추가 버튼도 함께 강조되어야 함을 확인하여 수정했습니다
- w-[546px] arbitrary value를 동일한 값의 스페이싱 스케일 단위(w-136.5)로 변경했습니다
…to feat/web/109-focus-view

# Conflicts:
#	apps/timo-web/app/[locale]/(main)/focus/page.tsx
- Figma 디자인을 기반으로 태스크 상세 영역(날짜, 체크박스, 하위 테스크, 메모, 재생 버튼)을 구현했습니다
- FocusTaskContainer와 FocusTimerContainer를 FocusSessionContainer로 통합해 재생/완료 상태를 공유하도록 했습니다
- 재생 중에도 종료·시간추가 버튼은 회색을 유지하고 재생 버튼만 강조되도록 이전 변경을 되돌렸습니다
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 8, 2026 6:18am

@github-actions github-actions Bot requested review from kimminna and yumin-kim2 July 7, 2026 18:57
@github-actions github-actions Bot added ⏰ Timo-web Timo 웹 서비스 ✨ Feature 새로운 기능(기능성) 구현 ♠️ 정민 정민양 labels Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 22 minutes

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

How can I continue?

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

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 509f73cc-1578-461d-b6c2-5e712fbb1869

📥 Commits

Reviewing files that changed from the base of the PR and between aab8d25 and 45e5e41.

📒 Files selected for processing (2)
  • apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskItem.tsx
  • apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx

Walkthrough

Focus 페이지에 세션 화면이 추가되었습니다. 태스크 타입, 목데이터, 날짜·시간 포맷 유틸, 카드 UI, 상태 컨테이너가 새로 연결되고, 페이지는 새 컨테이너를 렌더링합니다.

Changes

Focus 세션 UI 구현

Layer / File(s) Summary
FocusTask 타입, 목데이터, 포맷 유틸
apps/timo-web/app/[locale]/(main)/focus/_types/task-type.ts, apps/timo-web/app/[locale]/(main)/focus/_mocks/task-mock.ts, apps/timo-web/app/[locale]/(main)/focus/_utils/date.ts, apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts
FocusTaskSubtask/FocusTask 인터페이스, focusTaskMock, 날짜 표기 유틸, HH:MM 소요시간 변환 유틸이 추가되었다.
FocusTaskCard 렌더링과 토글 연결
apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskCard.tsx
날짜/요일, 완료 체크박스, 실행 버튼, 서브태스크 목록, 조건부 메모를 렌더링하고 각 토글 콜백을 연결한다.
FocusSessionContainer 상태 관리 및 화면 연결
apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx, apps/timo-web/app/[locale]/(main)/focus/page.tsx
task 상태와 토글 핸들러를 구현하고, 카드·타이머·컨트롤을 조합한 뒤 page.tsx가 새 컨테이너를 렌더링하도록 변경된다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FocusTaskCard
  participant FocusSessionContainer
  participant TimerControls

  User->>FocusTaskCard: 완료/재생/서브태스크 체크
  FocusTaskCard->>FocusSessionContainer: 상태 토글 콜백 호출
  FocusSessionContainer->>FocusSessionContainer: task 상태 갱신
  FocusSessionContainer->>TimerControls: 종료/재생/시간추가 콜백 전달
  FocusSessionContainer->>FocusTaskCard: 갱신된 task 다시 전달
Loading

Possibly related PRs

  • Team-Timo/Timo-client#111: 같은 Focus 라우트에서 FocusHeaderContainer 대신 새 세션 컨테이너를 렌더링하도록 바꾸는 흐름과 직접 연결됩니다.

Suggested labels: ♦️ 민아

Suggested reviewers: ehye1, yumin-kim2

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 좌측 상세+우측 타이머 레이아웃은 구현했지만, 최상단 투두 연동·다음 투두 전환·인라인 수정·플레이스홀더 등 핵심 요구가 대부분 남았습니다. 원문 TODO 중 우선순위가 높은 요구를 먼저 구현하고, 미지원 항목은 후속 PR로 분리해 범위를 명확히 하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 Focus 타이머와 태스크 상세 화면 구현이라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed 설명이 타이머·태스크 상세 화면 구현과 상태 공유를 잘 설명해 변경 내용과 일치합니다.
Out of Scope Changes check ✅ Passed 추가된 타입, mock, 유틸, 카드, 컨테이너는 모두 Focus 페이지 구현을 뒷받침하는 범위 안에 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web/109-focus-view

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

❤️ Share

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

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale] 0 B 🟡 205.79 kB
/[locale]/focus 0 B 🟡 205.79 kB
/[locale]/home 0 B 🟡 205.79 kB
/[locale]/onboarding 0 B 🟡 205.79 kB
/[locale]/settings 0 B 🟡 205.79 kB
/[locale]/settings/account 0 B 🟡 205.79 kB
/[locale]/settings/policy 0 B 🟡 205.79 kB
/[locale]/statistics 0 B 🟡 205.79 kB
/[locale]/today 0 B 🟡 205.79 kB

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

Lighthouse — timo-web

⚠️ Lighthouse 결과를 가져오지 못했습니다.

Image Optimization — timo-web

public/ 디렉토리에 이미지가 없습니다.

측정 커밋: a3098d9

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/_components/FocusTaskCard.tsx:
- Around line 42-112: The top-level wrapper in FocusTaskCard should use a
semantic container instead of a plain div to better represent this as a task
card. Replace the outermost container in FocusTaskCard with an appropriate
semantic tag such as article or section, keeping the existing layout and styling
classes intact. Make sure the change is applied only to the card root so the
inner structure and handlers remain unchanged.

In
`@apps/timo-web/app/`[locale]/(main)/focus/_containers/FocusSessionContainer.tsx:
- Line 42: The handleAddTime placeholder in FocusSessionContainer is left as an
empty function, unlike the other handlers that clearly mark pending work with a
TODO comment. Update handleAddTime to match the existing handler pattern in
FocusSessionContainer by adding an explicit TODO marker (or otherwise making the
intentional stub obvious) so it is clear this is unfinished work rather than an
accidental no-op.
- Around line 67-69: The top-level wrapper in FocusSessionContainer should use a
semantic main landmark instead of a plain div. Update the outer container
returned by FocusSessionContainer so the page’s primary content is wrapped with
main while preserving the existing layout classes and nested structure. Keep the
change localized to the FocusSessionContainer component and ensure the inner
flex container remains unchanged.

In `@apps/timo-web/app/`[locale]/(main)/focus/_types/task-type.ts:
- Line 4: The boolean field name in the Task type does not follow the
is/has/should/can naming convention. Rename the `completed` property in `Task`
to `isCompleted`, then update every usage that reads or writes this field in
`task-mock.ts` and `FocusTaskCard.tsx` so the type and consumers stay aligned
with the new name.
🪄 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: aa5d5197-78ff-4b30-af3a-ff1e1048ae30

📥 Commits

Reviewing files that changed from the base of the PR and between cf96425 and c4a2457.

📒 Files selected for processing (7)
  • apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskCard.tsx
  • apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx
  • apps/timo-web/app/[locale]/(main)/focus/_mocks/task-mock.ts
  • apps/timo-web/app/[locale]/(main)/focus/_types/task-type.ts
  • apps/timo-web/app/[locale]/(main)/focus/_utils/date.ts
  • apps/timo-web/app/[locale]/(main)/focus/_utils/duration.ts
  • apps/timo-web/app/[locale]/(main)/focus/page.tsx

Comment on lines +42 to +112
<div className="flex w-full min-w-0 flex-col items-start gap-5 px-[34.5px]">
<div className="text-timo-gray-900 flex flex-col items-start">
<p className="typo-headline-b-30">{dayNumber}</p>
<p className="typo-headline-m-14">{dayOfWeek}</p>
</div>

<div className="flex w-full items-center gap-2.5">
<Checkbox checked={completed} onChange={onToggleCompleted} />
<p className="typo-headline-b-22 text-timo-black min-w-0 flex-1 wrap-break-word">
{title}
</p>
</div>

<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2">
<div className="flex items-center gap-0.5">
<CalendarOnIcon width={22} height={22} />
<span className="typo-caption-r-10 text-timo-gray-900 whitespace-nowrap">
{dateText}
</span>
</div>

<div className="flex items-center gap-0.5">
<ClockOnIcon width={22} height={22} />
<span className="typo-caption-r-10 text-timo-gray-900 w-9 text-center whitespace-nowrap">
{durationText}
</span>
</div>
</div>

<PlayButton
variant={isRunning ? "stop" : "play"}
size="lg"
onClick={onTogglePlay}
>
{isRunning ? (
<StopIcon width={24} height={24} />
) : (
<PlayIcon width={24} height={24} />
)}
</PlayButton>
</div>

<div className="border-timo-gray-500 flex min-h-26 w-full min-w-0 flex-col gap-3 border-t pt-3">
{subtasks.map((subtask) => (
<div key={subtask.subtaskId} className="flex items-center gap-2">
<Checkbox
checked={subtask.completed}
onChange={(checked) =>
onToggleSubtaskCompleted(subtask.subtaskId, checked)
}
/>
<p
className={cn(
"typo-body-r-12 min-w-0 flex-1 wrap-break-word",
subtask.completed ? "text-timo-gray-700" : "text-timo-black",
)}
>
{subtask.content}
</p>
</div>
))}

{memo && (
<p className="typo-body-r-12 text-timo-gray-800 min-w-0 wrap-break-word">
{memo}
</p>
)}
</div>
</div>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

카드 전체를 감싸는 div, semantic 태그로 바꿔볼까요?

최상위 컨테이너가 패딩/레이아웃 스타일을 가진 시각적 컨테이너라 div 자체는 허용 범위지만, 태스크 "카드"라는 의미 단위를 표현하려면 <article>이나 <section>을 쓰는 게 접근성 트리 상 더 명확해요.

As per path instructions, "div 남발 금지 → main, article, section, nav, header, footer 등 시맨틱 태그 사용".

♻️ 제안 diff
-    <div className="flex w-full min-w-0 flex-col items-start gap-5 px-[34.5px]">
+    <article className="flex w-full min-w-0 flex-col items-start gap-5 px-[34.5px]">
       ...
-    </div>
+    </article>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex w-full min-w-0 flex-col items-start gap-5 px-[34.5px]">
<div className="text-timo-gray-900 flex flex-col items-start">
<p className="typo-headline-b-30">{dayNumber}</p>
<p className="typo-headline-m-14">{dayOfWeek}</p>
</div>
<div className="flex w-full items-center gap-2.5">
<Checkbox checked={completed} onChange={onToggleCompleted} />
<p className="typo-headline-b-22 text-timo-black min-w-0 flex-1 wrap-break-word">
{title}
</p>
</div>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2">
<div className="flex items-center gap-0.5">
<CalendarOnIcon width={22} height={22} />
<span className="typo-caption-r-10 text-timo-gray-900 whitespace-nowrap">
{dateText}
</span>
</div>
<div className="flex items-center gap-0.5">
<ClockOnIcon width={22} height={22} />
<span className="typo-caption-r-10 text-timo-gray-900 w-9 text-center whitespace-nowrap">
{durationText}
</span>
</div>
</div>
<PlayButton
variant={isRunning ? "stop" : "play"}
size="lg"
onClick={onTogglePlay}
>
{isRunning ? (
<StopIcon width={24} height={24} />
) : (
<PlayIcon width={24} height={24} />
)}
</PlayButton>
</div>
<div className="border-timo-gray-500 flex min-h-26 w-full min-w-0 flex-col gap-3 border-t pt-3">
{subtasks.map((subtask) => (
<div key={subtask.subtaskId} className="flex items-center gap-2">
<Checkbox
checked={subtask.completed}
onChange={(checked) =>
onToggleSubtaskCompleted(subtask.subtaskId, checked)
}
/>
<p
className={cn(
"typo-body-r-12 min-w-0 flex-1 wrap-break-word",
subtask.completed ? "text-timo-gray-700" : "text-timo-black",
)}
>
{subtask.content}
</p>
</div>
))}
{memo && (
<p className="typo-body-r-12 text-timo-gray-800 min-w-0 wrap-break-word">
{memo}
</p>
)}
</div>
</div>
);
<article className="flex w-full min-w-0 flex-col items-start gap-5 px-[34.5px]">
<div className="text-timo-gray-900 flex flex-col items-start">
<p className="typo-headline-b-30">{dayNumber}</p>
<p className="typo-headline-m-14">{dayOfWeek}</p>
</div>
<div className="flex w-full items-center gap-2.5">
<Checkbox checked={completed} onChange={onToggleCompleted} />
<p className="typo-headline-b-22 text-timo-black min-w-0 flex-1 wrap-break-word">
{title}
</p>
</div>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-2">
<div className="flex items-center gap-0.5">
<CalendarOnIcon width={22} height={22} />
<span className="typo-caption-r-10 text-timo-gray-900 whitespace-nowrap">
{dateText}
</span>
</div>
<div className="flex items-center gap-0.5">
<ClockOnIcon width={22} height={22} />
<span className="typo-caption-r-10 text-timo-gray-900 w-9 text-center whitespace-nowrap">
{durationText}
</span>
</div>
</div>
<PlayButton
variant={isRunning ? "stop" : "play"}
size="lg"
onClick={onTogglePlay}
>
{isRunning ? (
<StopIcon width={24} height={24} />
) : (
<PlayIcon width={24} height={24} />
)}
</PlayButton>
</div>
<div className="border-timo-gray-500 flex min-h-26 w-full min-w-0 flex-col gap-3 border-t pt-3">
{subtasks.map((subtask) => (
<div key={subtask.subtaskId} className="flex items-center gap-2">
<Checkbox
checked={subtask.completed}
onChange={(checked) =>
onToggleSubtaskCompleted(subtask.subtaskId, checked)
}
/>
<p
className={cn(
"typo-body-r-12 min-w-0 flex-1 wrap-break-word",
subtask.completed ? "text-timo-gray-700" : "text-timo-black",
)}
>
{subtask.content}
</p>
</div>
))}
{memo && (
<p className="typo-body-r-12 text-timo-gray-800 min-w-0 wrap-break-word">
{memo}
</p>
)}
</div>
</article>
🤖 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/_components/FocusTaskCard.tsx around
lines 42 - 112, The top-level wrapper in FocusTaskCard should use a semantic
container instead of a plain div to better represent this as a task card.
Replace the outermost container in FocusTaskCard with an appropriate semantic
tag such as article or section, keeping the existing layout and styling classes
intact. Make sure the change is applied only to the card root so the inner
structure and handlers remain unchanged.

Source: Path instructions

}));
};

const handleAddTime = () => {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

handleAddTime, 조용히 비어있네요 (TODO 주석마저 없이).

다른 핸들러들은 // TODO: API 주석으로 나중에 작업임을 표시해뒀는데, handleAddTime만 빈 화살괄호로 덩그러니 있어요. 리뷰어가 "이거 의도된 거 맞나?" 갸우뚱하게 만드는 포인트입니다.

-  const handleAddTime = () => {};
+  const handleAddTime = () => {
+    // TODO: 시간 추가 로직 (API 연동 및 타이머 엔진 연결 후속 작업)
+  };

원하시면 이 부분 이슈로 등록해드릴까요?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleAddTime = () => {};
const handleAddTime = () => {
// TODO: 시간 추가 로직 (API 연동 및 타이머 엔진 연결 후속 작업)
};
🤖 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
at line 42, The handleAddTime placeholder in FocusSessionContainer is left as an
empty function, unlike the other handlers that clearly mark pending work with a
TODO comment. Update handleAddTime to match the existing handler pattern in
FocusSessionContainer by adding an explicit TODO marker (or otherwise making the
intentional stub obvious) so it is clear this is unfinished work rather than an
accidental no-op.

Comment thread apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx Outdated
export interface FocusTaskSubtask {
subtaskId: number;
content: string;
completed: boolean;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Boolean 네이밍 컨벤션 살짝 아쉬워요 😅

completedis/has/should/can 접두사가 없어서 컨벤션 위반이에요. isCompleted로 바꾸면 isRunning과도 짝이 맞아서 훨씬 자연스러워집니다. 이 타입을 task-mock.tsFocusTaskCard.tsx에서도 그대로 쓰고 있어서, 한 번에 고치면 전체가 정리돼요.

As per path instructions, "Boolean 변수명: is, has, should, can 접두사".

♻️ 제안 diff
 export interface FocusTaskSubtask {
   subtaskId: number;
   content: string;
-  completed: boolean;
+  isCompleted: boolean;
 }

 export interface FocusTask {
   taskId: number;
   title: string;
-  completed: boolean;
+  isCompleted: boolean;
   scheduledDate: Date;
   durationSeconds: number;
   isRunning: boolean;
   subtasks: FocusTaskSubtask[];
   memo?: string;
 }

Also applies to: 10-10

🤖 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/_types/task-type.ts at line 4, The
boolean field name in the Task type does not follow the is/has/should/can naming
convention. Rename the `completed` property in `Task` to `isCompleted`, then
update every usage that reads or writes this field in `task-mock.ts` and
`FocusTaskCard.tsx` so the type and consumers stay aligned with the new name.

Source: Path instructions

Comment thread apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx Outdated
Comment thread apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx Outdated

@ehye1 ehye1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Image

반응형에서 확인해보니 해당 영역이 깨지는 것 같아요! Timer 영역 위로 요소들이 겹쳐 올라오는 것 같은데 이 부분도 한 번 확인 부탁드랴요!!
수고하셨습니다👍🏻💗

- 태스크 카드가 무한정 찌그러지지 않도록 최소 너비를 지정했습니다
- 화면이 좁아지면 축소 대신 페이지에 가로 스크롤이 생기도록 했습니다

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx (2)

89-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Timer props가 하드코딩되어 있습니다 — 의도적인지 확인 부탁드립니다.

time="10:30", plannedLabel="12M", progress={70}이 정적으로 하드코딩되어 있습니다. PR objectives에 "mock data for now"라고 명시되어 있어 의도적일 수 있으나, task.durationSeconds에서 파생된 값이나 별도 목데이터 상수로 관리하면 후속 API 연동 시 교체 포인트가 명확해집니다.

♻️ 제안: 목데이터 상수로 분리
+ // _mocks/timer-mock.ts 또는 상단에 정의
+ const timerMock = {
+   time: "10:30",
+   plannedLabel: "12M",
+   progress: 70,
+ } as const;

       <Timer
-        icon={<TimerOnIcon />}
-        time="10:30"
-        plannedLabel="12M"
-        progress={70}
+        icon={<TimerOnIcon />}
+        time={timerMock.time}
+        plannedLabel={timerMock.plannedLabel}
+        progress={timerMock.progress}
         size="lg"
       />
🤖 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 89 - 95, The Timer props in FocusSessionContainer are currently
hardcoded, so either confirm this is intentional mock data or move the values
into a dedicated mock-data constant or derive them from task.durationSeconds.
Update the Timer usage in FocusSessionContainer and any related helper logic so
time, plannedLabel, and progress come from a single replaceable source rather
than inline literals.

22-22: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

new Date()가 매 렌더마다 새로 생성됩니다.

const today = new Date()는 컴포넌트가 리렌더링될 때마다 새 Date 객체를 생성합니다. 타이머 토글 등 상태 변경 시 불필요한 재할당이 발생합니다. useMemo로 감싸면 날짜 값이 동일한 렌더 사이클에서 안정적으로 유지됩니다.

참고: React useMemo 공식 문서

♻️ 제안 diff
+import { useMemo, useState } from "react";

 export const FocusSessionContainer = () => {
   const [task, setTask] = useState<FocusTask>(focusTaskMock);
-  const today = new Date();
+  const today = useMemo(() => new Date(), []);
🤖 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
at line 22, `FocusSessionContainer`에서 생성되는 `today` 값이 매 렌더마다 새 Date 객체로 할당되고
있습니다. 이 값을 컴포넌트 렌더 사이클 동안 안정적으로 유지하도록 `FocusSessionContainer` 내부의 `today` 계산을
`useMemo`로 감싸고, 의존성이 없으면 한 번만 생성되도록 조정하세요. `today`를 사용하는 로직 전체가 동일한 memoized 값을
참조하도록 맞춰 주세요.
apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskCard.tsx (1)

85-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

서브태스크 영역도 시맨틱 태그를 고려해보세요.

서브태스크 목록을 감싸는 <div>(line 85)와 각 서브태스크 아이템의 <div>(line 87)가 의미론적으로는 리스트 구조입니다. <ul>/<li>를 사용하면 스크린 리더에서 "목록, N개 항목"으로 읽어주어 접근성이 개선됩니다.

♻️ 제안 diff
-      <div className="border-timo-gray-500 flex min-h-26 w-full min-w-0 flex-col gap-3 border-t pt-3">
+      <ul className="border-timo-gray-500 flex min-h-26 w-full min-w-0 flex-col gap-3 border-t pt-3">
         {subtasks.map((subtask) => (
-          <div key={subtask.subtaskId} className="flex items-center gap-2">
+          <li key={subtask.subtaskId} className="flex items-center gap-2">
             ...
-          </div>
+          </li>
         ))}
         ...
-      </div>
+      </ul>
🤖 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/_components/FocusTaskCard.tsx around
lines 85 - 103, The subtask section in FocusTaskCard is rendered with generic
containers even though it is a list structure. Update the wrapper and item
markup in FocusTaskCard to use semantic list elements like a list container and
list items around the subtasks map, while keeping the existing Checkbox,
subtaskId keying, and onToggleSubtaskCompleted behavior unchanged.

Source: Path instructions

♻️ Duplicate comments (1)
apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskCard.tsx (1)

42-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

과거 리뷰에서 제안된 <article> 시맨틱 태그 전환이 아직 반영되지 않았습니다.

이전 리뷰에서 최상위 <div><article>로 변경하라는 제안이 있었으나, 현재 코드는 여전히 <div>를 사용하고 있습니다. 다만 path instructions에 따르면 "컴포넌트 자체가 시각적 컨테이너(배경색·크기·border-radius 등 스타일을 정의)인 경우는 div 허용"이므로, 현재 px-[34.5px] py-8 패딩만 있고 배경색·border-radius 등은 없어 시각적 컨테이너라기보단 그룹핑 목적에 가깝습니다. <article>이 접근성 트리에서 태스크 카드라는 의미 단위를 더 명확히 전달합니다.

As per path instructions, "div 남발 금지 → main, article, section, nav, header, footer 등 시맨틱 태그 사용".

♻️ 제안 diff
-    <div className="flex w-full min-w-80 flex-col items-start gap-5 px-[34.5px] py-8">
+    <article className="flex w-full min-w-80 flex-col items-start gap-5 px-[34.5px] py-8">
       ...
-    </div>
+    </article>
🤖 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/_components/FocusTaskCard.tsx at
line 42, Update FocusTaskCard so the top-level wrapper is an <article> instead
of the current <div>, since this component represents a meaningful task card
rather than a purely visual container. Keep the existing styling and structure
intact, but change the outer element in FocusTaskCard to the semantic tag that
better identifies the card as a self-contained content unit.

Source: Path instructions

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

Outside diff comments:
In `@apps/timo-web/app/`[locale]/(main)/focus/_components/FocusTaskCard.tsx:
- Around line 85-103: The subtask section in FocusTaskCard is rendered with
generic containers even though it is a list structure. Update the wrapper and
item markup in FocusTaskCard to use semantic list elements like a list container
and list items around the subtasks map, while keeping the existing Checkbox,
subtaskId keying, and onToggleSubtaskCompleted behavior unchanged.

In
`@apps/timo-web/app/`[locale]/(main)/focus/_containers/FocusSessionContainer.tsx:
- Around line 89-95: The Timer props in FocusSessionContainer are currently
hardcoded, so either confirm this is intentional mock data or move the values
into a dedicated mock-data constant or derive them from task.durationSeconds.
Update the Timer usage in FocusSessionContainer and any related helper logic so
time, plannedLabel, and progress come from a single replaceable source rather
than inline literals.
- Line 22: `FocusSessionContainer`에서 생성되는 `today` 값이 매 렌더마다 새 Date 객체로 할당되고
있습니다. 이 값을 컴포넌트 렌더 사이클 동안 안정적으로 유지하도록 `FocusSessionContainer` 내부의 `today` 계산을
`useMemo`로 감싸고, 의존성이 없으면 한 번만 생성되도록 조정하세요. `today`를 사용하는 로직 전체가 동일한 memoized 값을
참조하도록 맞춰 주세요.

---

Duplicate comments:
In `@apps/timo-web/app/`[locale]/(main)/focus/_components/FocusTaskCard.tsx:
- Line 42: Update FocusTaskCard so the top-level wrapper is an <article> instead
of the current <div>, since this component represents a meaningful task card
rather than a purely visual container. Keep the existing styling and structure
intact, but change the outer element in FocusTaskCard to the semantic tag that
better identifies the card as a self-contained content unit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 391371a9-c818-4aa0-b2c7-c5a361038e42

📥 Commits

Reviewing files that changed from the base of the PR and between c4a2457 and aab8d25.

📒 Files selected for processing (2)
  • apps/timo-web/app/[locale]/(main)/focus/_components/FocusTaskCard.tsx
  • apps/timo-web/app/[locale]/(main)/focus/_containers/FocusSessionContainer.tsx

- py-8(상하 패딩)을 pb-8(하단 패딩만)로 변경했습니다
- 카드보다 아이템에 가까운 컴포넌트라 용도에 맞게 리네임했습니다
- 루트 태그를 div에서 section으로 변경했습니다
@kimminna kimminna merged commit 32cb8ce into develop Jul 8, 2026
11 checks passed
@kimminna kimminna deleted the feat/web/109-focus-view branch July 8, 2026 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능(기능성) 구현 ⏰ Timo-web Timo 웹 서비스 ♠️ 정민 정민양

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] Focus 뷰(집중 모드) 페이지 구현

3 participants