Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Change Summary

- commit: `c0734f2 fix(room): 큐와 모달 UI 표시 오류를 교정`
- PR: https://github.com/Queuing-org/frontend/pull/61

## Delivered

- 내 노래 현재 재생곡/history 중복 제거
- 노래 신청 다음 섹션 간격 40px 통일
- 방 내부 Settings/Friends dim backdrop
- Friends/Settings 데스크톱 기본 높이 540px
- Friends 사용자 상세 상단 padding 복원

## QA

- targeted/full test, lint, build, diff-check 통과
- fresh reviewer 최종 `pass`
- browser visual QA는 세션 unavailable로 미실시
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Delivery State

- status: ci-pending
- branch: dev
- base: main
- issue:
- pr: https://github.com/Queuing-org/frontend/pull/61
- selected_skills: queuing-feature-delivery, queuing-orchestrator, queuing-ui-flow, frontend-architecture-guardrails, queuing-qa-reviewer
- local_qa: pass — targeted 5 files/26 tests, lint, full test 155 files/688 tests, build, diff-check, fresh QA pass
- ci: pending
- review_threads: none
- next_action: Draft PR #61 CI와 human visual review를 확인한다.
71 changes: 71 additions & 0 deletions docs/exec-plans/active/2026-09-05-room-queue-modal-polish/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 방 큐·모달 UI 교정

## Scope

- 내 노래 탭에서 현재 재생곡과 같은 history 항목이 중복 표시되지 않게 한다.
- 노래 신청 URL 입력 영역 이후 다음 섹션까지의 간격을 40px로 통일한다.
- 방 내부에서 연 Settings/Friends 모달만 배경을 어둡게 처리한다.
- Friends와 Settings 데스크톱 모달의 기본 높이를 540px로 맞춘다.
- Friends 목록에서 연 프로필 상세의 누락된 상단 패딩을 복원한다.

## Acceptance Criteria

- 동일한 `entryId`의 현재곡과 history는 내 노래 타임라인에 한 번만 보인다.
- 일반 URL 폼은 URL 입력 필드 아래부터 선정 이유 타이틀까지 40px이다.
- 재생목록 선택지가 나타난 폼은 선택지 영역 아래부터 선정 이유 타이틀까지 40px이다.
- 방 내부 Settings/Friends가 열리면 방 배경에 dim이 생기고 홈/검색 호출부는 기존 배경을 유지한다.
- Friends와 Settings 데스크톱 기본 모달 높이는 540px이다.
- Friends 프로필 상세의 본문 상단 여백이 일반/compact 레이아웃 비율에 맞게 보인다.

## Selected Skills

- queuing-feature-delivery
- queuing-orchestrator
- queuing-ui-flow
- frontend-architecture-guardrails
- queuing-qa-reviewer

## Ownership

- 큐 타임라인 파생 데이터: `useRoomQueuePanel`
- 노래 신청 레이아웃: `AddTrackModal.module.css`
- 방 내부 modal dim 여부: `RoomPlaybackJoinedContent`가 호출 문맥을 소유하고 각 modal은 표현 옵션만 받는다.
- Friends/Settings 크기와 Friends 상세 패딩: 각 feature CSS module

## Commit Slices

1. `fix(room): 큐와 모달 UI 표시 오류를 교정`
2. `docs(delivery): 방 큐와 모달 UI QA를 기록`

## Progress

- [x] 관련 렌더 경로와 CSS 원인 확인
- [x] 실행 계획/상태 기록
- [x] 구현 및 targeted test
- [x] lint/test/build
- [x] fresh read-only QA
- [x] feature commit (`c0734f2`), push, Draft PR #61 게시

## Verification

- queue hook 및 room modal targeted Vitest
- `npm run lint`
- `npm run test`
- `npm run build`
- `git diff --check`

결과:

- targeted Vitest: 5 files / 26 tests pass
- full Vitest: 155 files / 688 tests pass
- lint: pass
- build: pass
- `git diff --check`: pass
- fresh read-only QA: 1차 `fix`, Settings compact 432px override 제거 후 최종 `pass`
- browser visual QA: 브라우저 세션 unavailable로 미실시

## Residual Risk

- 실제 브라우저의 40px 간격, dim stacking, 프로필 padding 픽셀 배치는 수동 확인이 필요하다.
- compact density는 57.6px 친구 행을 사용하므로 540px에서 약 8행이 보이며, 약 6.5행은 normal density 기준이다.
- 매우 낮은 데스크톱 viewport의 540px Settings 안전 영역은 후속 반응형 QA가 필요하다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# QA Report

## Result

`pass`

## Boundary Review

- Queue: 화면에 표시되는 현재곡의 `entryId`만 history에서 제거해 현재곡/history 이중 렌더를 방지한다. 자동 재생이나 내 곡이 아닌 현재곡은 기존 history를 유지한다.
- Add track: URL field의 다음 형제가 story field인 경로와 playlist fieldset인 경로를 분리해, 각각 story title 직전 간격을 40px로 맞췄다.
- Room modal: `RoomPlaybackJoinedContent`만 `dimBackdrop`을 활성화한다. Home/Search의 기존 Settings/Follow 호출부는 기본값 `false`를 유지한다.
- Modal height: Friends는 49vh 대신 540px 기준을 사용한다. Settings는 base 540px을 덮던 compact 432px override를 제거했다.
- Follow profile: normal 16px, compact 12.8px 상단 padding을 복원했다.
- API, mutation, React Query cache, websocket 계약 변경은 없다.

## Fresh Review

- 1차 판정: `fix`
- finding: Settings compact media query의 432px 높이가 540px 요구를 덮어씀
- fix: compact height override 제거
- 최종 판정: `pass`, blocking finding 없음

## Verification

- targeted Vitest: 5 files / 26 tests pass
- `npm run lint`: pass
- `npm run test`: 155 files / 688 tests pass
- `npm run build`: pass
- `git diff --check`: pass

## Residual Risk

- 연결 가능한 브라우저 세션이 없어 실제 픽셀 시각 QA는 실행하지 못했다.
- compact density에서 Friends 행 높이는 57.6px이므로 540px modal에 보이는 행 수가 normal density의 약 6.5행과 다르다.
- 매우 낮은 데스크톱 viewport의 세부 반응형은 이번 요청의 비목표다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Request Summary

2026-09-05 사용자 요청:

1. 내 노래 탭에서 재생 중인 노래가 두 개로 보이는 중복 버그 수정
2. 유튜브 링크 입력 다음 선정 이유 타이틀까지 40px 간격 적용; 재생목록 선택지가 생기면 그 영역 아래부터 40px 적용
3. 방 내부 Settings/Friends 모달이 열릴 때 배경 dim 적용
4. Friends/Settings 탭 기본 높이를 540px로 맞춰 팔로잉 목록 약 6.5개가 보이게 조정
5. Friends 목록에서 사용자를 눌러 연 프로필 상세의 잘못된 패딩을 기존 프로필 패턴에 맞게 교정

첨부 이미지는 1번 중복 버그를 보여 준다.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# UI Flow

## Queue

`history query -> useRoomQueuePanel derived historyEntries -> RoomQueueHistoryList`

- 현재곡 `entryId`는 타임라인 경계에서 별도로 렌더하므로 history 파생 배열에서 제외한다.
- 전체/내 노래 필터 뒤에 동일한 dedupe 규칙을 적용한다.

## Add Track

- URL field 다음 형제가 story field이면 40px.
- URL field 다음에 playlist fieldset이 있으면 fieldset 자체 간격은 유지하고 fieldset 다음 story field 간격을 40px.

## Room Self Modals

- `RoomPlaybackJoinedContent`가 방 내부 호출 문맥을 소유한다.
- SettingsModal/FollowModal은 `dimBackdrop` 표현 옵션을 받고 방 호출부에서만 활성화한다.
- modal open/close local state와 홈/검색 동작은 변경하지 않는다.

## Modal Size and Profile Padding

- desktop Friends/Settings modal은 540px 기준 높이를 사용하며 viewport보다 클 때만 줄어든다.
- Friends profile body에는 일반 16px, compact 12.8px의 상단 패딩을 적용해 기존 80% density 규칙을 유지한다.
2 changes: 2 additions & 0 deletions docs/exec-plans/active/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Active Execution Plans

- [2026-09-05-room-queue-modal-polish](./2026-09-05-room-queue-modal-polish/plan.md): ci-pending — Draft PR #61, 내 노래 현재곡 중복·신청 폼 간격·방 modal dim/높이·Friends 상세 padding 교정 및 로컬 QA 통과

- [2026-09-02-youtube-playlist-import](./2026-09-02-youtube-playlist-import/plan.md): ci-pending — Draft PR #60, 재생목록 감지 시 현재 영상/재생목록 노래 추가 선택을 요구하고 후속 로컬 QA 통과

- [2026-09-01-room-self-participant-menu](./2026-09-01-room-self-participant-menu/plan.md): ci-pending — Draft PR #59, 내 참가자 첫 행·`(나)`·Settings/Friends modal과 reconnect identity QA 통과
Expand Down
6 changes: 5 additions & 1 deletion src/features/follow/ui/FollowModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
z-index: 1400;
}

.overlay[data-dim-backdrop="true"] {
background: rgba(0, 0, 0, 0.64);
}

.modal {
--panel-horizontal-padding: 16px;
--panel-content-width: 1060px;
Expand All @@ -22,7 +26,7 @@
calc(var(--panel-content-width) + (var(--panel-horizontal-padding) * 2)),
calc(100vw - 32px)
);
height: min(49vh, calc(100vh - 48px));
height: min(540px, calc(100dvh - 48px));
box-sizing: border-box;
overflow: hidden;
border: 0.5px solid #d9d9d9;
Expand Down
8 changes: 7 additions & 1 deletion src/features/follow/ui/FollowModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import type { FollowUser } from "../model/types";
import FollowProfileModal from "./FollowProfileModal";

type FollowModalProps = {
dimBackdrop?: boolean;
open: boolean;
onClose: () => void;
};

export default function FollowModal({ open, onClose }: FollowModalProps) {
export default function FollowModal({
dimBackdrop = false,
open,
onClose,
}: FollowModalProps) {
const modal = useFollowModalState({ onClose, open });
const addFriendButtonRef = useRef<HTMLButtonElement>(null);
const selectedUserTriggerRef = useRef<HTMLButtonElement | null>(null);
Expand Down Expand Up @@ -49,6 +54,7 @@ export default function FollowModal({ open, onClose }: FollowModalProps) {
<div
className={styles.overlay}
data-add-friend-open={modal.isAddFriendOpen || undefined}
data-dim-backdrop={dimBackdrop || undefined}
onClick={(event) => {
if (event.target === event.currentTarget) {
modal.closeModal();
Expand Down
4 changes: 2 additions & 2 deletions src/features/follow/ui/FollowProfileModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
box-sizing: border-box;
flex-direction: column;
height: 100%;
padding: 0 18px 56px;
padding: 16px 18px 56px;
background: #ffffff;
color: #3c3c3c;
}
Expand Down Expand Up @@ -114,7 +114,7 @@
}

.content {
padding: 0 14.4px 44.8px;
padding: 12.8px 14.4px 44.8px;
}

.actionRow {
Expand Down
12 changes: 6 additions & 6 deletions src/features/playlist/add-track/ui/AddTrackModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}

.fieldGroup + .fieldGroup {
margin-top: 24px;
margin-top: 40px;
}

.queueModeFieldset {
Expand All @@ -59,7 +59,7 @@
}

.queueModeFieldset + .fieldGroup {
margin-top: 24px;
margin-top: 40px;
}

.queueModeLegend {
Expand Down Expand Up @@ -303,15 +303,15 @@
}

.fieldGroup + .fieldGroup {
margin-top: 28px;
margin-top: 40px;
}

.queueModeFieldset {
margin-top: 24px;
}

.queueModeFieldset + .fieldGroup {
margin-top: 28px;
margin-top: 40px;
}

.queueModeLegend,
Expand Down Expand Up @@ -367,9 +367,9 @@
.title { font-size: 19.2px; }
.form { margin-top: 33.6px; }
.fieldGroup { gap: 11.2px; }
.fieldGroup + .fieldGroup { margin-top: 19.2px; }
.fieldGroup + .fieldGroup { margin-top: 40px; }
.queueModeFieldset { margin-top: 16px; }
.queueModeFieldset + .fieldGroup { margin-top: 19.2px; }
.queueModeFieldset + .fieldGroup { margin-top: 40px; }
.queueModeLegend, .queueModeOption { font-size: 12px; }
.queueModeLegend { margin-bottom: 9.6px; }
.queueModeOption { gap: 8px; padding: 8px 9.6px; border-radius: 6.4px; }
Expand Down
48 changes: 40 additions & 8 deletions src/features/room/page/ui/RoomPlaybackJoinedContent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,37 @@ vi.mock("@/src/features/room/floating/ui/RoomFloatingWidgets", () => ({
),
}));
vi.mock("@/src/features/settings/ui/SettingsModal", () => ({
default: ({ onClose }: { onClose: () => void }) => (
<div role="dialog" aria-label="Setting modal">
default: ({
dimBackdrop,
onClose,
}: {
dimBackdrop?: boolean;
onClose: () => void;
}) => (
<div
role="dialog"
aria-label="Setting modal"
data-dim-backdrop={dimBackdrop || undefined}
>
<button type="button" onClick={onClose}>
Setting 닫기
</button>
</div>
),
}));
vi.mock("@/src/features/follow/ui/FollowModal", () => ({
default: ({ onClose }: { onClose: () => void }) => (
<div role="dialog" aria-label="Friends modal">
default: ({
dimBackdrop,
onClose,
}: {
dimBackdrop?: boolean;
onClose: () => void;
}) => (
<div
role="dialog"
aria-label="Friends modal"
data-dim-backdrop={dimBackdrop || undefined}
>
<button type="button" onClick={onClose}>
Friends 닫기
</button>
Expand Down Expand Up @@ -181,13 +201,19 @@ describe("RoomPlaybackJoinedContent self modals", () => {
renderContent({ isMobileLayout: true });

await user.click(screen.getByRole("button", { name: "모바일 Setting 열기" }));
expect(screen.getByRole("dialog", { name: "Setting modal" })).toBeVisible();
expect(screen.getByRole("dialog", { name: "Setting modal" })).toHaveAttribute(
"data-dim-backdrop",
"true",
);

await user.click(screen.getByRole("button", { name: "Setting 닫기" }));
expect(screen.queryByRole("dialog", { name: "Setting modal" })).toBeNull();

await user.click(screen.getByRole("button", { name: "모바일 Friends 열기" }));
expect(screen.getByRole("dialog", { name: "Friends modal" })).toBeVisible();
expect(screen.getByRole("dialog", { name: "Friends modal" })).toHaveAttribute(
"data-dim-backdrop",
"true",
);
});

it("데스크톱 draggable widget의 요청도 화면 최상위 modal layer로 전달한다", async () => {
Expand All @@ -197,12 +223,18 @@ describe("RoomPlaybackJoinedContent self modals", () => {
await user.click(
screen.getByRole("button", { name: "데스크톱 Setting 열기" }),
);
expect(screen.getByRole("dialog", { name: "Setting modal" })).toBeVisible();
expect(screen.getByRole("dialog", { name: "Setting modal" })).toHaveAttribute(
"data-dim-backdrop",
"true",
);

await user.click(screen.getByRole("button", { name: "Setting 닫기" }));
await user.click(
screen.getByRole("button", { name: "데스크톱 Friends 열기" }),
);
expect(screen.getByRole("dialog", { name: "Friends modal" })).toBeVisible();
expect(screen.getByRole("dialog", { name: "Friends modal" })).toHaveAttribute(
"data-dim-backdrop",
"true",
);
});
});
4 changes: 2 additions & 2 deletions src/features/room/page/ui/RoomPlaybackJoinedContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,10 @@ export default function RoomPlaybackJoinedContent({
const selfModal = (
<>
{activeSelfModal === "settings" ? (
<SettingsModal open onClose={closeSelfModal} />
<SettingsModal dimBackdrop open onClose={closeSelfModal} />
) : null}
{activeSelfModal === "friends" ? (
<FollowModal open onClose={closeSelfModal} />
<FollowModal dimBackdrop open onClose={closeSelfModal} />
) : null}
</>
);
Expand Down
Loading