Files
ExperionCrawler/웹UI-개선플랜-byQwen27B.md
windpacer 32a442abd6 docs: 세션 작업 문서 추가 — 웹UI 개선플랜(감리정정) + 대화모음
- 웹UI-개선플랜-byQwen27B.md: app.js/index.html 분리 리팩토링 계획.
  실코드 대조 감리 결과 §0.5 추가 — 치명 결함 3건(모듈레벨 상태/최상위
  실행문 누락, 로더 기동 부재, async 파셜 배선 타이밍) 정정 및 정정 로더 포함
- 메타데이터업데이트/문서뷰어/커밋-브랜치정리 대화모음

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 16:47:27 +09:00

24 KiB
Raw Blame History

웹UI 개선 — 코딩 실행 계획 (by Qwen27B)

작성: 2026-05-24 · 상태: 감리 정정 반영 — 실행 전 §0.5 필독 · 대상: src/Web/wwwroot/ 기반: 웹UI-개선플랜-byOPUS.md + diagnosis-checklist.md 규칙 준수 감리: 2026-05-24 실코드 대조 — 치명 결함 3건 발견·정정(§0.5)


0. 진단 요약 (Checklist STEP 1~4 적용)

STEP 1 — 맥락 파악

항목 내용
파일 역할 ASP.NET Static Files로 서빙되는 바닐라 JS SPA 프론트엔드
아키텍처 레이어 진입점 (UI). 백엔드 API(/api/*)에 fetch로 호출
관련 문서 AGENTS.md(빌드/런/배포), CODING_CONVENTIONS.md(JSON camelCase 규칙)
제약 빌드 스텝 0, 정적 파일만, dotnet publish로 배포

STEP 2 — 구조 탐색

wwwroot/
├── index.html          (1,761줄, pane 16개 + 모달 2개)  ← 초안 15개는 오기(docs=Tab16 누락)
├── js/
│   ├── app.js          (5,148줄, 전 탭 로직)
│   ├── docs.js         (712줄, ✅ 분리된 모범 사례)  ← 계획 초안 571줄은 오기(D6)
│   ├── pid-viewer.js   (P&ID 뷰어)
│   └── xlsx.full.min.js
├── css/
│   ├── style.css       (2,231줄)
│   ├── docs.css
│   └── pid_graph.css
└── lib/                (uPlot 등 외부 라이브러리)

STEP 3 — 코드 읽기 (핵심 발견)

  1. 탭 라우터: app.js:5-20document.querySelectorAll('.nav-item') click listener. 진입 훅 4개: opcsvr, t2s, fast, docs
  2. docs.js 패턴: docsInit() 진입 함수 + docsState.inited 플래그로 1회 초기화. 전역 esc()/kbToken 재사용
  3. 모달 2개: fastRecord 신규 세션 모달(index.html:1679-1724), 날짜 선택 팝업(index.html:1727-1754) — pane 외부에 위치
  4. datepicker: app.js:1660-1779 — 여러 탭에서 공유하는 공용 컴포넌트

STEP 4 — 호출 계층 지도

사용자 클릭 (nav-item)
  → app.js:5 click listener
      → tab 클래스 토글
      → if (tab === 'opcsvr') srvLoad()     ← API 호출
      → if (tab === 't2s') t2sInitMode()    ← UI 모드 설정
      → if (tab === 'fast') fastSessionsLoad() ← API 호출
      → if (tab === 'docs') docsInit()       ← API 호출 (docs.js)

0.5 감리 정정 (실코드 대조 — 2026-05-24) ⚠️ 실행 전 필독

본 계획을 src/Web/wwwroot/ 실제 코드와 1:1 대조한 결과, 실행을 막는 치명 결함 3건 + 정합성 결함 3건 발견. 아래 정정을 반영한 후 실행할 것. 헤더/Phase 본문의 라인 번호는 대부분 정확하나, 분리 전제와 로더 코드가 틀렸다.

결함 요약

# 심각도 위치 문제 정정
D1 🔴 치명 §4.2, §4.3 app.js는 함수 선언만 있지 않음 — 모듈레벨 상태변수 ~25개로드 시 실행되는 최상위 문(certStatus() 2593, btn-fast-*/btn-pid-* addEventListener 3001~3640)이 산재. "함수 단위 이동 + _t2sLastResult 1개만 이전"으로는 누락/오작동 상태변수·최상위 문 전수 이전(아래 인벤토리). DOM 배선은 init 훅 내부로
D2 🔴 치명 §2.2, §3.3, §5.1 로더에 기동 코드 부재loadPane/activateTab이 click에만 묶여, 페이지 로드 시 기본 활성 pane(pane-cert)이 빈 채로 남음. §3.3의 "eager 주입"도 §2.2 코드엔 없음 DOMContentLoaded에서 기본 pane 주입+init(아래 정정 로더)
D3 🔴 치명 §2.2, §5.2 pane이 async fetch 파셜이 되면, fast/pid 탭의 최상위 addEventListener 배선이 pane DOM 주입보다 먼저 실행돼 무력화(?.로 silent no-op) → 버튼 사망. eager(Phase1)에서도 fetch 비동기라 동일 발생 해당 배선을 fast/pid의 1회 setup 훅으로 이동
D4 🟡 정합성 §2.3 ↔ §4.2#3 로드 순서 모순 — §4.2#3 "datepicker가 hist/evt보다 먼저" vs §2.3 datepicker 맨 뒤. 실제로는 모두 function 선언(호이스팅)이라 순서 무관 §4.2#3 폐기, §2.3 순서 유지
D5 🟡 정합성 §4.1 "원본 라인" 범위가 상호 겹침(분할표가 아님): hist 1120-1658 ⊃ evt 1349-1503; t2s 1868-2643 ⊃ core helper 2599-2643; conn의 llm* 2개(160,177)가 llmchat llm*와 충돌 범위는 참고용. prefix grep 기계 분리 금지, 함수명 단위 판단
D6 경미 §0 STEP2, §6.1 수치 오기 — docs.js 571줄 → 712줄; pane 15개 → 16개(docs=Tab16); "15개 fetch" → 16개 정정(완료)

실제 함수 배치 — 탭별 연속 블록이 아님 (D5 근거)

app.js 함수는 탭별로 깔끔히 연속되지 않으며 교차(interleave)된다:

  • hist/ht: 1120-1332 + (evt 1349-1503 끼어듦) + 1505-1658
  • core helper fmtTs/fmtVal/parseEnumPv: 2599-2643 (t2s 영역 한가운데)
  • llmLoadConfig/llmSaveModelConfig: 160/177 (conn 영역 — llmchat 아님)
  • certStatus(); 호출: 2593 (t2s 영역 — cert 아님)

grep '^function llm' 같은 기계 분리 금지. 함수명 단위로 소속 탭을 직접 판단할 것.

모듈레벨 상태변수 전수 인벤토리 (각 소속 파일 상단으로 이전 — D1)

변수 라인 소속 파일
_t2sLastResult 2 t2s.js
_nmOffset / _nmTotal / _nmLimit 492-494 nm-dash.js
PB_GROUPS / pbPreviewData / SUBAREA_OPTIONS 613-614, 1022 pb.js
HIST_TAG_IDS 1117 hist.js
_dtp / _DTP_DAYS 1654, 1658 datepicker.js
_srvPollTimer 1777 opcsvr.js
t2sMode 1863 t2s.js
fastCurrentSessionId / fastChart / fastLivePollTimer / fastChartTagNames / fastDbConnected 2640-2644 fast.js
pidCurrentPage / pidPageSize / pidLastResult / pidExtracting / pidElapsedInterval / pidPrefixPanelVisible / CATEGORY_META / CATEGORY_ORDER 3100-3460 pid.js
llmSessions / llmActiveSessionId / llmAbortController / llmIsStreaming / llmType / llmUseTools / llmAgentMode / llmMcpTools / LLM_STARTER_CHIPS / llmKbDocMap / _llmSparkSeq / _llmSparkCache / LLM_MAX_HISTORY / LLM_SUMMARY_KEEP 3670-4262 llmchat.js
kbToken / kbExpiresAt / kbCollections / kbPollTimer 4673-4676 kbadmin.js

로드 시 실행되는 최상위 문 (init 훅으로 이전 — D1/D3)

라인 조치
.nav-item 탭 라우터 5 core.js (계획대로)
certStatus(); 2593 cert setup 훅 신설(1회)
btn-fast-* addEventListener ×7 3001-3079 fast setup 훅(1회, 중복배선 가드)
[href="#pane-fast"] 3093 fast setup 훅
btn-pid-export-* ×2 3613-3626 pid setup 훅(1회)
[data-tab="pid"/"llmchat"/"kbadmin"] 3640, 3700, 4694 nav 요소 대상이라 shell에 잔존 → core.js에 통합 가능(pane DOM 비의존)

정정 로더 (§2.2 · §5.1 대체 — D2/D3 반영)

핵심: ① 진입 시 DOM 주입 완료 후 init, ② 1회 setup(배선)매 진입 enter(데이터 로드) 분리(원동작 보존), ③ 기동 코드로 기본 pane 초기화.

// core.js — 정정판 로더
const paneCache  = new Map();   // src → html
const paneReady  = new Set();   // pane DOM 주입 완료
const paneSetup  = new Set();   // 1회 배선 완료(중복 addEventListener 방지)

// 1회만: pane DOM에 의존하는 최상위 배선 (D1/D3에서 이전)
const paneWire = {
  cert: () => certStatus(),     // 기존 최상위 호출(2593)
  fast: () => fastWire(),       // btn-fast-* 배선(3001-3093)을 담은 함수
  pid:  () => pidWire(),        // btn-pid-export-* 배선(3613-3626)
};
// 매 진입: 기존 탭 라우터의 init (데이터 갱신 등)
const paneEnter = {
  opcsvr: () => srvLoad(),
  t2s:    () => t2sInitMode(),
  fast:   () => fastSessionsLoad(),
  docs:   () => docsInit(),     // docsState.inited 내부 가드 있음
};

async function loadPane(tab) {
  const el = document.getElementById(`pane-${tab}`);
  if (!el || !el.dataset.src || paneReady.has(tab)) return;
  if (!paneCache.has(tab)) {
    try { paneCache.set(tab, await (await fetch(el.dataset.src)).text()); }
    catch { el.innerHTML = `<div class="card"><p style="color:var(--red)">패널 로딩 실패: ${esc(el.dataset.src)}</p></div>`; return; }
  }
  el.innerHTML = paneCache.get(tab);
  paneReady.add(tab);
}

async function activateTab(tab) {
  await loadPane(tab);                                   // ① DOM 주입 완료까지 대기
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
  document.querySelectorAll('.pane').forEach(p => p.classList.remove('active'));
  document.querySelector(`.nav-item[data-tab="${tab}"]`)?.classList.add('active');
  document.getElementById(`pane-${tab}`)?.classList.add('active');
  if (!paneSetup.has(tab)) { paneWire[tab]?.(); paneSetup.add(tab); } // ② 1회 배선
  paneEnter[tab]?.();                                                  //    매 진입
}

document.querySelectorAll('.nav-item').forEach(item =>
  item.addEventListener('click', () => activateTab(item.dataset.tab)));

// ③ 기동(D2): 기본 활성 pane 초기화 — 없으면 첫 화면이 빈 채로 뜸
document.addEventListener('DOMContentLoaded', () => {
  const def = document.querySelector('.nav-item.active')?.dataset.tab || 'cert';
  activateTab(def);
  // Phase 1(eager): 나머지도 미리 주입하려면 ↓ 주석 해제
  // document.querySelectorAll('.pane[data-src]').forEach(p => loadPane(p.id.slice(5)));
});

주의: fastWire/pidWire는 기존 최상위 addEventListener 블록을 그대로 감싼 함수다. ?. 옵셔널 체이닝은 유지하되, 반드시 pane DOM 주입 후(=paneWire에서) 호출돼야 배선이 성립한다.


1. 실행 전략

A안(HTML 파셜 fetch) + app.js 탭별 분리 채택.

단계 내용 파일 변경
Phase 0 core.js 생성 + 파셜 로더 js/core.js(신규), index.html
Phase 1 pane HTML을 panes/*.html으로 eager 분리 panes/*.html(15개 신규), index.html 수정
Phase 2 app.js를 탭별 JS로 분리 js/*.js(15개 신규), app.js 삭제
Phase 3 지연로딩 전환 core.js 수정

2. Phase 0 — core.js + 파셜 로더

2.1 js/core.js 생성

app.js의 공용 함수를 추출하여 core.js에 배치:

함수 현재 위치 역할
esc(s) app.js:23 HTML 이스케이프
setGlobal(state, text) app.js:28 전역 상태 표시
log(id, lines) app.js:33 로그박스 출력
api(method, path, body) app.js:41 fetch 래퍼
fmtTs(v) app.js:2599 타임스탬프 포맷
fmtVal(v) app.js:2616 값 포맷
parseEnumPv(v) app.js:2630 Enum PV 파싱

2.2 파셜 로더 구현

⚠️ 아래 초안 로더는 결함(D2 기동 부재 · D3 배선 타이밍) 있음. 실제 구현은 §0.5 "정정 로더"를 사용할 것. 초안은 설계 의도 참고용으로만 남김.

// core.js — 파셜 로더 (초안 · §0.5 정정판으로 대체)
const paneCache = new Map();

const paneInit = {
  opcsvr: () => srvLoad(),
  t2s:    () => t2sInitMode(),
  fast:   () => fastSessionsLoad(),
  docs:   () => docsInit(),
};

async function loadPane(tab) {
  const el = document.getElementById(`pane-${tab}`);
  if (!el || !el.dataset.src) return;

  if (!paneCache.has(tab)) {
    try {
      paneCache.set(tab, await (await fetch(el.dataset.src)).text());
    } catch (e) {
      el.innerHTML = `<div class="card"><p style="color:var(--red)">패널 로딩 실패: ${esc(el.dataset.src)}</p></div>`;
      return;
    }
  }
  el.innerHTML = paneCache.get(tab);
}

async function activateTab(tab) {
  await loadPane(tab);
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
  document.querySelectorAll('.pane').forEach(p => p.classList.remove('active'));
  document.querySelector(`.nav-item[data-tab="${tab}"]`)?.classList.add('active');
  const el = document.getElementById(`pane-${tab}`);
  if (el) el.classList.add('active');
  paneInit[tab]?.();
}

document.querySelectorAll('.nav-item').forEach(item =>
  item.addEventListener('click', () => activateTab(item.dataset.tab)));

2.3 index.html 스크립트 로드 순서

<script src="/lib/uPlot.iife.min.js"></script>
<script src="/js/xlsx.full.min.js"></script>
<script src="/js/core.js"></script>          <!-- 1. 공용 + 파셜 로더 -->
<script src="/js/cert.js"></script>           <!-- 2. 탭별 JS들 -->
<script src="/js/conn.js"></script>
<script src="/js/crawl.js"></script>
<script src="/js/db.js"></script>
<script src="/js/nm-dash.js"></script>
<script src="/js/pb.js"></script>
<script src="/js/hist.js"></script>
<script src="/js/opcsvr.js"></script>
<script src="/js/t2s.js"></script>
<script src="/js/fast.js"></script>
<script src="/js/pid.js"></script>
<script src="/js/evt.js"></script>
<script src="/js/llmchat.js"></script>
<script src="/js/kbadmin.js"></script>
<script src="/js/write.js"></script>
<script src="/js/datepicker.js"></script>     <!-- 공용 컴포넌트 -->
<script src="/js/docs.js"></script>           <!-- 이미 분리됨 -->

3. Phase 1 — pane HTML eager 분리

3.1 index.html 셸화

1,761줄 → ~120줄로 축소. <main> 내 pane을 data-src 셸로 대체:

<main class="content">
  <section class="pane active" id="pane-cert" data-src="/panes/cert.html"></section>
  <section class="pane" id="pane-conn" data-src="/panes/conn.html"></section>
  <section class="pane" id="pane-crawl" data-src="/panes/crawl.html"></section>
  <section class="pane" id="pane-db" data-src="/panes/db.html"></section>
  <section class="pane" id="pane-nm-dash" data-src="/panes/nm-dash.html"></section>
  <section class="pane" id="pane-pb" data-src="/panes/pb.html"></section>
  <section class="pane" id="pane-hist" data-src="/panes/hist.html"></section>
  <section class="pane" id="pane-opcsvr" data-src="/panes/opcsvr.html"></section>
  <section class="pane" id="pane-t2s" data-src="/panes/t2s.html"></section>
  <section class="pane" id="pane-fast" data-src="/panes/fast.html"></section>
  <section class="pane" id="pane-pid" data-src="/panes/pid.html"></section>
  <section class="pane" id="pane-evt" data-src="/panes/evt.html"></section>
  <section class="pane" id="pane-llmchat" data-src="/panes/llmchat.html"></section>
  <section class="pane" id="pane-kbadmin" data-src="/panes/kbadmin.html"></section>
  <section class="pane" id="pane-write" data-src="/panes/write.html"></section>
  <section class="pane" id="pane-docs" data-src="/panes/docs.html"></section>
</main>

모달 2개(fastRecord, datepicker)는 index.html에 유지(pane 외부 공유 컴포넌트).

3.2 panes/*.html 파일 생성

각 pane의 <section> 내용을 그대로 추출. <section> 태그는 제거(로더가 innerHTML로 주입).

파일 원본 라인 내용
panes/cert.html 111-151 인증서 관리
panes/conn.html 156-208 서버 접속 테스트
panes/crawl.html 213-296 데이터 크롤링 + 노드맵 수집
panes/db.html 301-352 DB 저장
panes/nm-dash.html 357-431 노드맵 대시보드
panes/pb.html 436-748 포인트빌더
panes/hist.html 753-902 이력 조회
panes/opcsvr.html 907-933 OPC UA 서버
panes/t2s.html 938-1028 Text-to-SQL
panes/fast.html 1033-1078 fastRecord
panes/pid.html 1083-1196 P&ID 추출
panes/evt.html 1201-1280 이벤트 히스토리
panes/llmchat.html 1285-1379 로컬 LLM 채팅
panes/kbadmin.html 1384-1543 RAG 관리
panes/write.html 1548-1629 OPC UA Write
panes/docs.html 1634-1672 문서 탐색기

3.3 Phase 1 완료 시 동작

  • core.js의 기동 코드가 페이지 로드 시 16개 pane을 모두 eager 주입(§0.5 정정 로더의 DOMContentLoaded에서 loadPane 일괄 호출 — 초안 §2.2엔 이 루프가 없음, D2)
  • 단, fetch는 비동기이므로 "DOM 즉시 존재"는 성립하지 않음 → pane DOM에 의존하는 배선/init은 주입 완료 후(paneWire/paneEnter)에만 실행해야 함(D3)
  • 동작은 기존과 동일하게 보이되, 파일만 분리됨

4. Phase 2 — app.js 탭별 분리

4.1 분리 매핑표

⚠️ "원본 라인"은 분할표가 아니라 참고용 — 함수가 교차 배치돼 범위가 서로 겹친다(hist⊃evt, t2s⊃core helper). 라인 기준 일괄 추출 금지, 함수명 단위로 이동(§0.5 D5).

파일 함수 접두어 함수 수 원본 라인
js/core.js esc, setGlobal, log, api, fmtTs, fmtVal, parseEnumPv 7 23-60, 2599-2643
js/cert.js cert* 2 52-104
js/conn.js conn*, getServerCfg, llmLoadConfig, llmSaveModelConfig 5 106-261
js/crawl.js crawl*, nodeMapCrawl 2 263-387
js/db.js db*, selectFile 4 389-495
js/nm-dash.js nm* 5 497-614
js/pb.js pb*, rt*, meta*, subArea* 31 616-1118
js/hist.js hist*, ht*, renderHistoryTable 10 1120-1658
js/opcsvr.js srv*, _srv* 9 1779-1866
js/t2s.js t2s*, toggleMcpMode, loadMcpTools, renderToolsChips, callTool, apiChat*, apiRenderResponse 28 1868-2643
js/fast.js fast* 25 2646-3102
js/pid.js pid* 23 3104-3688
js/llmchat.js llm* 46 3690-4676
js/kbadmin.js kb* 32 4678-5070
js/write.js wr* 4 5072-5148
js/evt.js evt*, _evt* 8 1349-1503
js/datepicker.js dt*, _dtp 16 1660-1779

4.2 분리 규칙

  1. 함수 단위 이동: 함수 정의 전체를 복사. import 없음 (전역 스코프). ⚠️ prefix grep 기계 분리 금지 — llmLoadConfig/llmSaveModelConfig(160/177)는 llm*지만 conn 소속, certStatus()/core helper는 다른 영역에 끼어있음(§0.5 참조)
  2. 전역 변수: _t2sLastResult 하나가 아님 — 모듈레벨 상태변수 ~25개 전부 이전 필수. §0.5 "모듈레벨 상태변수 전수 인벤토리" 표 기준
  3. 로드 순서: core.js 먼저, 그다음 탭별 JS. datepicker가 hist/evt보다 먼저불필요(전부 function 선언 호이스팅, 순서 무관). 단 로드 시 실행되는 최상위 문은 §0.5대로 init 훅으로 이전해야 함(D4)
  4. docs.js: 이미 분리됨. 변경 없음

4.3 app.js 삭제

모든 함수가 분리된 후 app.js 삭제. app.js.backup는 유지(롤백용).


5. Phase 3 — 지연로딩 전환

5.1 eager → lazy 변경

core.jsloadPane()을 수정하여 탭 진입 시에만 fetch. ⚠️ §0.5 정정 로더는 이미 lazy(진입 시 loadPane) + 기동 코드(기본 pane) 포함 — 별도 전환 불필요. 아래 초안은 기동·배선 누락(D2/D3) 상태이므로 §0.5판을 사용:

const paneLoaded = new Set();

async function loadPane(tab) {
  if (paneLoaded.has(tab)) return;

  const el = document.getElementById(`pane-${tab}`);
  if (!el || !el.dataset.src) return;

  try {
    el.innerHTML = await (await fetch(el.dataset.src)).text();
    paneLoaded.add(tab);
  } catch (e) {
    el.innerHTML = `<div class="card"><p style="color:var(--red)">패널 로딩 실패</p></div>`;
  }
}

5.2 init 타이밍 검증

지연로딩 전환 시 반드시 검증할 항목:

init 함수 검증 사항
opcsvr srvLoad() srvLoad()가 pane 내부 DOM을 참조하는지 확인
t2s t2sInitMode() 同上
fast fastSessionsLoad() 同上
docs docsInit() docsState.inited 플래그로 안전

리스크: index.html에 인라인되어 있던 onclick="..."은 innerHTML 파싱 시 정상 바인딩됨. <script> 태그는 innerHTML로 실행되지 않으므로 pane 파일에는 <script> 포함 금지.

🔴 추가 리스크(D3): pane 내부 버튼을 onclick 인라인이 아니라 최상위 addEventListener로 배선하는 탭이 있음 — fast(btn-fast-* 3001-3093), pid(btn-pid-export-* 3613-3626). 이 배선은 스크립트 로드 시점에 실행되는데, 파셜 pane은 그 시점에 DOM에 없어 ?.조용히 무력화된다(eager·lazy 공통). 반드시 §0.5 정정 로더의 paneWire(1회 setup 훅) 안에서, pane DOM 주입 후 배선할 것. [data-tab="..."]/.nav-item 대상 배선은 shell에 남는 nav 요소라 안전.


6. 실행 순서 및 검증

6.1 Phase별 실행 순서

Phase 0: core.js 생성 + 파셜 로더
  ↓ 검증: 빌드 성공, 탭 전환 동작
Phase 1: panes/*.html eager 분리
  ↓ 검증: 모든 탭 동작 동일, 네트워크 탭에서 16개 fetch 확인(panes/*.html)
Phase 2: app.js 탭별 분리
  ↓ 검증: 모든 탭 동작 동일, app.js 삭제
Phase 3: 지연로딩 전환
  ↓ 검증: 탭 진입 시 fetch, init 타이밍 정상

6.2 검증 명령

# 빌드
dotnet build src/Web/ExperionCrawler.csproj

# 테스트
dotnet test

6.3 롤백 전략

Phase 롤백 방법
0 core.js 삭제, index.html 스크립트 태그 복원
1 panes/*.html 내용을 index.html에 복붙, data-src 제거
2 app.js.backupapp.js 복원, 탭별 JS 삭제
3 core.jspaneLoaded 제거, eager 주입으로 복귀

7. 체크리스트 교차 검증 (STEP 6)

Q1. 이미 수정된 문제인가?

  • docs.js는 이미 분리되어 있음 → 중복 작업 아님
  • app.js의 탭 라우터는 app.js:5-20에 존재 → 아직 분리되지 않음

Q2. 다른 레이어에서 처리되고 있는가?

  • UseStaticFiles()panes/*.html을 그대로 서빙 → 추가 설정 불필요
  • MapFallbackToFile("index.html")은 SPA 라우팅용 → panes fetch와 충돌 없음

Q3. 의도적 설계인가?

  • "빌드 스텝 없음"은 AGENTS.md에 명시된 의도적 설계 → 준수
  • docs.js의 분리 패턴은 이미 입증된 모범 사례

Q4. 실제 장애 시나리오가 있는가?

  • 1,761줄 HTML 파일에서 한 탭 수정 시 전체 파일 열어야 함 → 편집 충돌 발생 가능
  • 5,148줄 JS에서 함수 검색이 어려움 → 디버깅 시간 증가

8. CSS 분리 (Phase 4, 선택)

우선순위 낮음. HTML/JS 분리 완료 후 진행.

파일 분리 대상
css/style.css (2,231줄) 공용 토큰/레이아웃만 유지
css/llmchat.css (신규) .llm-* 클래스 추출
css/kbadmin.css (신규) .kb-* 클래스 추출
css/pb.css (신규) .pb-* 클래스 추출

9. 자가 검증 (STEP 8)

  • 각 지적 사항을 "현재 파일 몇 번 줄"로 직접 가리킬 수 있는가? → app.js:5-20, index.html:111-1672
  • HIGH 항목은 재현 가능한 시나리오를 한 문장으로 말할 수 있는가? → "한 탭 수정 시 1,761줄 파일 전체를 열어야 해서 두 사람이 동시에 편집 시 충돌 발생"
  • 교차 검증 4개 질문을 모두 통과한 항목만 포함되어 있는가? → 위 §7 참조
  • 보고서의 수정 예시가 현재 코드에 아직 적용되지 않은 내용인가? → core.js/panes/*.html은 아직 존재하지 않음
  • "더 좋은 방법 제안"과 "현재 코드가 틀렸다"를 혼동하지 않았는가? → 현재 코드는 정상 동작. 유지보수성 개선 제안임