칸반 드래그 정렬 버그와 우선순위 정렬 철회
오늘의 목표와 맥락
사내 업무관리 웹 서비스의 칸반 영역을 이어서 작업했다. 전 세션이 중간에 끊겨 회귀 테스트가 커밋되지 않은 채 남아 있었고, 설계 문서의 적용 순서 중 6단계 하나만 "보류" 상태였다.
시작점은 단순했다. 남은 6단계를 끝내고, 데이터를 정리하고, 사용자가 실제로 써보며 발견한 문제를 잡는 것. 결과적으로 드래그 정렬이 겉으로만 동작하고 있었다는 사실을 찾아내면서 설계 결정 하나를 되돌리게 됐다.
구현한 내용
크게 네 덩어리였고, 모두 Structural / Behavioral 을 분리해 커밋했다. 총 16개 커밋.
프로젝트 미정 할일 허용
할일의 project_id 를 nullable 로 완화했다(Alembic 0029). 아직 어느 프로젝트 것인지 모르는 아이디어도 일단 백로그에 적어두고 나중에 배정할 수 있다.
구현하면서 기존 결함 하나가 드러났다. 할일의 프로젝트를 바꿀 때 새 컬럼의 순서를 다시 매기지 않아, 카드가 옛 자리 번호를 그대로 들고 건너와 순서가 겹쳤다. "나중에 배정"이 이 기능의 핵심이라 반드시 걸리는 경로였다.
데이터 백필
이미 쌓여 있던 할일 25건을 마일스톤에 연결했다. project_id 는 이미 모두 채워져 있었고 실제로 빈 곳은 마일스톤 쪽뿐이었다.
규칙은 마감일 기준으로 잡았다. "종료일이 그 마감일 이상인 마일스톤 중 가장 이른 것"에 넣고, 못 잡으면 그 프로젝트의 마지막 마일스톤으로 보낸다. 마일스톤이 하나도 없던 프로젝트에는 새로 하나 만들어 연결했다.
칸반 드래그 개선
- 같은 컬럼 안에서 순서 이동이 되도록 수정
- 드래그 중 들어갈 자리를 열어 보여주는 효과 추가
- 카드를 누르면 편집 모달이 열리도록
- 마일스톤 모달 확대
프론트 테스트 러너 도입
순수 로직을 컴포넌트 밖에서 검증할 수단이 없어 Vitest 를 추가하고 CI 에 단계를 넣었다. globals 는 켜지 않았다. TypeScript 6 은 types 기본값이 [] 라 전역 타입이 자동으로 붙지 않기 때문에, import { describe, it, expect } from "vitest" 방식이 tsconfig 를 건드리지 않는다.
설계와 판단
백로그 우선순위 자동 정렬을 철회했다
오늘의 가장 큰 결정이다. 원래 백로그 컬럼은 우선순위 → 순서 로 자동 정렬했다. 의도는 좋았다. 급한 일이 위로 올라오니까.
문제는 정렬을 우선순위가 지배하면 드래그가 화면에 아무 효과를 못 낸다는 것이었다. 서버 요청은 정상적으로 나가고 order 값도 실제로 바뀌는데, 화면은 그대로다. 사용자 입장에서는 "이동이 안 된다"로 보인다.
드레그로 위치이동은 되지만 실제로 이동을 마치고 나면 원래 자리에 유지된다.
우선순위를 색 점과 필터로만 남기고 순서는 사람이 정하도록 바꿨다. 설계 문서에는 취소선과 함께 왜 되돌렸는지를 남겼다. 같은 설계가 나중에 다시 제안되는 걸 막기 위해서다.
덤으로 숨은 결함 하나가 함께 사라졌다. 화면 순서(우선순위순)와 서버 목록 순서(order순)가 서로 달라 우리가 보내는 index 가 어긋나고 있었는데, 두 순서가 같아지면서 근본적으로 없어졌다. 보정 코드를 넣지 않아도 됐다.
위치 판정에 offsetTop 을 쓴 이유
드래그 중 카드를 밀어 자리를 열려면 translateY 를 걸어야 한다. 그런데 위치 판정을 getBoundingClientRect 로 하면 transform 이 반영되어 → 판정이 바뀌고 → 다시 밀고 → 또 바뀌는 진동이 생긴다.
offsetTop 은 transform 의 영향을 받지 않는다. 레이아웃 좌표로 판정하면 드래그 내내 기준이 고정된다. 자리를 여는 padding-bottom 역시 자식의 offsetTop 을 바꾸지 않아 안전하다.
클릭과 드래그를 거리로 가른다
카드 클릭으로 편집 모달을 열 때, 끌어서 옮긴 경우는 열리면 안 된다. "드래그 뒤에 click 이 따라오는지"는 브라우저마다 달라 이벤트 순서에 기댈 수 없었다.
대신 누른 지점에서 움직인 거리로 판단했다. 5px 이내면 클릭. 어느 브라우저에서나 같게 동작하고 손떨림으로 모달이 안 열리는 일도 없다.
문제와 해결
같은 컬럼 재정렬이 안 되던 버그 — 원인이 두 개였다
증상은 하나인데 원인은 둘이었다.
| 원인 | 내용 |
|---|---|
| 조기 반환 | 같은 컬럼 드롭을 if (task.status === status) return 으로 버려서 요청 자체가 안 나감 |
| 위치 지정 수단 없음 | 드롭 핸들러가 컬럼에만 걸려 있고 순서가 항상 "맨 뒤"로 하드코딩 |
첫 번째만 고쳤다면 카드가 맨 아래로만 갔을 것이다. 백엔드는 이미 임의 위치 삽입을 지원하고 있어 순수 프론트엔드 결함이었다.
드롭이 두 번 처리되던 문제
목록과 컬럼에 모두 onDrop 이 걸려 있어 이벤트가 버블링되며 이동 요청이 두 번 나갔다. 같은 렌더의 클로저라 두 번째 호출에서도 상태가 아직 살아 있었다. stopPropagation 으로 끊었다.
구버전 서버로 검증한 헛발질
정렬을 고치고 실서버로 검증했는데 결과가 그대로였다. 잠깐 수정이 틀렸나 싶었지만, --reload 없이 띄운 서버가 변경 전 코드로 돌고 있었던 것. 재시작 후 정상 확인했다.
작은 실수지만 잘못된 결론을 낼 뻔했다. 로컬 검증에서는 서버가 지금 어떤 코드로 떠 있는지부터 확인하는 게 맞다.
검증과 다음 단계
검증
- 백엔드: 108개 통과, ruff clean, 실제 PostgreSQL 에 마이그레이션 적용 후
alembic check드리프트 없음 - 프론트: Vitest 17개 통과, eslint / tsc / build 통과
- 실서버 왕복: 임시 프로젝트를 만들어 HTTP API 로 실제 이동을 재현하고 끝나면 삭제
특히 우선순위를 일부러 섞은 백로그로 왕복 검증한 게 유효했다. 이전 코드였다면 세 번의 이동이 모두 같은 결과로 고정됐을 자리다.
남은 것
브라우저 자동화 도구가 로컬 dev 서버를 열지 못해 시각 확인은 못 했다. 순수 로직은 테스트로 덮었지만 인터랙션 자체는 사람 눈이 필요하다.
- 드래그 중 자리 열림이 부드러운지, 깜빡이지 않는지
- 살짝 끌었다 놓았을 때 편집 모달이 안 뜨는지
- 삭제 버튼 클릭 시 편집 모달이 같이 안 뜨는지
- 백로그에서 급한 일이 아래에 묻히지 않는지
마지막 항목이 신경 쓰인다. 우선순위 정렬을 뺀 대가가 실제로 어떤지는 며칠 써봐야 안다. 불편하면 정렬 토글을 붙이는 쪽을 검토할 생각이다.
관련 글
댓글
아직 댓글이 없습니다.
댓글을 작성하려면 GitHub로 로그인하세요.