Skip to content

feat(S-DOCS2): 트리 D&D 크로스-부모 이동 — parent_id 변경 + 순환 이동 방지#245

Merged
AngryJay91 merged 2 commits intomainfrom
feat/SID-E036-DOCS2-tree-dnd-move
Apr 15, 2026
Merged

feat(S-DOCS2): 트리 D&D 크로스-부모 이동 — parent_id 변경 + 순환 이동 방지#245
AngryJay91 merged 2 commits intomainfrom
feat/SID-E036-DOCS2-tree-dnd-move

Conversation

@AngryJay91
Copy link
Copy Markdown
Contributor

Summary

S-DOCS2 — 트리 D&D 크로스-부모 문서 이동

변경 파일: doc-tree.tsx, docs-shell-client.tsx, doc-tree.test.ts, en.json, ko.json

AC 체크리스트

  • 트리에서 문서 노드를 다른 부모 아래로 드래그하여 parent_id 변경
    • onMove(docId, newParentId, newSortOrder) 콜백으로 PATCH /api/docs/{id} 호출
    • Optimistic update + 실패 시 fetchTree rollback
  • 같은 depth 내에서 순서(sort_order) 변경 가능
    • 기존 same-parent 재정렬 동작 그대로 유지
  • 순환 이동 방지 (자기 자신의 하위로 드롭 불가)
    • isDescendant(docs, activeDoc.id, overDoc.id) 순수 함수로 감지
    • 차단 시 onMoveDenied('circular') → 에러 토스트
  • 권한 없는 위치로 드롭 차단 + 토스트
    • onMove prop 미제공 시 크로스-부모 드롭 차단
    • onMoveDenied('no-permission') → 경고 토스트

주요 구현

doc-tree.tsx

// 순환 감지 순수 함수 (export)
export function isDescendant(docs, ancestorId, nodeId): boolean { ... }

// handleDragEnd — 크로스-부모 분기
if (activeDoc.parent_id !== overDoc.parent_id) {
  if (isDescendant(docs, activeDoc.id, overDoc.id)) { onMoveDenied?.('circular'); return; }
  if (!onMove) { onMoveDenied?.('no-permission'); return; }
  await onMove(activeDoc.id, overDoc.parent_id, overDoc.sort_order);
  return;
}
// same-parent: 기존 onReorder 경로

docs-shell-client.tsx

  • handleMove: PATCH /api/docs/{id} { parent_id, sort_order } + optimistic update
  • handleMoveDenied: useToastaddToast (circular=error, no-permission=warning)
  • <ToastContainer> 추가

유닛 테스트 12케이스 (doc-tree.test.ts)

  • isDescendant 7케이스: 직접 자식, 손자, 형제, 역방향, 무관, 존재하지 않는 노드
  • drag guard 5케이스: 동일부모 재정렬, 크로스-부모 이동, 권한없음 차단, 순환 차단, 리프→형제 서브트리

Test plan

  • pnpm vitest run "doc-tree.test" → 12/12 pass
  • pnpm type-check → 5 successful
  • 트리에서 노드 드래그 → 다른 부모 아래 드롭 → parent_id 변경 확인
  • 자기 하위로 드롭 시도 → 차단 토스트 확인
  • 같은 레벨 재정렬 동작 회귀 없음 확인

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 15, 2026

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

Project Deployment Actions Updated (UTC)
sprintable Ready Ready Preview, Comment Apr 15, 2026 0:11am

- isDescendant() 순수 함수: 순환 이동 감지 (자기 하위로 드롭 차단)
- DocTreeProps에 onMove / onMoveDenied 추가
- handleDragEnd: 크로스-부모 드롭 시 onMove 호출; 순환·권한 없을 때 onMoveDenied 콜백
- 기존 same-parent 재정렬 동작 유지
- DocsShellClient: handleMove (PATCH /api/docs/{id} parent_id+sort_order) 연동
- ToastContainer로 차단 사유(circular/no-permission) 토스트 표시
- i18n: moveCircularError / movePermissionError 키 추가 (en, ko)
- 유닛 12케이스 (isDescendant 7 + drag guard 5), type-check 클린

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
handleDragEnd was using overDoc.parent_id as newParentId, placing the
dragged node as a sibling of the drop target instead of a child.
Fix: use overDoc.id so the drop target becomes the new parent.

Sync simulateDrag helper + expected values to match corrected behaviour.
12/12 doc-tree tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@AngryJay91 AngryJay91 merged commit 072f086 into main Apr 15, 2026
3 of 4 checks 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.

1 participant