fix(important): 자동/구간/붙여넣기 탭 간 검토 상태(byIdx·sel·selCut) 오염 방지

세 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) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-08-05 13:27:25 +09:00
parent 6ab3d998e9
commit e88571e5e9

View File

@ -244,7 +244,7 @@ function renderCutPanel(box,panelId,data,opts){
/* ' ' . /* ' ' .
selCut[hlId][idx] = 인덱스. sel[hlId] 항상 순서로 정렬해 둔다 selCut[hlId][idx] = 인덱스. sel[hlId] 항상 순서로 정렬해 둔다
(업로드 순서 = 배치 순서라서). */ (업로드 순서 = 배치 순서라서). */
const selCut={}; let selCut={};
function sortSel(hlId){ function sortSel(hlId){
const m=selCut[hlId]||{}; const m=selCut[hlId]||{};
sel[hlId].sort((a,b)=>(m[a]??999)-(m[b]??999)); 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;}} 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( ). /* 2 ID + renderCutPanel( ).
준비(prepare) 마친 ID만 온다 실패한 ID는 경고 로그로만 알린다( 자체가 없음). */ 준비(prepare) 마친 ID만 온다 실패한 ID는 경고 로그로만 알린다( 자체가 없음). */
function onPrepareResult(ev){ function onPrepareResult(ev){
P=ev;byIdx={};sel={};selCut={};curId=null; P=ev;byIdx={};sel={};selCut={};curId=null;
clearOtherPanels("auto");
(ev.comments||[]).forEach(c=>{byIdx[c.idx]=c;}); (ev.comments||[]).forEach(c=>{byIdx[c.idx]=c;});
(ev.warnings||[]).forEach(w=>prepLog("⚠️ "+w)); (ev.warnings||[]).forEach(w=>prepLog("⚠️ "+w));
const R=$("#autoReview");R.innerHTML=""; const R=$("#autoReview");R.innerHTML="";
@ -978,7 +998,9 @@ function ytFail(m){ytLog("⚠️ "+m);ytDone();}
(channel 유튜브에서 자동인식됐을 있음) 여기서 입력칸에 반영해 보여준다 (channel 유튜브에서 자동인식됐을 있음) 여기서 입력칸에 반영해 보여준다
사용자가 여기서 고치면 값이 생성(ytBuildAll) 그대로 실린다. */ 사용자가 여기서 고치면 값이 생성(ytBuildAll) 그대로 실린다. */
function onYtResult(ev){ 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)); (ev.warnings||[]).forEach(w=>ytLog("⚠️ "+w));
if(ev.title_top!=null) $("#yttop").value=ev.title_top; if(ev.title_top!=null) $("#yttop").value=ev.title_top;
if(ev.title_main!=null) $("#ytmain").value=ev.title_main; 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를 정확히 맞춰야 한다 expandAllCuts·refreshSel 전부 "#hlbox-"+panelId 찾으므로 id를 정확히 맞춰야 한다
(yt 탭의 #hlbox-yt 동일한 방식). */ (yt 탭의 #hlbox-yt 동일한 방식). */
function onPasteResult(ev){ 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)); (ev.warnings||[]).forEach(w=>pasteLog("⚠️ "+w));
PASTE_HL={id:"paste",need:ev.need,cuts:ev.cuts,paste:{cuts:ev.cutRanges}}; PASTE_HL={id:"paste",need:ev.need,cuts:ev.cuts,paste:{cuts:ev.cutRanges}};
sel["paste"]=[];selCut["paste"]={}; sel["paste"]=[];selCut["paste"]={};