feat(B01,B03): 프로젝트에 연결된 보관함을 목록에서 제외 (E2E 결함 5)

연결된 묶음은 파일이 프로젝트로 옮겨져 내용이 비어 있는데도 목록에 남아, 파일 0건·용량
0·만료 없음·버튼 없음인 빈 껍데기가 영구히 쌓였다(2026-08-08 사용자 지시로 제외).

- list_temp_batches: status <> 'linked' 만 조회. 행 자체는 이력용으로 DB에 남긴다 —
  어느 자료가 어느 프로젝트로 갔는지 추적할 근거다.
- 대시보드 UI: 목록에 연결된 묶음이 오지 않으므로 "연결됨" 배지·삭제 버튼 숨김·파일 행
  비활성 분기를 걷어냈다(죽은 코드).

검증: 서버 재시작 후 목록 응답 0건, DB 보관 행 3건 유지.
typecheck·prettier·ruff 통과, 정적 번들 재빌드.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 20:00:12 +09:00
co-authored by Claude Opus 5
parent 431791c257
commit b3d8371882
2 changed files with 29 additions and 33 deletions
+22 -31
View File
@@ -113,16 +113,14 @@ export function buildTempUploadSection(): HTMLElement {
function fileRows(batch: TempBatchItem): HTMLElement[][] {
const rows: HTMLElement[][] = [];
const linked = batch.status === "linked";
for (const file of batch.files) {
const action = linked
? text("-")
: createButton({
label: L("Common_Btn_Delete"),
variant: "danger",
onClick: () => void removeFile(batch, file.file_type, file.original_filename),
});
// 목록에는 아직 프로젝트에 안 쓴 묶음만 온다 — 항상 지울 수 있다.
const action = createButton({
label: L("Common_Btn_Delete"),
variant: "danger",
onClick: () => void removeFile(batch, file.file_type, file.original_filename),
});
rows.push([
text(file.file_type.toUpperCase()),
text(file.original_filename),
@@ -192,12 +190,9 @@ export function buildTempUploadSection(): HTMLElement {
title.textContent = batch.name;
const badge = document.createElement("span");
badge.className = "b01-temp__badge";
badge.textContent =
batch.status === "linked"
? L("B01_Temp_Status_Linked")
: batch.required_complete
? L("B01_Temp_Status_Ready")
: L("B01_Temp_Status_Uploading");
badge.textContent = batch.required_complete
? L("B01_Temp_Status_Ready")
: L("B01_Temp_Status_Uploading");
const meta = document.createElement("span");
meta.className = "b01-temp__group-meta";
@@ -205,9 +200,7 @@ export function buildTempUploadSection(): HTMLElement {
`${batch.files.length}${L("B01_Temp_Meta_FileCount")}`,
formatTempBytes(batch.total_size_bytes),
];
if (batch.status === "linked") {
parts.push(L("B01_Temp_Meta_Linked"));
} else if (batch.expires_at) {
if (batch.expires_at) {
parts.push(`${L("B01_Temp_Meta_Expires")} ${formatDate(batch.expires_at)}`);
}
meta.textContent = parts.join(" · ");
@@ -217,20 +210,18 @@ export function buildTempUploadSection(): HTMLElement {
actions.className = "b01-dashboard__actions";
// 버튼 클릭이 접기/펼치기까지 건드리지 않게 막는다.
actions.addEventListener("click", (event) => event.stopPropagation());
if (batch.status !== "linked") {
actions.append(
createButton({
label: L("B01_Temp_Btn_Add"),
variant: "ghost",
onClick: () => openAddModal(batch),
}),
createButton({
label: L("Common_Btn_Delete"),
variant: "danger",
onClick: () => void removeBatch(batch),
}),
);
}
actions.append(
createButton({
label: L("B01_Temp_Btn_Add"),
variant: "ghost",
onClick: () => openAddModal(batch),
}),
createButton({
label: L("Common_Btn_Delete"),
variant: "danger",
onClick: () => void removeBatch(batch),
}),
);
head.append(titleRow, actions);
head.setAttribute("aria-expanded", String(opened));
head.addEventListener("click", () => {
@@ -70,14 +70,19 @@ async def list_temp_batches(
*,
user_id: int,
) -> list[dict[str, Any]]:
"""내 보관함 묶음 목록(최신순). 프로젝트로 옮긴 묶음도 이력으로 함께 준다."""
"""내 보관함 묶음 목록(최신순) — **아직 쓰지 않은 자료만**.
프로젝트에 연결된 묶음은 파일이 프로젝트로 옮겨져 내용이 비어 있다. 목록에 남겨 두면
빈 껍데기가 영구히 쌓여 화면만 지저분해진다(2026-08-08 사용자 지시로 제외).
행 자체는 이력용으로 DB에 남긴다 — 어느 자료가 어느 프로젝트로 갔는지 추적할 근거다.
"""
async with connection.cursor(aiomysql.DictCursor) as cursor:
await cursor.execute(
"""
SELECT id, name, memo, status, completed_at, expires_at,
linked_project_id, created_at
FROM temp_upload_batches
WHERE user_id = %s
WHERE user_id = %s AND status <> 'linked'
ORDER BY created_at DESC
""",
(user_id,),