Skip to content

feat: 사이드 프로젝트 스프린트 보드·진행률 차트 DB 연동 - #44

Merged
Kwon812 merged 25 commits into
developfrom
feat/#41/side-project-sprint-backend
Jul 13, 2026
Merged

feat: 사이드 프로젝트 스프린트 보드·진행률 차트 DB 연동 #44
Kwon812 merged 25 commits into
developfrom
feat/#41/side-project-sprint-backend

Conversation

@Kwon812

@Kwon812 Kwon812 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Pull Request

작업 내용

  • 사이드 프로젝트 스프린트 보드를 Mock에서 실제 Supabase 연동으로 전환했습니다. (읽기 + 쓰기 전체)
  • 진행률 차트도 같은 sprint/task 엔티티를 쓰기 때문에 함께 연동했습니다. 별도 데이터 소스 없이 스프린트 보드와 동일한 useSprints(get_sprints RPC) / useSprintTasks 훅을 재사용해, 스프린트 카드·벨로시티·상태 분포가 모두 실 DB에서 파생됩니다.
  • 읽기는 tanstack-query(useQuery), 쓰기는 서버액션 + useMutation(단일 컬럼 부분 수정은 클라 직접 update)으로 컨벤션(docs/conventions/supabase-convention.md)에 맞춰 배선했습니다.

작업 결과

  • 스프린트 보드: 실 DB 스프린트/업무/백로그 조회, 업무 생성·수정·삭제, 칸반 DnD(상태 이동, 낙관적 업데이트), 백로그 → 현재 스프린트 편입, 스프린트 생성·수정·삭제.
  • 진행률 차트: 실 DB 기준 현재 스프린트 카드·벨로시티·상태 분포 도넛 표시.
  • 담당자 표시는 워크스페이스 닉네임(workspace_members)으로 일관 표시, 담당자 지정 시 실제 assignee_id 저장.
  • DB 테이블 스키마 변경 없음 — 집계용 get_sprints 읽기 RPC(additive)만 추가.

변경 사항

Added

  • get_sprints RPC (supabase/migrations/..._create_sprint_rpcs.sql) — 스프린트 목록 + 포인트 집계(total/completed) + days_left를 단일 쿼리로 반환 (N+1 없음, 테이블 스키마 불변).
  • DB row ↔ 엔티티 매퍼toTask / toSprint(읽기), toTaskInsert/toTaskUpdate, toSprintInsert/toSprintUpdate(쓰기). enum은 GenericEnums로 파생(리터럴 중복 제거, §6).
  • 읽기 훅useSprints / useSprintTasks / useBacklogTasks (useQuery + 브라우저 클라이언트).
  • 쓰기 경로createTask/updateTask/deleteTask 서버액션(zod 재검증) + useCreateTask/useUpdateTask/useDeleteTask. DnD 상태 이동·백로그 편입은 클라 직접 update(updateTaskStatus/updateTaskSprint, §4).
  • 스프린트 CRUDmanage-sprints 피처(SprintToolbar/SprintFormDialog/SprintDeleteDialog) + create/update/delete-sprint 서버액션 + 뮤테이션. 날짜 검증(종료일 ≥ 시작일).
  • 백로그 → 스프린트 편입 버튼 (update-task-sprint).
  • 진행률 차트: ProgressChartViewuseSprints/useSprintTasks + selectVelocity/countByStatus로 실 DB 파생.

Changed

  • 페이지 RSC → client 전환 — 스프린트 보드/진행률 차트 페이지는 얇은 RSC(존재/purpose 판정, members 조회)로 두고, 데이터 조회·조립은 client View(useQuery)가 담당.
  • 담당자 모델 정규화Task.assignee{ name, avatarLabel }assigneeId. 표시명은 task 쿼리의 profiles 조인 대신 members(닉네임)에서 해석(BoardTask). 워크스페이스 닉네임 단일 출처.
  • members 실연동 — RSC에서 getWorkspaceMembersByWorkspaceId 조회 후 prop 주입(mock 제거).
  • 피처 리네임features/sprint-boardfeatures/manage-sprint-tasks(태스크 CRUD), 스프린트 CRUD는 features/manage-sprints로 분리.
  • 쓰기 성공 시 ['tasks']/['sprints'] 쿼리 무효화로 보드·차트·포인트 집계 동기화.

Fixed

  • 담당자 이름 불일치 — 기존엔 로드 시 real_name(프로필), 선택 시 workspace_nickname으로 표시가 갈리던 것을 닉네임으로 통일.
  • 스프린트 없을 때 생성 버튼이 안 보이던 문제 — 빈 상태에도 새 스프린트 노출.

실행화면

스크린샷 2026-07-13 오후 12 42 48 스크린샷 2026-07-13 오후 12 42 07 스크린샷 2026-07-13 오후 12 42 28 스크린샷 2026-07-13 오후 12 42 16

테스트

  • 로컬 실행 확인
  • 주요 시나리오 확인 (스프린트/업무 CRUD, DnD, 백로그 편입, 스프린트 CRUD, 진행률 차트 표시)
  • 영향 범위 확인 (대시보드 위젯은 mock 유지 — 별도 연동 대상)
  • npm run typecheck / lint / format:check 통과 (변경 파일 기준)

리뷰 체크리스트

  • PR base branch가 올바릅니다. (feature/* -> develop)
  • 브랜치명이 Type/#issue-number/description 형식을 따릅니다. (feat/#41/side-project-sprint-backend)
  • 커밋 메시지가 컨벤션을 따릅니다.
  • 불필요한 console.log, 주석, 임시 코드를 제거했습니다.
  • 타입 에러와 린트 에러를 확인했습니다.
  • CodeRabbit 1차 리뷰를 확인했습니다.
  • CodeRabbit 리뷰 반영 후 Discord에 공유했습니다.
  • 최소 1명 이상의 approve 후 merge합니다.

리뷰 요청사항

  • 읽기/쓰기 경로 분리가 컨벤션과 맞는지: 조회 = useQuery, 쓰기(생성/수정/삭제) = 서버액션, 단일 컬럼 부분 수정(DnD 상태·백로그 편입) = 클라 직접 update(§4).
  • 담당자 표시를 members(닉네임)에서 해석하는 구조(BoardTask) — task 쿼리에서 profiles 조인을 제거하고 assigneeId만 두는 방향이 적절한지.
  • get_sprints RPC 집계(포인트/days_left)와 무효화 시점(쓰기 후 ['sprints']/['tasks'])이 진행률 차트까지 잘 반영되는지.
  • members는 RSC prop(정적 참조), sprint/task는 client useQuery(변동 잦음)로 나눈 판단.

관련 이슈

Closes #41

Summary by CodeRabbit

  • 새 기능
    • 사이드 프로젝트 워크스페이스에 스프린트 보드와 진행률(Progress) 차트를 추가했습니다.
    • 스프린트/업무의 생성·수정·삭제, 상태 변경, 스프린트 내 이동 및 백로그를 현재 스프린트로 이동을 지원합니다.
  • 개선
    • 칸반 화면이 선택 스프린트 기준으로 동작하며, 업무/스프린트가 서버 데이터로 갱신됩니다.
    • 업무 담당자 선택/표시 방식을 개선했으며, 드래그 앤 드롭으로 상태 이동이 가능합니다.
    • 캘린더 화면 스타일과 일부 UI 흐름을 다듬었습니다.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

사이드 프로젝트의 스프린트·태스크 기능을 Mock 기반에서 Supabase와 React Query 기반으로 전환했습니다. 스프린트 보드 CRUD·DnD·백로그 이동, 진행률 차트, 라우팅과 관련 UI·타입·매퍼를 추가하거나 갱신했습니다.

Changes

사이드 프로젝트 기능 연동

Layer / File(s) Summary
라우팅과 데이터 계약
src/app/workspaces/..., src/entities/side-project/{sprint,task}/model/*, supabase/migrations/*, src/shared/model/database.types.ts
사이드 프로젝트 라우트, 스프린트 RPC, DB 타입, 입력 스키마, 엔티티 매퍼와 enum 기반 태스크 타입을 추가했습니다.
Supabase 조회와 변경 API
src/entities/side-project/{sprint,task}/api/*, src/entities/side-project/{sprint,task}/index.ts
스프린트·태스크 조회를 Supabase로 전환하고 생성·수정·삭제·상태 변경 서버 액션과 React Query 훅을 추가했습니다.
스프린트 보드 상호작용
src/features/manage-sprint-tasks/*, src/features/manage-sprints/*, src/views/side-project/sprint-board/*
멤버 기반 담당자 표시, 폼, DnD, 백로그 이동, 스프린트 관리 다이얼로그와 클라이언트 조회 흐름을 연결했습니다.
진행률 차트 화면
src/views/side-project/progress-chart/*, src/views/progress-chart/*, src/app/workspaces/.../progress-chart/*
현재 스프린트의 포인트 통계, 상태 도넛 차트, 벨로시티 차트와 로딩·오류·빈 상태 UI를 추가했습니다.
캘린더와 대시보드 지원 변경
src/features/manage-calendar/*, src/widgets/side-project/*
캘린더 스타일을 조정하고 대시보드 위젯의 태스크·백로그·벨로시티 데이터 소스를 변경했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • TeampleRun/syncly#27 — 스프린트 보드의 초기 RSC seed 흐름을 선택 스프린트 및 클라이언트 조회 방식으로 대체한 변경과 연결됩니다.
  • TeampleRun/syncly#17 — 워크스페이스 목적별 리다이렉트 로직과 직접 연결됩니다.
  • TeampleRun/syncly#35 — 캘린더 페이지의 비동기 params 처리 및 캘린더 기능 변경과 연결됩니다.

Suggested reviewers: 0011810, jiwoonge, seongjinss555

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 이슈 #41의 쓰기 경로는 서버 액션+useMutation을 요구하는데, 상태/백로그 편입은 클라이언트 직접 update로 구현돼 요구와 다릅니다. updateTaskStatus와 updateTaskSprint를 서버 액션으로 옮기고, useMutation은 해당 서버 액션만 호출하도록 정리하세요.
Out of Scope Changes check ⚠️ Warning 캘린더 페이지/뷰와 대시보드 위젯 mock 조정은 스프린트 보드·진행률 차트 DB 연동 범위를 벗어난 변경입니다. 이슈 #41 범위와 직접 연관 없는 캘린더/대시보드 변경은 분리하거나 제거하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 28.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 사이드 프로젝트 스프린트 보드와 진행률 차트의 DB 연동이라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed 작업 내용, 결과, 변경 사항, 테스트, 리뷰 체크리스트, 관련 이슈가 템플릿 구조를 대부분 충족합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#41/side-project-sprint-backend

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

@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: 13

Caution

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

⚠️ Outside diff range comments (2)
src/features/manage-calendar/ui/CalendarView.tsx (1)

329-343: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

라벨-인풋 연결 누락 (접근성)

일정 이름 라벨이 htmlFor/id 또는 중첩 없이 입력과 분리되어 있어, 스크린리더 사용자가 어떤 입력과 연결된 라벨인지 알 수 없습니다. 정적 분석 도구에서도 동일하게 플래그되었습니다.

♿ 제안 수정
-                <label className="text-brand-muted block text-[12px] font-semibold">
+                <label htmlFor="calendar-event-title" className="text-brand-muted block text-[12px] font-semibold">
                   일정 이름 <span className="text-[`#ff6565`]">*</span>
                 </label>
                 <input
+                  id="calendar-event-title"
                   type="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 `@src/features/manage-calendar/ui/CalendarView.tsx` around lines 329 - 343,
Update the 일정 이름 label and its associated input in the CalendarView form to use
a matching htmlFor and id, ensuring the label explicitly references this title
field without changing the existing validation or input behavior.

Source: Linters/SAST tools

src/views/side-project/sprint-board/ui/SprintBoardView.tsx (1)

67-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

스프린트 전환 시 보드만 로딩 처리하세요
tasksQuery.isPending || backlogQuery.isPending에서 바로 return해서, 새 스프린트를 처음 열 때 SprintSelector/SprintToolbar/SprintSummaryHeader까지 같이 사라집니다. SprintBoard만 조건부로 감싸고, useSprintTasks에는 placeholderData를 넣어 전환 깜빡임을 줄이는 편이 좋습니다.

🤖 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 `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx` around lines 67 -
89, Update the loading flow in SprintBoardView so tasksQuery.isPending or
backlogQuery.isPending no longer returns before rendering the sprint selector,
toolbar, and summary header. Keep the surrounding sprint UI visible,
conditionally render only SprintBoard for the loading state, and configure
useSprintTasks with placeholderData to preserve previous task data during sprint
transitions and reduce flicker.
🤖 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 `@src/entities/side-project/sprint/api/update-sprint.ts`:
- Around line 13-26: Update updateSprint to require and apply a workspace_id
equality filter alongside the existing id filter when updating sprints, using
the authenticated workspace context rather than trusting an arbitrary client
value. Also remove the permissive dev_full_access RLS policy from the production
database configuration or migration.

In `@src/entities/side-project/task/api/create-task.ts`:
- Line 3: Update the task creation flow in create-task.ts to use the shared
getCurrentUserId() helper from current-user.ts instead of DEV_USER_ID when
populating created_by, preserving the existing validation and insert behavior so
development-only fallback remains centralized in the helper.

In `@src/entities/side-project/task/api/get-backlog-tasks.ts`:
- Around line 7-20: Update the database RLS configuration governing the tasks
query used by getBacklogTasks to remove or narrowly restrict the public.*
dev_full_access policy, ensuring access is limited to the authenticated user’s
workspace membership while preserving legitimate backlog reads.

In `@src/entities/side-project/task/api/use-update-task-status.ts`:
- Around line 30-34: Update the rollback loop in the mutation’s onError handler
to safely skip iteration when context or its previous snapshot is undefined,
while preserving restoration of every snapshot when available. Keep the existing
error toast behavior unchanged.

In `@src/entities/side-project/task/model/task.schema.ts`:
- Line 16: Update the assigneeId schema to use Zod’s standard z.uuid() validator
followed by .nullable(), replacing the current z.string().uuid() chain while
preserving nullable UUID validation.

In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 353-365: Connect the “시간 (선택)” label to its input by adding a
matching htmlFor and id pair around the time field in CalendarView, using a
unique identifier and preserving the existing input behavior.

In `@src/features/manage-sprint-tasks/model/use-task-dnd.ts`:
- Around line 9-41: Extend useTaskDnd beyond native drag events by exposing a
keyboard- and single-pointer-accessible status-change action, such as a status
selector or change handler that invokes onMove with the task ID and target
TaskStatus. Integrate this alternative with the task card or column UI, and add
the missing status control to TaskFormDialog or the relevant card component so
users can move tasks without dragging.

In `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx`:
- Around line 13-26: Extract the repeated dialog setup from SprintDeleteDialog,
SprintFormDialog, and TaskFormDialog into a shared useNativeDialog hook under
src/shared, preserving showModal, cancel prevention, onClose invocation,
listener cleanup, and the onClose dependency. Replace each component’s local
ref/effect block with the shared hook and use its returned dialog ref.
- Around line 3-4: Remove the outdated UI-only and future-wiring comments from
SprintDeleteDialog.tsx, since SprintToolbar’s onConfirm already invokes the
useDeleteSprint mutation via deleteSprint.mutate(sprint.id). Do not change the
existing deletion wiring.

In `@src/features/manage-sprints/ui/SprintFormDialog.tsx`:
- Line 90: Review the autoFocus prop in SprintFormDialog and verify that
focusing the initial field on native modal open is intentional and accessible.
Preserve it if it matches the established TaskFormDialog pattern and does not
harm screen-reader behavior; otherwise replace it with the project’s existing
modal-focus approach.
- Around line 3-5: Remove the outdated “저장 로직 미배선” and follow-up onSubmit TODO
comments from SprintFormDialog.tsx, since SprintToolbar.handleSubmit already
routes create and update operations through the appropriate mutations. Keep the
existing UI shell comments that remain accurate.

In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx`:
- Around line 7-12: Update the import from the task module in MyTasks.tsx so
Task is also marked as a type-only import, matching TaskStatus and the
convention used by Backlog.tsx; leave the runtime imports getMockSprintTasks and
TASK_STATUS unchanged.

In `@supabase/migrations/20260712220809_create_sprint_rpcs.sql`:
- Around line 1-8: Update the get_sprints RPC and the createSprint server action
to explicitly require public.is_workspace_member(p_workspace_id) before reading
or modifying workspace data. Reject unauthorized requests and preserve the
existing behavior for valid workspace members; do not rely solely on the
existing RLS policies while dev_full_access remains enabled.

---

Outside diff comments:
In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 329-343: Update the 일정 이름 label and its associated input in the
CalendarView form to use a matching htmlFor and id, ensuring the label
explicitly references this title field without changing the existing validation
or input behavior.

In `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx`:
- Around line 67-89: Update the loading flow in SprintBoardView so
tasksQuery.isPending or backlogQuery.isPending no longer returns before
rendering the sprint selector, toolbar, and summary header. Keep the surrounding
sprint UI visible, conditionally render only SprintBoard for the loading state,
and configure useSprintTasks with placeholderData to preserve previous task data
during sprint transitions and reduce flicker.
🪄 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: 86597ecf-fe80-42a2-b8a2-2abf4afe2535

📥 Commits

Reviewing files that changed from the base of the PR and between 259b8ed and 5c20d71.

📒 Files selected for processing (74)
  • src/app/workspaces/[workspaceId]/calendar/page.tsx
  • src/app/workspaces/[workspaceId]/page.tsx
  • src/app/workspaces/[workspaceId]/progress-chart/page.tsx
  • src/app/workspaces/[workspaceId]/sprint-board/page.tsx
  • src/entities/calendar-event/model/calendar-event.types.ts
  • src/entities/side-project/sprint/api/create-sprint.ts
  • src/entities/side-project/sprint/api/delete-sprint.ts
  • src/entities/side-project/sprint/api/get-sprints.ts
  • src/entities/side-project/sprint/api/update-sprint.ts
  • src/entities/side-project/sprint/api/use-create-sprint.ts
  • src/entities/side-project/sprint/api/use-delete-sprint.ts
  • src/entities/side-project/sprint/api/use-sprints.ts
  • src/entities/side-project/sprint/api/use-update-sprint.ts
  • src/entities/side-project/sprint/index.ts
  • src/entities/side-project/sprint/model/sprint.db.types.ts
  • src/entities/side-project/sprint/model/sprint.mapper.ts
  • src/entities/side-project/sprint/model/sprint.mock.ts
  • src/entities/side-project/sprint/model/sprint.schema.ts
  • src/entities/side-project/sprint/model/sprint.selectors.ts
  • src/entities/side-project/task/api/create-task.ts
  • src/entities/side-project/task/api/delete-task.ts
  • src/entities/side-project/task/api/get-backlog-tasks.ts
  • src/entities/side-project/task/api/get-sprint-tasks.ts
  • src/entities/side-project/task/api/update-task-sprint.ts
  • src/entities/side-project/task/api/update-task-status.ts
  • src/entities/side-project/task/api/update-task.ts
  • src/entities/side-project/task/api/use-backlog-tasks.ts
  • src/entities/side-project/task/api/use-create-task.ts
  • src/entities/side-project/task/api/use-delete-task.ts
  • src/entities/side-project/task/api/use-sprint-tasks.ts
  • src/entities/side-project/task/api/use-update-task-sprint.ts
  • src/entities/side-project/task/api/use-update-task-status.ts
  • src/entities/side-project/task/api/use-update-task.ts
  • src/entities/side-project/task/index.ts
  • src/entities/side-project/task/model/task.db.types.ts
  • src/entities/side-project/task/model/task.mapper.ts
  • src/entities/side-project/task/model/task.mock.ts
  • src/entities/side-project/task/model/task.schema.ts
  • src/entities/side-project/task/model/task.selectors.ts
  • src/entities/side-project/task/model/task.types.ts
  • src/features/manage-calendar/ui/CalendarView.tsx
  • src/features/manage-sprint-tasks/index.ts
  • src/features/manage-sprint-tasks/lib/avatar-color.ts
  • src/features/manage-sprint-tasks/model/board-task.ts
  • src/features/manage-sprint-tasks/model/sprint-board-columns.ts
  • src/features/manage-sprint-tasks/model/task-form.ts
  • src/features/manage-sprint-tasks/model/use-sprint-board.ts
  • src/features/manage-sprint-tasks/model/use-task-dnd.ts
  • src/features/manage-sprint-tasks/ui/BacklogRow.tsx
  • src/features/manage-sprint-tasks/ui/BacklogSection.tsx
  • src/features/manage-sprint-tasks/ui/SprintBoard.tsx
  • src/features/manage-sprint-tasks/ui/SprintColumn.tsx
  • src/features/manage-sprint-tasks/ui/TaskCard.tsx
  • src/features/manage-sprint-tasks/ui/TaskFormDialog.tsx
  • src/features/manage-sprints/index.ts
  • src/features/manage-sprints/ui/SprintDeleteDialog.tsx
  • src/features/manage-sprints/ui/SprintFormDialog.tsx
  • src/features/manage-sprints/ui/SprintToolbar.tsx
  • src/features/sprint-board/index.ts
  • src/features/sprint-board/model/task-form.ts
  • src/features/sprint-board/model/use-sprint-board.ts
  • src/shared/model/database.types.ts
  • src/views/side-project/progress-chart/index.ts
  • src/views/side-project/progress-chart/ui/ProgressChartView.tsx
  • src/views/side-project/progress-chart/ui/ProgressStatRow.tsx
  • src/views/side-project/progress-chart/ui/SprintProgressCard.tsx
  • src/views/side-project/progress-chart/ui/StatusDonutChart.tsx
  • src/views/side-project/progress-chart/ui/VelocityChart.tsx
  • src/views/side-project/sprint-board/ui/SprintBoardView.tsx
  • src/views/side-project/sprint-board/ui/SprintSelector.tsx
  • src/widgets/side-project/dashboard-backlog/ui/Backlog.tsx
  • src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx
  • src/widgets/side-project/dashboard-velocity/ui/Velocity.tsx
  • supabase/migrations/20260712220809_create_sprint_rpcs.sql
💤 Files with no reviewable changes (3)
  • src/features/sprint-board/index.ts
  • src/features/sprint-board/model/use-sprint-board.ts
  • src/features/sprint-board/model/task-form.ts

@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

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: 13

Caution

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

⚠️ Outside diff range comments (2)
src/features/manage-calendar/ui/CalendarView.tsx (1)

329-343: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

라벨-인풋 연결 누락 (접근성)

일정 이름 라벨이 htmlFor/id 또는 중첩 없이 입력과 분리되어 있어, 스크린리더 사용자가 어떤 입력과 연결된 라벨인지 알 수 없습니다. 정적 분석 도구에서도 동일하게 플래그되었습니다.

♿ 제안 수정
-                <label className="text-brand-muted block text-[12px] font-semibold">
+                <label htmlFor="calendar-event-title" className="text-brand-muted block text-[12px] font-semibold">
                   일정 이름 <span className="text-[`#ff6565`]">*</span>
                 </label>
                 <input
+                  id="calendar-event-title"
                   type="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 `@src/features/manage-calendar/ui/CalendarView.tsx` around lines 329 - 343,
Update the 일정 이름 label and its associated input in the CalendarView form to use
a matching htmlFor and id, ensuring the label explicitly references this title
field without changing the existing validation or input behavior.

Source: Linters/SAST tools

src/views/side-project/sprint-board/ui/SprintBoardView.tsx (1)

67-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

스프린트 전환 시 보드만 로딩 처리하세요
tasksQuery.isPending || backlogQuery.isPending에서 바로 return해서, 새 스프린트를 처음 열 때 SprintSelector/SprintToolbar/SprintSummaryHeader까지 같이 사라집니다. SprintBoard만 조건부로 감싸고, useSprintTasks에는 placeholderData를 넣어 전환 깜빡임을 줄이는 편이 좋습니다.

🤖 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 `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx` around lines 67 -
89, Update the loading flow in SprintBoardView so tasksQuery.isPending or
backlogQuery.isPending no longer returns before rendering the sprint selector,
toolbar, and summary header. Keep the surrounding sprint UI visible,
conditionally render only SprintBoard for the loading state, and configure
useSprintTasks with placeholderData to preserve previous task data during sprint
transitions and reduce flicker.
🤖 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 `@src/entities/side-project/sprint/api/update-sprint.ts`:
- Around line 13-26: Update updateSprint to require and apply a workspace_id
equality filter alongside the existing id filter when updating sprints, using
the authenticated workspace context rather than trusting an arbitrary client
value. Also remove the permissive dev_full_access RLS policy from the production
database configuration or migration.

In `@src/entities/side-project/task/api/create-task.ts`:
- Line 3: Update the task creation flow in create-task.ts to use the shared
getCurrentUserId() helper from current-user.ts instead of DEV_USER_ID when
populating created_by, preserving the existing validation and insert behavior so
development-only fallback remains centralized in the helper.

In `@src/entities/side-project/task/api/get-backlog-tasks.ts`:
- Around line 7-20: Update the database RLS configuration governing the tasks
query used by getBacklogTasks to remove or narrowly restrict the public.*
dev_full_access policy, ensuring access is limited to the authenticated user’s
workspace membership while preserving legitimate backlog reads.

In `@src/entities/side-project/task/api/use-update-task-status.ts`:
- Around line 30-34: Update the rollback loop in the mutation’s onError handler
to safely skip iteration when context or its previous snapshot is undefined,
while preserving restoration of every snapshot when available. Keep the existing
error toast behavior unchanged.

In `@src/entities/side-project/task/model/task.schema.ts`:
- Line 16: Update the assigneeId schema to use Zod’s standard z.uuid() validator
followed by .nullable(), replacing the current z.string().uuid() chain while
preserving nullable UUID validation.

In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 353-365: Connect the “시간 (선택)” label to its input by adding a
matching htmlFor and id pair around the time field in CalendarView, using a
unique identifier and preserving the existing input behavior.

In `@src/features/manage-sprint-tasks/model/use-task-dnd.ts`:
- Around line 9-41: Extend useTaskDnd beyond native drag events by exposing a
keyboard- and single-pointer-accessible status-change action, such as a status
selector or change handler that invokes onMove with the task ID and target
TaskStatus. Integrate this alternative with the task card or column UI, and add
the missing status control to TaskFormDialog or the relevant card component so
users can move tasks without dragging.

In `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx`:
- Around line 13-26: Extract the repeated dialog setup from SprintDeleteDialog,
SprintFormDialog, and TaskFormDialog into a shared useNativeDialog hook under
src/shared, preserving showModal, cancel prevention, onClose invocation,
listener cleanup, and the onClose dependency. Replace each component’s local
ref/effect block with the shared hook and use its returned dialog ref.
- Around line 3-4: Remove the outdated UI-only and future-wiring comments from
SprintDeleteDialog.tsx, since SprintToolbar’s onConfirm already invokes the
useDeleteSprint mutation via deleteSprint.mutate(sprint.id). Do not change the
existing deletion wiring.

In `@src/features/manage-sprints/ui/SprintFormDialog.tsx`:
- Line 90: Review the autoFocus prop in SprintFormDialog and verify that
focusing the initial field on native modal open is intentional and accessible.
Preserve it if it matches the established TaskFormDialog pattern and does not
harm screen-reader behavior; otherwise replace it with the project’s existing
modal-focus approach.
- Around line 3-5: Remove the outdated “저장 로직 미배선” and follow-up onSubmit TODO
comments from SprintFormDialog.tsx, since SprintToolbar.handleSubmit already
routes create and update operations through the appropriate mutations. Keep the
existing UI shell comments that remain accurate.

In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx`:
- Around line 7-12: Update the import from the task module in MyTasks.tsx so
Task is also marked as a type-only import, matching TaskStatus and the
convention used by Backlog.tsx; leave the runtime imports getMockSprintTasks and
TASK_STATUS unchanged.

In `@supabase/migrations/20260712220809_create_sprint_rpcs.sql`:
- Around line 1-8: Update the get_sprints RPC and the createSprint server action
to explicitly require public.is_workspace_member(p_workspace_id) before reading
or modifying workspace data. Reject unauthorized requests and preserve the
existing behavior for valid workspace members; do not rely solely on the
existing RLS policies while dev_full_access remains enabled.

---

Outside diff comments:
In `@src/features/manage-calendar/ui/CalendarView.tsx`:
- Around line 329-343: Update the 일정 이름 label and its associated input in the
CalendarView form to use a matching htmlFor and id, ensuring the label
explicitly references this title field without changing the existing validation
or input behavior.

In `@src/views/side-project/sprint-board/ui/SprintBoardView.tsx`:
- Around line 67-89: Update the loading flow in SprintBoardView so
tasksQuery.isPending or backlogQuery.isPending no longer returns before
rendering the sprint selector, toolbar, and summary header. Keep the surrounding
sprint UI visible, conditionally render only SprintBoard for the loading state,
and configure useSprintTasks with placeholderData to preserve previous task data
during sprint transitions and reduce flicker.
🪄 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: 86597ecf-fe80-42a2-b8a2-2abf4afe2535

📥 Commits

Reviewing files that changed from the base of the PR and between 259b8ed and 5c20d71.

📒 Files selected for processing (74)
  • src/app/workspaces/[workspaceId]/calendar/page.tsx
  • src/app/workspaces/[workspaceId]/page.tsx
  • src/app/workspaces/[workspaceId]/progress-chart/page.tsx
  • src/app/workspaces/[workspaceId]/sprint-board/page.tsx
  • src/entities/calendar-event/model/calendar-event.types.ts
  • src/entities/side-project/sprint/api/create-sprint.ts
  • src/entities/side-project/sprint/api/delete-sprint.ts
  • src/entities/side-project/sprint/api/get-sprints.ts
  • src/entities/side-project/sprint/api/update-sprint.ts
  • src/entities/side-project/sprint/api/use-create-sprint.ts
  • src/entities/side-project/sprint/api/use-delete-sprint.ts
  • src/entities/side-project/sprint/api/use-sprints.ts
  • src/entities/side-project/sprint/api/use-update-sprint.ts
  • src/entities/side-project/sprint/index.ts
  • src/entities/side-project/sprint/model/sprint.db.types.ts
  • src/entities/side-project/sprint/model/sprint.mapper.ts
  • src/entities/side-project/sprint/model/sprint.mock.ts
  • src/entities/side-project/sprint/model/sprint.schema.ts
  • src/entities/side-project/sprint/model/sprint.selectors.ts
  • src/entities/side-project/task/api/create-task.ts
  • src/entities/side-project/task/api/delete-task.ts
  • src/entities/side-project/task/api/get-backlog-tasks.ts
  • src/entities/side-project/task/api/get-sprint-tasks.ts
  • src/entities/side-project/task/api/update-task-sprint.ts
  • src/entities/side-project/task/api/update-task-status.ts
  • src/entities/side-project/task/api/update-task.ts
  • src/entities/side-project/task/api/use-backlog-tasks.ts
  • src/entities/side-project/task/api/use-create-task.ts
  • src/entities/side-project/task/api/use-delete-task.ts
  • src/entities/side-project/task/api/use-sprint-tasks.ts
  • src/entities/side-project/task/api/use-update-task-sprint.ts
  • src/entities/side-project/task/api/use-update-task-status.ts
  • src/entities/side-project/task/api/use-update-task.ts
  • src/entities/side-project/task/index.ts
  • src/entities/side-project/task/model/task.db.types.ts
  • src/entities/side-project/task/model/task.mapper.ts
  • src/entities/side-project/task/model/task.mock.ts
  • src/entities/side-project/task/model/task.schema.ts
  • src/entities/side-project/task/model/task.selectors.ts
  • src/entities/side-project/task/model/task.types.ts
  • src/features/manage-calendar/ui/CalendarView.tsx
  • src/features/manage-sprint-tasks/index.ts
  • src/features/manage-sprint-tasks/lib/avatar-color.ts
  • src/features/manage-sprint-tasks/model/board-task.ts
  • src/features/manage-sprint-tasks/model/sprint-board-columns.ts
  • src/features/manage-sprint-tasks/model/task-form.ts
  • src/features/manage-sprint-tasks/model/use-sprint-board.ts
  • src/features/manage-sprint-tasks/model/use-task-dnd.ts
  • src/features/manage-sprint-tasks/ui/BacklogRow.tsx
  • src/features/manage-sprint-tasks/ui/BacklogSection.tsx
  • src/features/manage-sprint-tasks/ui/SprintBoard.tsx
  • src/features/manage-sprint-tasks/ui/SprintColumn.tsx
  • src/features/manage-sprint-tasks/ui/TaskCard.tsx
  • src/features/manage-sprint-tasks/ui/TaskFormDialog.tsx
  • src/features/manage-sprints/index.ts
  • src/features/manage-sprints/ui/SprintDeleteDialog.tsx
  • src/features/manage-sprints/ui/SprintFormDialog.tsx
  • src/features/manage-sprints/ui/SprintToolbar.tsx
  • src/features/sprint-board/index.ts
  • src/features/sprint-board/model/task-form.ts
  • src/features/sprint-board/model/use-sprint-board.ts
  • src/shared/model/database.types.ts
  • src/views/side-project/progress-chart/index.ts
  • src/views/side-project/progress-chart/ui/ProgressChartView.tsx
  • src/views/side-project/progress-chart/ui/ProgressStatRow.tsx
  • src/views/side-project/progress-chart/ui/SprintProgressCard.tsx
  • src/views/side-project/progress-chart/ui/StatusDonutChart.tsx
  • src/views/side-project/progress-chart/ui/VelocityChart.tsx
  • src/views/side-project/sprint-board/ui/SprintBoardView.tsx
  • src/views/side-project/sprint-board/ui/SprintSelector.tsx
  • src/widgets/side-project/dashboard-backlog/ui/Backlog.tsx
  • src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx
  • src/widgets/side-project/dashboard-velocity/ui/Velocity.tsx
  • supabase/migrations/20260712220809_create_sprint_rpcs.sql
💤 Files with no reviewable changes (3)
  • src/features/sprint-board/index.ts
  • src/features/sprint-board/model/use-sprint-board.ts
  • src/features/sprint-board/model/task-form.ts
🛑 Comments failed to post (13)
src/entities/side-project/sprint/api/update-sprint.ts (1)

13-26: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# sprints 테이블에 대한 RLS 정책 확인
rg -n "sprints" supabase/migrations -l
rg -n "policy|RLS|ENABLE ROW LEVEL SECURITY" supabase/migrations -i -A5 | rg -B5 -A5 "sprints"

Repository: TeampleRun/syncly

Length of output: 1489


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== migrations mentioning sprints ==\n'
rg -n "sprints|ENABLE ROW LEVEL SECURITY|CREATE POLICY|ALTER TABLE.*ROW LEVEL SECURITY|workspace_members|auth.uid" supabase/migrations -i -A4 -B4

printf '\n== sprints table definition ==\n'
sed -n '1,260p' supabase/migrations/20260709025327_create_enums_and_core_tables.sql

printf '\n== auth RLS policies migration ==\n'
sed -n '1,260p' supabase/migrations/20260709025820_add_auth_rls_policies.sql

printf '\n== sprint RPCs migration ==\n'
sed -n '1,240p' supabase/migrations/20260712220809_create_sprint_rpcs.sql

printf '\n== updateSprint implementation and callers ==\n'
sed -n '1,220p' src/entities/side-project/sprint/api/update-sprint.ts
rg -n "updateSprint\\(" src -A3 -B3

Repository: TeampleRun/syncly

Length of output: 46072


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== dev_full_access references ==\n'
rg -n "dev_full_access|drop policy|drop all policies|disable row level security|enable row level security" supabase/migrations -i -A3 -B3

printf '\n== any later sprint policy changes ==\n'
rg -n "sprints_member_all|create policy.*sprints|drop policy.*sprints" supabase/migrations -i -A3 -B3

printf '\n== updateSprint call sites with workspace context ==\n'
rg -n "updateSprint\\(" src -A5 -B5

Repository: TeampleRun/syncly

Length of output: 2397


workspace_id 기준 스코프를 추가하세요.
지금은 public.sprintsdev_full_access RLS가 남아 있어 id만 알면 다른 워크스페이스 스프린트도 수정됩니다. updateSprintworkspace_id 조건을 넣고, 운영 환경에선 이 permissive 정책을 제거해야 합니다.

🤖 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 `@src/entities/side-project/sprint/api/update-sprint.ts` around lines 13 - 26,
Update updateSprint to require and apply a workspace_id equality filter
alongside the existing id filter when updating sprints, using the authenticated
workspace context rather than trusting an arbitrary client value. Also remove
the permissive dev_full_access RLS policy from the production database
configuration or migration.
src/entities/side-project/task/api/create-task.ts (1)

3-3: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# DEV_USER_ID 사용처와 실제 인증(세션) 연동 여부 확인
rg -n "DEV_USER_ID" --type=ts
rg -n "auth.getUser|getSession" --type=ts -g '!**/node_modules/**'

Repository: TeampleRun/syncly

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## target file\n'
if [ -f src/entities/side-project/task/api/create-task.ts ]; then
  wc -l src/entities/side-project/task/api/create-task.ts
  cat -n src/entities/side-project/task/api/create-task.ts
else
  echo "missing target file"
fi

printf '\n## nearby task api files\n'
fd -a 'create-task.ts' src || true
fd -a 'task' src/entities/side-project || true

printf '\n## search for relevant symbols\n'
rg -n "DEV_USER_ID|created_by|createdBy|auth\\.getUser|auth\\.getSession|getSession\\(|user\\.id|session\\.user|supabase\\.auth" src

Repository: TeampleRun/syncly

Length of output: 6698


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## current-user helper\n'
wc -l src/shared/api/supabase/current-user.ts
cat -n src/shared/api/supabase/current-user.ts

printf '\n## actions using getCurrentUserId\n'
rg -n "getCurrentUserId\(" src/entities src/shared

printf '\n## DEV_USER_ID direct usages around create/update actions\n'
rg -n "DEV_USER_ID" src/entities/side-project src/entities/work-schedule src/entities/workspace src/shared/config

Repository: TeampleRun/syncly

Length of output: 2491


create-task.ts에서 DEV_USER_ID 대신 getCurrentUserId()를 사용하세요.

주석과 달리 현재 생성자(created_by)가 실제 로그인 유저가 아니라 고정 DEV_USER_ID로 저장됩니다. src/shared/api/supabase/current-user.ts의 공용 헬퍼로 바꿔 개발 환경만 fallback 되게 맞춰야 합니다.

🤖 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 `@src/entities/side-project/task/api/create-task.ts` at line 3, Update the task
creation flow in create-task.ts to use the shared getCurrentUserId() helper from
current-user.ts instead of DEV_USER_ID when populating created_by, preserving
the existing validation and insert behavior so development-only fallback remains
centralized in the helper.
src/entities/side-project/task/api/get-backlog-tasks.ts (1)

7-20: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# tasks 테이블 RLS 정책 확인
rg -n "tasks" supabase/migrations --iglob '*.sql' -A3 -B3 | rg -n "policy|rls" -i

Repository: TeampleRun/syncly

Length of output: 978


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) tasks 관련 RLS 정책 정의 확인
sed -n '1,220p' supabase/migrations/20260709025820_add_auth_rls_policies.sql

# 2) tasks 테이블에서 select 정책이 별도로 정의됐는지 확인
rg -n "create policy|policy .*tasks|tasks" supabase/migrations --iglob '*.sql' -A4 -B4

Repository: TeampleRun/syncly

Length of output: 26466


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) dev_full_access가 제거되는지 확인
rg -n "drop policy .*dev_full_access|dev_full_access" supabase/migrations --iglob '*.sql' -A3 -B3

# 2) 브라우저용 Supabase 클라이언트가 anon/publishable key를 쓰는지 확인
rg -n "getSupabaseBrowserClient|createBrowserClient|NEXT_PUBLIC_SUPABASE|publishable|anon" src supabase --iglob '*.{ts,tsx,ts,mts,cts,js,mjs,cjs,sql}' -A2 -B2

Repository: TeampleRun/syncly

Length of output: 11559


브라우저 조회는 현재 dev_full_access 때문에 워크스페이스 경계가 막히지 않습니다.
tasks에는 멤버십 RLS가 추가돼 있지만, public.* 전체에 적용된 dev_full_access가 아직 남아 있어 이 조회로 다른 워크스페이스 데이터가 읽힐 수 있습니다. 이 정책을 제거하거나 더 좁은 권한으로 제한해야 합니다.

🤖 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 `@src/entities/side-project/task/api/get-backlog-tasks.ts` around lines 7 - 20,
Update the database RLS configuration governing the tasks query used by
getBacklogTasks to remove or narrowly restrict the public.* dev_full_access
policy, ensuring access is limited to the authenticated user’s workspace
membership while preserving legitimate backlog reads.
src/entities/side-project/task/api/use-update-task-status.ts (1)

30-34: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

옵셔널 체이닝 누락으로 롤백 시 런타임 에러 위험.

context?.previous.forEach(...)에서 ?.previous 접근만 보호하며, contextundefined일 경우 context?.previousundefined가 되고 이어지는 .forEach(...) 호출에서 TypeError가 발생합니다. onMutate에서 cancelQueries가 실패하는 등 예외가 던져지면 context가 정의되지 않은 채로 onError가 호출될 수 있습니다.

🐛 제안 수정
-      context?.previous.forEach(([key, data]) => queryClient.setQueryData(key, data));
+      context?.previous.forEach(([key, data]) => queryClient.setQueryData(key, data)); // 기존 유지 시 아래로 대체
+      context?.previous?.forEach(([key, data]) => queryClient.setQueryData(key, data));
🤖 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 `@src/entities/side-project/task/api/use-update-task-status.ts` around lines 30
- 34, Update the rollback loop in the mutation’s onError handler to safely skip
iteration when context or its previous snapshot is undefined, while preserving
restoration of every snapshot when available. Keep the existing error toast
behavior unchanged.
src/entities/side-project/task/model/task.schema.ts (1)

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

🧩 Analysis chain

🌐 Web query:

Zod 4 z.string().uuid() deprecated in favor of z.uuid()?

💡 Result:

Yes, in Zod 4, the method z.string.uuid is deprecated in favor of the top-level z.uuid [1][2]. In Zod 4, common string validation methods like.email,.url, and.uuid have been moved from the ZodString prototype to the top-level z namespace [1]. While the previous method-chaining syntax (e.g., z.string.uuid) continues to work, it is considered deprecated in favor of the new, more tree-shakable and concise top-level API [1]. Additionally, it is important to note that Zod 4 introduced a breaking change regarding UUID validation behavior. The new z.uuid strictly enforces RFC 9562/4122 compliance, meaning it will reject some UUIDs that were accepted by Zod 3 [3][4][1]. If you require a more permissive validator that accepts any 8-4-4-4-12 hex pattern (similar to the legacy behavior), you should use z.guid instead [3][1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 확인: Zod 버전과 해당 스키마 사용 맥락을 봅니다.
git ls-files package.json pnpm-lock.yaml package-lock.json yarn.lock src/entities/side-project/task/model/task.schema.ts \
  | xargs -r -I{} sh -c 'echo "=== {} ==="; sed -n "1,220p" "{}"; echo'

Repository: TeampleRun/syncly

Length of output: 10820


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 확인: Zod 버전과 해당 스키마 사용 맥락을 봅니다.
for f in package.json pnpm-lock.yaml package-lock.json yarn.lock src/entities/side-project/task/model/task.schema.ts; do
  if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
    echo "=== $f ==="
    sed -n '1,220p' "$f"
    echo
  fi
done

Repository: TeampleRun/syncly

Length of output: 10820


z.string().uuid() 대신 z.uuid()로 바꾸세요.
Zod 4.4.3에서는 UUID 검증의 표준 API가 z.uuid()라서, 이 필드는 z.uuid().nullable()로 쓰면 됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/entities/side-project/task/model/task.schema.ts` at line 16, Update the
assigneeId schema to use Zod’s standard z.uuid() validator followed by
.nullable(), replacing the current z.string().uuid() chain while preserving
nullable UUID validation.
src/features/manage-calendar/ui/CalendarView.tsx (1)

353-365: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

라벨-인풋 연결 누락 (접근성)

시간 (선택) 라벨도 동일하게 대응하는 입력과 연결되어 있지 않습니다. htmlFor/id로 연결하면 353, 356 두 정적 분석 경고를 모두 해소할 수 있습니다.

♿ 제안 수정
-                <label className="text-brand-muted block text-[12px] font-semibold">
+                <label htmlFor="calendar-event-time" className="text-brand-muted block text-[12px] font-semibold">
                   시간 (선택)
                 </label>
                 <input
+                  id="calendar-event-time"
                   type="text"
📝 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.

                <label htmlFor="calendar-event-time" className="text-brand-muted block text-[12px] font-semibold">
                  시간 (선택)
                </label>
                <input
                  id="calendar-event-time"
                  type="text"
                  value={formValues.time}
                  onChange={(event) =>
                    setFormValues((current) => ({ ...current, time: event.target.value }))
                  }
                  placeholder="예: 오후 3:00"
                  className="text-brand-ink focus:ring-brand mt-2 h-11 w-full rounded-[16px] bg-[`#f1f3fb`] px-4 text-[14px] transition outline-none placeholder:text-[`#a8afc8`] focus:ring-1"
                />
              </div>
🧰 Tools
🪛 React Doctor (0.5.8)

[warning] 353-353: Screen reader users can't tell which input this label names because it's tied to none, so add htmlFor or wrap the input inside it.

Tie every label to a control with htmlFor, or by nesting the input.

(label-has-associated-control)


[warning] 356-356: Blind users can't tell what this control does because screen readers find no label, so add visible text, aria-label, or aria-labelledby.

Give every interactive control a label screen readers can read.

(control-has-associated-label)

🤖 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 `@src/features/manage-calendar/ui/CalendarView.tsx` around lines 353 - 365,
Connect the “시간 (선택)” label to its input by adding a matching htmlFor and id
pair around the time field in CalendarView, using a unique identifier and
preserving the existing input behavior.

Source: Linters/SAST tools

src/features/manage-sprint-tasks/model/use-task-dnd.ts (1)

9-41: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether TaskFormDialog/task-form.ts expose a status field as a drag alternative
fd -e ts -e tsx . src/features/manage-sprint-tasks | xargs rg -n -i 'status' -C3

Repository: TeampleRun/syncly

Length of output: 14514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files 'src/features/manage-sprint-tasks/**' | sed -n '1,200p'

echo
echo "== outline task-form.ts =="
ast-grep outline src/features/manage-sprint-tasks/model/task-form.ts --view expanded || true

echo
echo "== task-form.ts relevant lines =="
sed -n '1,220p' src/features/manage-sprint-tasks/model/task-form.ts

echo
echo "== SprintColumn.tsx relevant lines =="
sed -n '1,240p' src/features/manage-sprint-tasks/ui/SprintColumn.tsx

echo
echo "== TaskCard.tsx relevant lines =="
sed -n '1,260p' src/features/manage-sprint-tasks/ui/TaskCard.tsx

echo
echo "== SprintBoard.tsx relevant lines =="
sed -n '1,220p' src/features/manage-sprint-tasks/ui/SprintBoard.tsx

Repository: TeampleRun/syncly

Length of output: 11033


상태 변경에 키보드/단일 포인터 대안을 추가하세요

src/features/manage-sprint-tasks/model/use-task-dnd.ts의 상태 변경은 네이티브 드래그에만 연결돼 있고, TaskFormDialog에도 status 필드가 없어 카드 이동을 드래그 없이 할 수 없습니다. 카드나 컬럼에 상태 변경 버튼/셀렉트 같은 대안을 두어야 합니다.

🤖 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 `@src/features/manage-sprint-tasks/model/use-task-dnd.ts` around lines 9 - 41,
Extend useTaskDnd beyond native drag events by exposing a keyboard- and
single-pointer-accessible status-change action, such as a status selector or
change handler that invokes onMove with the task ID and target TaskStatus.
Integrate this alternative with the task card or column UI, and add the missing
status control to TaskFormDialog or the relevant card component so users can
move tasks without dragging.
src/features/manage-sprints/ui/SprintDeleteDialog.tsx (2)

3-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

TODO 주석이 실제 배선 상태와 불일치

"UI 전용 셸(삭제 로직 미배선)"이라는 주석과 TODO가 남아있지만, SprintToolbar.tsxonConfirm={() => deleteSprint.mutate(sprint.id)}(91번째 줄)에서 이미 useDeleteSprint 뮤테이션에 연결되어 있습니다. 오래된 주석은 향후 혼동이나 중복 작업을 유발할 수 있으니 정리해주세요.

📝 주석 정리 제안
-// 스프린트 삭제 확인 모달 — UI 전용 셸(삭제 로직 미배선).
-// TODO(후속): onConfirm을 스프린트 삭제 서버액션(useMutation)에 연결한다.
+// 스프린트 삭제 확인 모달 — onConfirm은 SprintToolbar에서 useDeleteSprint 뮤테이션으로 연결된다.
📝 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.

// 스프린트 삭제 확인 모달 — onConfirm은 SprintToolbar에서 useDeleteSprint 뮤테이션으로 연결된다.
🤖 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 `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx` around lines 3 - 4,
Remove the outdated UI-only and future-wiring comments from
SprintDeleteDialog.tsx, since SprintToolbar’s onConfirm already invokes the
useDeleteSprint mutation via deleteSprint.mutate(sprint.id). Do not change the
existing deletion wiring.

13-26: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

다이얼로그 오픈/취소 처리 로직 중복 — 공용 훅 추출 제안

useEffect(showModal + cancel 리스너) 블록이 SprintFormDialog.tsx(47-57)와 기존 TaskFormDialog.tsx(58-70)에도 그대로 반복됩니다. useNativeDialog(onClose) 같은 공용 훅으로 추출해 src/shared/lib에 두는 것을 제안합니다.

♻️ 공용 훅 추출 예시
// src/shared/lib/use-native-dialog.ts
import { useEffect, useRef } from 'react';

export function useNativeDialog(onClose: () => void) {
  const dialogRef = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    const dialog = dialogRef.current;
    if (!dialog) return undefined;
    dialog.showModal();
    const handleCancel = (event: Event) => {
      event.preventDefault();
      onClose();
    };
    dialog.addEventListener('cancel', handleCancel);
    return () => dialog.removeEventListener('cancel', handleCancel);
  }, [onClose]);

  return dialogRef;
}
-  const dialogRef = useRef<HTMLDialogElement>(null);
-
-  useEffect(() => {
-    const dialog = dialogRef.current;
-    if (!dialog) return undefined;
-    dialog.showModal();
-    const handleCancel = (event: Event) => {
-      event.preventDefault();
-      onClose();
-    };
-    dialog.addEventListener('cancel', handleCancel);
-    return () => dialog.removeEventListener('cancel', handleCancel);
-  }, [onClose]);
+  const dialogRef = useNativeDialog(onClose);

As per coding guidelines, "src/shared/**/*.{ts,tsx}: Place reusable common code in src/shared, including UI components and libraries."

🤖 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 `@src/features/manage-sprints/ui/SprintDeleteDialog.tsx` around lines 13 - 26,
Extract the repeated dialog setup from SprintDeleteDialog, SprintFormDialog, and
TaskFormDialog into a shared useNativeDialog hook under src/shared, preserving
showModal, cancel prevention, onClose invocation, listener cleanup, and the
onClose dependency. Replace each component’s local ref/effect block with the
shared hook and use its returned dialog ref.

Source: Coding guidelines

src/features/manage-sprints/ui/SprintFormDialog.tsx (2)

3-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

TODO 주석이 실제 배선 상태와 불일치

"저장 로직 미배선" TODO가 남아있지만, SprintToolbar.tsxhandleSubmit(39-45번째 줄)에서 updateSprint.mutate(...)/createSprint.mutate(...)로 이미 연결되어 있습니다. SprintDeleteDialog.tsx와 동일한 패턴이니 함께 정리해주세요.

📝 주석 정리 제안
-// 스프린트 생성/수정 모달 — UI 전용 셸(저장 로직 미배선).
-// 톤은 TaskFormDialog와 동일(네이티브 <dialog>.showModal, rounded-2xl 패널, 슬레이트 입력).
-// TODO(후속): onSubmit을 스프린트 생성/수정 서버액션(useMutation)에 연결한다.
+// 스프린트 생성/수정 모달 — 톤은 TaskFormDialog와 동일(네이티브 <dialog>.showModal, rounded-2xl 패널, 슬레이트 입력).
+// onSubmit은 SprintToolbar에서 useCreateSprint/useUpdateSprint 뮤테이션으로 연결된다.
📝 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.

// 스프린트 생성/수정 모달 — 톤은 TaskFormDialog와 동일(네이티브 <dialog>.showModal, rounded-2xl 패널, 슬레이트 입력).
// onSubmit은 SprintToolbar에서 useCreateSprint/useUpdateSprint 뮤테이션으로 연결된다.
🤖 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 `@src/features/manage-sprints/ui/SprintFormDialog.tsx` around lines 3 - 5,
Remove the outdated “저장 로직 미배선” and follow-up onSubmit TODO comments from
SprintFormDialog.tsx, since SprintToolbar.handleSubmit already routes create and
update operations through the appropriate mutations. Keep the existing UI shell
comments that remain accurate.

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

정적 분석: autoFocus 사용 경고 — 모달 컨텍스트에서는 참고용

React Doctor(no-autofocus)가 autoFocus를 경고했습니다. 다만 네이티브 <dialog>.showModal() 안에서 첫 입력 필드에 초기 포커스를 주는 것은 WAI-ARIA 모달 다이얼로그 패턴에서 흔히 권장되는 방식이며, 같은 코드베이스의 TaskFormDialog.tsx도 동일하게 사용 중입니다. 다만 스크린리더 사용자 경험에 문제가 없는지 한 번 확인해보시는 것을 권장합니다.

🧰 Tools
🪛 React Doctor (0.5.8)

[warning] 90-90: autoFocus moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.

Do not use autoFocus. It disorients users on load.

(no-autofocus)

🤖 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 `@src/features/manage-sprints/ui/SprintFormDialog.tsx` at line 90, Review the
autoFocus prop in SprintFormDialog and verify that focusing the initial field on
native modal open is intentional and accessible. Preserve it if it matches the
established TaskFormDialog pattern and does not harm screen-reader behavior;
otherwise replace it with the project’s existing modal-focus approach.

Source: Linters/SAST tools

src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx (1)

7-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

타입 전용 import 스타일 불일치

같은 모듈에서 TaskStatustype 접두사를 사용하지만 Task는 사용하지 않습니다 (Backlog.tsx에서는 type Task로 일관되게 사용). verbatimModuleSyntax가 활성화된 경우 타입만 사용되는 식별자에 type 접두사가 없으면 빌드/린트 오류가 날 수 있습니다.

🔧 제안 수정
 import {
   getMockSprintTasks,
-  Task,
+  type Task,
   TASK_STATUS,
   type TaskStatus,
 } from '`@/entities/side-project/task`';
📝 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 {
  getMockSprintTasks,
  type Task,
  TASK_STATUS,
  type TaskStatus,
} from '`@/entities/side-project/task`';
🤖 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 `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx` around lines 7 -
12, Update the import from the task module in MyTasks.tsx so Task is also marked
as a type-only import, matching TaskStatus and the convention used by
Backlog.tsx; leave the runtime imports getMockSprintTasks and TASK_STATUS
unchanged.
supabase/migrations/20260712220809_create_sprint_rpcs.sql (1)

1-8: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg 'supabase/migrations/20260712220809_create_sprint_rpcs.sql|src/entities/side-project/sprint/api/create-sprint.ts|get-sprints.ts|supabase/.+sql$|migrations/.+sql$' || true

echo
echo "== outline migration file =="
ast-grep outline supabase/migrations/20260712220809_create_sprint_rpcs.sql --view expanded || true

echo
echo "== outline create-sprint api file if present =="
fd -a 'create-sprint.ts' src || true

echo
echo "== search for sprint RLS/policies/functions =="
rg -n --hidden -S "create policy|alter table .* enable row level security|get_sprints|create_sprint|workspace_id|auth\.uid\(\)" supabase src | sed -n '1,240p'

Repository: TeampleRun/syncly

Length of output: 28349


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file stats =="
for f in supabase/migrations/20260712220809_create_sprint_rpcs.sql src/entities/side-project/sprint/api/create-sprint.ts; do
  if [ -f "$f" ]; then
    wc -l "$f"
  fi
done

echo
echo "== migration excerpt =="
sed -n '1,220p' supabase/migrations/20260712220809_create_sprint_rpcs.sql

echo
echo "== create-sprint excerpt =="
if [ -f src/entities/side-project/sprint/api/create-sprint.ts ]; then
  sed -n '1,220p' src/entities/side-project/sprint/api/create-sprint.ts
fi

echo
echo "== migrations mentioning sprints/tasks policies =="
rg -n --hidden -S "enable row level security|create policy|sprints|tasks" supabase/migrations | sed -n '1,260p'

Repository: TeampleRun/syncly

Length of output: 8726


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== inspect auth RLS migration around sprints/tasks =="
sed -n '1,180p' supabase/migrations/20260709025820_add_auth_rls_policies.sql

echo
echo "== search for dev_full_access removal or policy replacement =="
rg -n --hidden -S "dev_full_access|drop policy|alter policy|sprints_select_member|tasks_select_member" supabase/migrations | sed -n '1,240p'

Repository: TeampleRun/syncly

Length of output: 4702


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('supabase/migrations/20260709025820_add_auth_rls_policies.sql')
text = p.read_text()
for needle in ['sprints','tasks','dev_full_access','create policy']:
    print(f'-- {needle} --')
    for i, line in enumerate(text.splitlines(), 1):
        if needle in line:
            print(f'{i}: {line}')
PY

Repository: TeampleRun/syncly

Length of output: 1718


RPC/서버액션에서 워크스페이스 멤버십을 직접 확인해야 합니다

sprints/tasks용 RLS 정책은 이미 있지만, dev_full_access가 살아 있는 동안에는 get_sprintscreateSprintp_workspace_id만 믿고 다른 워크스페이스를 조회/수정할 수 있습니다. public.is_workspace_member(p_workspace_id) 가드를 이 경로에 추가하세요.

🤖 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 `@supabase/migrations/20260712220809_create_sprint_rpcs.sql` around lines 1 -
8, Update the get_sprints RPC and the createSprint server action to explicitly
require public.is_workspace_member(p_workspace_id) before reading or modifying
workspace data. Reject unauthorized requests and preserve the existing behavior
for valid workspace members; do not rely solely on the existing RLS policies
while dev_full_access remains enabled.

@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: 2

🤖 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 `@src/entities/side-project/task/api/create-task.ts`:
- Around line 27-29: createTask 주변에서 Supabase 서버 클라이언트가 중복 생성되지 않도록 정리하세요. 먼저
생성된 supabase 클라이언트를 재사용할 수 있게 current-user.ts의 getCurrentUserId 시그니처와 구현을 수정하고,
createTask의 호출부 및 다른 getCurrentUserId 호출부(use-create-task.ts 등)를 새 인자 계약에 맞게
업데이트하세요.

In `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx`:
- Line 9: MyTasks가 getMockSprintTasks를 사용하지 않도록 제거하고, useSprintTasks 등 기존 React
Query 경로에서 조회한 실제 Supabase 스프린트 태스크를 사용하도록 복원하세요. 스프린트 보드의 생성·수정·상태 변경이 대시보드에
반영되도록 해당 훅의 데이터와 로딩·빈 상태 처리 흐름을 유지하세요.
🪄 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: c9a0f37a-31f7-46fa-8a66-d778131bde96

📥 Commits

Reviewing files that changed from the base of the PR and between 5c20d71 and b9e6745.

📒 Files selected for processing (6)
  • src/app/workspaces/[workspaceId]/progress-chart/page.tsx
  • src/entities/side-project/task/api/create-task.ts
  • src/features/manage-sprints/ui/SprintDeleteDialog.tsx
  • src/features/manage-sprints/ui/SprintFormDialog.tsx
  • src/views/progress-chart/ui/ProgressChartPage.tsx
  • src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx
💤 Files with no reviewable changes (2)
  • src/features/manage-sprints/ui/SprintDeleteDialog.tsx
  • src/features/manage-sprints/ui/SprintFormDialog.tsx

Comment on lines +27 to +29
const supabase = await createSupabaseServerClient();
const createdBy = await getCurrentUserId();
const payload = toTaskInsert(parsed.data, { workspaceId, sprintId, createdBy });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Supabase 서버 클라이언트가 중복 생성됩니다.

Line 27에서 createSupabaseServerClient()로 클라이언트를 생성한 뒤, Line 28의 getCurrentUserId()가 내부적으로 또 한 번 createSupabaseServerClient()를 호출합니다(제공된 그래프 컨텍스트 참조: getCurrentUserId는 자체적으로 await createSupabaseServerClient()를 수행). 기능상 문제는 없지만 요청당 클라이언트가 두 번 생성되어 불필요한 오버헤드가 발생합니다.

♻️ 개선 제안 — `getCurrentUserId`가 클라이언트를 인자로 받도록 리팩터링(선택)
-  const supabase = await createSupabaseServerClient();
-  const createdBy = await getCurrentUserId();
+  const supabase = await createSupabaseServerClient();
+  const createdBy = await getCurrentUserId(supabase);

이 변경은 src/shared/api/supabase/current-user.ts의 시그니처 변경이 필요하므로, 다른 호출부(use-create-task.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 `@src/entities/side-project/task/api/create-task.ts` around lines 27 - 29,
createTask 주변에서 Supabase 서버 클라이언트가 중복 생성되지 않도록 정리하세요. 먼저 생성된 supabase 클라이언트를
재사용할 수 있게 current-user.ts의 getCurrentUserId 시그니처와 구현을 수정하고, createTask의 호출부 및 다른
getCurrentUserId 호출부(use-create-task.ts 등)를 새 인자 계약에 맞게 업데이트하세요.

import { getSprintTasks, Task, TASK_STATUS, type TaskStatus } from '@/entities/side-project/task';
import {
getMockSprintTasks,
type Task,

@coderabbitai coderabbitai Bot Jul 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

대시보드 태스크를 Mock 데이터로 되돌리지 마세요.

이 변경으로 MyTasks가 Supabase의 실제 스프린트 태스크 대신 getMockSprintTasks를 계속 사용합니다. 따라서 스프린트 보드에서 생성·수정·상태 변경한 내용이 대시보드에 반영되지 않고, PR 목표인 대시보드 데이터 소스의 실 DB 전환도 충족하지 못합니다. useSprintTasks 등 기존 React Query 경로에서 조회한 데이터를 사용하도록 복원하세요.

Also applies to: 21-21

🤖 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 `@src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx` at line 9,
MyTasks가 getMockSprintTasks를 사용하지 않도록 제거하고, useSprintTasks 등 기존 React Query 경로에서
조회한 실제 Supabase 스프린트 태스크를 사용하도록 복원하세요. 스프린트 보드의 생성·수정·상태 변경이 대시보드에 반영되도록 해당 훅의
데이터와 로딩·빈 상태 처리 흐름을 유지하세요.

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.

실 DB 연동 대상이라면 조회 실패 또는 빈 데이터 상황에서 목업으로 fallback하지 않는 방향이 맞을 거 같습니다. useSprintTasks와 같은 React Query 기반 조회 결과를 사용하고 로딩 중, 조회 실패, 정상 조회 시처럼 구분하여 표시하면 좋을 거 같습니다

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@seongjinss555, 전적으로 동의합니다. 실 DB 연동 대상이라면 조회 실패나 빈 데이터를 목업으로 감추는 대신, useSprintTasks 결과의 isPending/isError/데이터 유무를 명시적으로 구분해서 보여주는 게 맞습니다. 예를 들면:

const { data: sprintTasks, isPending, isError } = useSprintTasks(currentSprint?.id);

if (isPending) return <WidgetCard>{header}<LoadingState /></WidgetCard>;
if (isError) return <WidgetCard>{header}<ErrorState /></WidgetCard>;
if (!sprintTasks || sprintTasks.length === 0) return <WidgetCard>{header}<EmptyState /></WidgetCard>;

이렇게 하면 스프린트 보드에서 생성·수정·상태 변경한 내용이 대시보드에도 즉시 반영되고, 실패/빈 상태를 목업으로 가리는 문제도 없어집니다.


✏️ Learnings added
Learnt from: seongjinss555
Repo: TeampleRun/syncly PR: 44
File: src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx:9-9
Timestamp: 2026-07-13T06:28:33.452Z
Learning: In `src/widgets/side-project/dashboard-my-tasks/ui/MyTasks.tsx` (and similar dashboard widgets consuming Supabase-backed entities like `@/entities/side-project/task`), do not fall back to mock data (e.g., `getMockSprintTasks`) when a React Query hook such as `useSprintTasks` returns a loading, error, or empty state. Instead, use the query's `isPending`/`isError`/data states to render distinct loading, error, and empty UI, so dashboard widgets always reflect real Supabase data.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/views/progress-chart/ui/ProgressChartPage.tsx`:
- Line 24: Update the ProgressChartPage render of ProgressChartView to pass the
current workspace’s actual ID instead of the hardcoded 'test' value, using the
existing workspace context or page data source. Preserve the workspace-specific
mock task lookup performed by ProgressChartView.
🪄 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: 073eaf77-c697-42f8-a590-4f185576ab39

📥 Commits

Reviewing files that changed from the base of the PR and between b9e6745 and 31dc537.

📒 Files selected for processing (1)
  • src/views/progress-chart/ui/ProgressChartPage.tsx

return (
<div className={`${plusJakartaSans.className} bg-brand-surface min-h-full`}>
<ProgressChartView workspaceId={workspaceId} />
<ProgressChartView workspaceId={'test'} />

@coderabbitai coderabbitai Bot Jul 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

실제 workspaceId를 전달해야 합니다.

ProgressChartView는 전달받은 ID로 워크스페이스별 mock 업무를 조회하는데, 현재 모든 비사이드 프로젝트가 'test'의 데이터를 렌더링합니다. 이로 인해 다른 워크스페이스에서 잘못된 진행률 차트가 표시됩니다.

-      <ProgressChartView workspaceId={'test'} />
+      <ProgressChartView workspaceId={workspaceId} />
📝 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
<ProgressChartView workspaceId={'test'} />
<ProgressChartView workspaceId={workspaceId} />
🤖 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 `@src/views/progress-chart/ui/ProgressChartPage.tsx` at line 24, Update the
ProgressChartPage render of ProgressChartView to pass the current workspace’s
actual ID instead of the hardcoded 'test' value, using the existing workspace
context or page data source. Preserve the workspace-specific mock task lookup
performed by ProgressChartView.

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.

현재 test 쪽 하드코딩으로 인해 team/store 워크스페이스에서도 test 목업 데이터가 표시될 수 있을 거 같습니다. 피드백처럼workspaceId={workspaceId}로 전달하도록 수정하면 좋을 거 같습니다. 아니면 혹시 test로 하신 이유가 있을까요..??

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f . src/features/manage-progress-chart

Length of output: 5569


🏁 Script executed:

#!/bin/bash
rg -n "getMockTasksByWorkspaceId" -A 15 src/features/manage-progress-chart/model/progress-chart.ts

Length of output: 418


🏁 Script executed:

#!/bin/bash
rg -n "getMockTasksByWorkspaceId" -A 20 -r "" src -g '*.ts' -g '*.tsx'
</br>
rg -n "getMockTasksByWorkspaceId" src -A 20
</br>
fd -t f -a mock src/features/manage-progress-chart
</br>
rg -n "workspaceId" src/features/manage-progress-chart -n

Length of output: 8294


The comment/TODO in the file explains the intent, and I dug into manage-progress-chart to confirm the actual behavior:

  • src/features/manage-progress-chart/ui/ProgressChartView.tsx still calls getMockTasksByWorkspaceId(workspaceId) from src/entities/task/model/mock-tasks-by-workspace.ts, which does a plain lookup: mockTasksByWorkspaceId[workspaceId]?.map(...) ?? [].
  • So 'test' was hardcoded because the mock dataset is keyed by that literal id, and this view (owned by another teammate per the code comment) hasn't been wired to Supabase yet — the TODO explicitly defers real integration to that follow-up work.

That said, the concern you and the original review raised is still valid: today, every non-side-project workspace (team/store) renders the same 'test' mock dataset instead of something scoped to its own id. Two options:

  1. Minimal safe fix now: switch to workspaceId={workspaceId}. Since mockTasksByWorkspaceId falls back to [] for unknown ids, real workspaces would just show an empty/zeroed chart instead of someone else's 'test' demo data — less confusing, and it's a no-risk one-line change.
  2. Leave as-is: keep 'test' intentionally as a working demo placeholder until the real DB integration for manage-progress-chart lands, relying on the TODO comment to track it.

Given the risk of showing unrelated mock data across workspaces, I'd lean toward option 1 as a quick safety improvement even though it doesn't fully resolve the mock-data gap — but happy to leave it if you'd rather track this purely via the TODO for the other owner's follow-up.


🧬 Code Graph Analysis Results

Relevant snippets

src/views/progress-chart/ui/ProgressChartPage.tsx (provided in prompt, approx. line 1-27)

// side-project 진행률만 실 DB 연동 완료.
// 그 외 purpose는 manage-progress-chart 뷰(다른 담당자, mock 기반) — 실 API 연동 전이라
// workspaceId 'test' 하드코딩 상태. 실 연동은 해당 담당자 작업으로 남김.
// TODO(담당자): ProgressChartView 실 API 연동 + workspaceId={workspaceId} 전달
import { ProgressChartView } from '`@/features/manage-progress-chart`';
import { ProgressChartView as SideProjectProgressChartView } from '`@/views/side-project/progress-chart`';
import { plusJakartaSans } from '`@/shared/lib/fonts`';
import { getWorkspaceById } from '`@/entities/workspace/api/get-workspace-by-id`';
import { notFound } from 'next/navigation';

interface ProgressChartPageProps {
  workspaceId: string;
}

export default async function ProgressChartPage({ workspaceId }: ProgressChartPageProps) {
  const workspace = await getWorkspaceById(workspaceId);
  if (!workspace) return notFound();

  if (workspace.purpose === 'side-project') {
    return <SideProjectProgressChartView workspaceId={workspaceId} />;
  }
  return (
    <div className={`${plusJakartaSans.className} bg-brand-surface min-h-full`}>
      <ProgressChartView workspaceId={'test'} />
    </div>
  );
}

src/features/manage-progress-chart/ui/ProgressChartView.tsx (around lines 175-223)

export function ProgressChartView({ workspaceId }: ProgressChartViewProps) {
  const tasks = getMockTasksByWorkspaceId(workspaceId);
  const summary = createProgressChartSummary(tasks);

  return (
    <section className="w-full max-w-[1280px]">
      <header className="mb-6">
        <h1 className="text-brand-ink text-[28px] leading-[1.15] font-extrabold tracking-[-0.05em]">
          진행률 차트
        </h1>
      </header>

      <div className="grid grid-cols-1 gap-4 xl:grid-cols-12">
        <div className="xl:col-span-4">
          <SummaryNumberCard value={summary.totalTaskCount} label="전체 업무" />
        </div>
        <div className="xl:col-span-4">
          <SummaryNumberCard
            value={summary.doneTaskCount}
            label="완료"
            valueClassName="text-[`#00b73d`]"
          />
        </div>
        <div className="xl:col-span-4">
          <SummaryNumberCard
            value={summary.inProgressTaskCount}
            label="진행 중"
            valueClassName="text-[`#615bff`]"
          />
        </div>

        <div className="xl:col-span-6">
          <OverallProgressCard
            progress={summary.overallProgressRate}
            doneCount={summary.doneTaskCount}
            totalCount={summary.totalTaskCount}
          />
        </div>
        <div className="xl:col-span-6 xl:row-span-2">
          <AssigneeBarChartCard items={summary.assigneeItems} />
        </div>

        <div className="xl:col-span-6">
          <StatusDistributionCard items={summary.statusItems} />
        </div>
      </div>
    </section>
  );
}

src/views/side-project/progress-chart/ui/ProgressChartView.tsx (around lines 28-64)

export function ProgressChartView({ workspaceId }: { workspaceId: string }) {
  const sprintsQuery = useSprints(workspaceId);
  // 진행 중(오늘이 기간 안) 스프린트 우선 → 없으면 최신. 로딩 중이면 undefined.
  const sprint = sprintsQuery.data ? resolveCurrentSprint(sprintsQuery.data) : undefined;
  const tasksQuery = useSprintTasks(sprint?.id);

  if (sprintsQuery.isPending) return <CenteredMessage>불러오는 중…</CenteredMessage>;
  if (sprintsQuery.isError) return <CenteredMessage>진행률을 불러오지 못했습니다.</CenteredMessage>;

  // 스프린트가 하나도 없는 워크스페이스 — 빈 상태
  if (!sprint) return <CenteredMessage>아직 생성된 스프린트가 없습니다.</CenteredMessage>;

  if (tasksQuery.isPending) return <CenteredMessage>불러오는 중…</CenteredMessage>;
  if (tasksQuery.isError) return <CenteredMessage>업무를 불러오지 못했습니다.</CenteredMessage>;

  const velocity = selectVelocity(sprintsQuery.data);
  const statusCounts = countByStatus(tasksQuery.data);

  return (
    <div className={`${jakarta.className} bg-brand-surface min-h-full`}>
      <div className="flex flex-col gap-4">
        <ProgressStatRow sprint={sprint} />

        <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
          {/* 좌: 진행률 바(짧음) + 상태 도넛(김) 세로 스택 */}
          <div className="flex flex-col gap-4">
            <SprintProgressCard sprint={sprint} />
            <StatusDonutChart counts={statusCounts} className="flex-1" />
          </div>

          {/* 우: 벨로시티 막대(좌측 컬럼 전체 높이) */}
          <VelocityChart data={velocity} />
        </div>
      </div>
    </div>
  );
}

src/entities/workspace/api/get-workspace-by-id.ts (around lines 7-29)

export const getWorkspaceById = cache(async (workspaceId: string): Promise<Workspace | null> => {
  const supabase = await createSupabaseServerClient();
  const { data, error } = await supabase
    .from('workspaces')
    .select('id, name, description, purpose')
    .eq('id', workspaceId)
    .maybeSingle();

  if (error) {
    throw new Error(`워크스페이스 조회에 실패했습니다: ${error.message}`);
  }

  if (!data) {
    return null;
  }

  return {
    id: data.id,
    name: data.name,
    description: data.description ?? undefined,
    purpose: toUiPurpose(data.purpose),
  };
});

✏️ Learnings added
Learnt from: seongjinss555
Repo: TeampleRun/syncly PR: 44
File: src/views/progress-chart/ui/ProgressChartPage.tsx:24-24
Timestamp: 2026-07-13T06:31:12.288Z
Learning: In `src/views/progress-chart/ui/ProgressChartPage.tsx`, the non-side-project branch renders `@/features/manage-progress-chart`'s `ProgressChartView` with a hardcoded `workspaceId={'test'}` intentionally, because that view still uses mock data (`getMockTasksByWorkspaceId` from `src/entities/task/model/mock-tasks-by-workspace.ts`, keyed by literal workspace ids like `'test'`) and has not yet been migrated to Supabase. This is tracked via an in-file TODO comment as a follow-up for a different owner; using the real `workspaceId` would currently just return an empty task list since the mock dataset isn't keyed by real workspace ids.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@seongjinss555 seongjinss555 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.

한 번 확인해주세요~

return (
<div className={`${plusJakartaSans.className} bg-brand-surface min-h-full`}>
<ProgressChartView workspaceId={workspaceId} />
<ProgressChartView workspaceId={'test'} />

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.

현재 test 쪽 하드코딩으로 인해 team/store 워크스페이스에서도 test 목업 데이터가 표시될 수 있을 거 같습니다. 피드백처럼workspaceId={workspaceId}로 전달하도록 수정하면 좋을 거 같습니다. 아니면 혹시 test로 하신 이유가 있을까요..??

import { getSprintTasks, Task, TASK_STATUS, type TaskStatus } from '@/entities/side-project/task';
import {
getMockSprintTasks,
type Task,

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.

실 DB 연동 대상이라면 조회 실패 또는 빈 데이터 상황에서 목업으로 fallback하지 않는 방향이 맞을 거 같습니다. useSprintTasks와 같은 React Query 기반 조회 결과를 사용하고 로딩 중, 조회 실패, 정상 조회 시처럼 구분하여 표시하면 좋을 거 같습니다

@Kwon812

Kwon812 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

한 번 확인해주세요~

지웅님 템플릿 진행률차트가 아직 db전환이 안되어있어서 workspaceId를 넣으면 팀플템플릿 진행률 차트에 아무것도 안뜨더라구요 그래서 팀프로젝트 진행률차트뷰는 기존에 지웅님 목 표시방식으로 하드코딩 해두었습니다! 추후 지웅님이 db전환하시면 'test'말고 원래 방식대로 워크스페이스 아이디 넣는 방식으로 전환해주시면 될거에요

아직 대시보드 위젯은 연결 안해놓은 상태입니다! 해당하는 위젯들은 일단 목데이터로 나두고 전환은 나중에 별도 이슈로 한번에 진행하려고 합니다!( 목 정리 + 위젯별 api엔티티 연결)

@seongjinss555 seongjinss555 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.

확인했습니다 승인해드릴게요~ 고생하셨습니다

@Kwon812
Kwon812 merged commit 9baf0c7 into develop Jul 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 스프린트 보드 페이지 스프린트 생성버튼 추가 / supabase db 연결

2 participants