From e88571e5e9e9e87dc919b02fbac982bd59f207ae Mon Sep 17 00:00:00 2001 From: "hehihoho3@gmail.com" Date: Wed, 5 Aug 2026 13:27:25 +0900 Subject: [PATCH] =?UTF-8?q?fix(important):=20=EC=9E=90=EB=8F=99/=EA=B5=AC?= =?UTF-8?q?=EA=B0=84/=EB=B6=99=EC=97=AC=EB=84=A3=EA=B8=B0=20=ED=83=AD=20?= =?UTF-8?q?=EA=B0=84=20=EA=B2=80=ED=86=A0=20=EC=83=81=ED=83=9C(byIdx=C2=B7?= =?UTF-8?q?sel=C2=B7selCut)=20=EC=98=A4=EC=97=BC=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 세 result 핸들러(onPrepareResult/onYtResult/onPasteResult)가 byIdx는 전역 리셋하면서 sel/selCut은 자기 탭 키만 건드려, 한 세션에서 두 탭을 쓰면 칩이 다른 탭 댓글을 가리키거나 "ID n 사용중" 오표시가 뜨고, 이미 그려진 다른 탭 카드를 클릭하면 sel[hlId]가 undefined라 toggle()에서 TypeError가 났다(const selCut 재할당 시도 자체도 원래 런타임 에러였음 — let로 변경). "검토는 한 번에 하나" 원칙으로 세 핸들러 모두 byIdx· sel·selCut을 전역 리셋하고, 다른 탭의 검토 DOM·빌드 버튼도 clearOtherPanels() 로 함께 지워 클릭 경로 자체를 없앴다. grep 대조: byIdx/sel/selCut을 읽고 쓰는 곳(toggle/sortSel/cutFull/ refreshSel/wrapOf/usedByOthers/applyUsedMarks/buildAll류)은 모두 hlId로 키를 참조하므로 이번 전역 리셋과 일관되게 동작한다. Co-Authored-By: Claude Opus 5 (1M context) --- server/static/auto.js | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/server/static/auto.js b/server/static/auto.js index 2a46ad7..a90707d 100644 --- a/server/static/auto.js +++ b/server/static/auto.js @@ -244,7 +244,7 @@ function renderCutPanel(box,panelId,data,opts){ /* ── 컷별 선택 ── 카드는 '어느 컷 소속'인지가 배치를 정한다. selCut[hlId][idx] = 컷 인덱스. sel[hlId] 는 항상 컷 순서로 정렬해 둔다 (업로드 순서 = 배치 순서라서). */ -const selCut={}; +let selCut={}; function sortSel(hlId){ const m=selCut[hlId]||{}; sel[hlId].sort((a,b)=>(m[a]??999)-(m[b]??999)); @@ -732,10 +732,30 @@ function updatePrepStep(ev){ if(ev.detail){const d=el.querySelector(".sdetail");d.hidden=false;d.textContent=ev.detail;}} } +/* ── 검토는 한 번에 하나 ── + byIdx/sel/selCut 는 탭(auto/yt/paste) 구분 없는 전역 상태다. 세 result 핸들러가 + 전부 byIdx={} 로 전역 리셋하면서 sel/selCut 은 자기 키만 건드리면, 다른 탭이 + 그려둔 카드가 방금 리셋된 byIdx/sel 을 참조해 "엉뚱한 댓글을 가리킴"·"ID n 사용중" + 오표시·클릭 시 TypeError(toggle()의 list.indexOf, sel[hlId]가 undefined)가 난다. + → 새 결과가 오면 sel/selCut 도 전역 리셋하고, 다른 탭의 검토 DOM·빌드 버튼까지 + 지워 클릭 경로 자체를 없앤다. */ +const PANEL_DOM={auto:{review:"#autoReview",build:"#autoBuild"}, + yt:{review:"#ytReview",build:"#ytBuild"}, + paste:{review:"#pasteReview",build:"#pasteBuild"}}; +function clearOtherPanels(exceptId){ + for(const id in PANEL_DOM){ + if(id===exceptId) continue; + const d=PANEL_DOM[id]; + const r=$(d.review); if(r) r.innerHTML=""; + const b=$(d.build); if(b) b.style.display="none"; + } +} + /* ── 2차 검토 화면 — 지금까지와 같은 ID별 탭 + renderCutPanel(컷별 댓글 섹션). ── 준비(prepare)를 마친 ID만 온다 — 실패한 ID는 경고 로그로만 알린다(탭 자체가 없음). */ function onPrepareResult(ev){ P=ev;byIdx={};sel={};selCut={};curId=null; + clearOtherPanels("auto"); (ev.comments||[]).forEach(c=>{byIdx[c.idx]=c;}); (ev.warnings||[]).forEach(w=>prepLog("⚠️ "+w)); const R=$("#autoReview");R.innerHTML=""; @@ -978,7 +998,9 @@ function ytFail(m){ytLog("⚠️ "+m);ytDone();} (channel 은 유튜브에서 자동인식됐을 수 있음) 여기서 입력칸에 반영해 보여준다 — 사용자가 여기서 또 고치면 그 값이 생성(ytBuildAll)에 그대로 실린다. */ function onYtResult(ev){ - byIdx={};(ev.comments||[]).forEach(c=>{byIdx[c.idx]=c;}); + byIdx={};sel={};selCut={};curId=null; // 검토는 한 번에 하나 — 다른 탭 선택 상태까지 전부 리셋 + clearOtherPanels("yt"); + (ev.comments||[]).forEach(c=>{byIdx[c.idx]=c;}); (ev.warnings||[]).forEach(w=>ytLog("⚠️ "+w)); if(ev.title_top!=null) $("#yttop").value=ev.title_top; if(ev.title_main!=null) $("#ytmain").value=ev.title_main; @@ -1136,7 +1158,9 @@ function pasteFail(m){pasteLog("⚠️ "+m);pasteDone();} expandAllCuts·refreshSel 이 전부 "#hlbox-"+panelId 로 찾으므로 id를 정확히 맞춰야 한다 (yt 탭의 #hlbox-yt 와 동일한 방식). */ function onPasteResult(ev){ - byIdx={};(ev.comments||[]).forEach(c=>{byIdx[c.idx]=c;}); + byIdx={};sel={};selCut={};curId=null; // 검토는 한 번에 하나 — 다른 탭 선택 상태까지 전부 리셋 + clearOtherPanels("paste"); + (ev.comments||[]).forEach(c=>{byIdx[c.idx]=c;}); (ev.warnings||[]).forEach(w=>pasteLog("⚠️ "+w)); PASTE_HL={id:"paste",need:ev.need,cuts:ev.cuts,paste:{cuts:ev.cutRanges}}; sel["paste"]=[];selCut["paste"]={};