ws_ 세션(작업실 앱 전용 채팅)이 "지금 어떤 프로젝트를 보고 있는지" 정보를
시스템 프롬프트에 전혀 못 받고 있었음 — tool-scope.ts의 isWorkshopAppSession은
"작업실 세션이다"만 판별하지 활성 프로젝트 ID는 안 넘김.
그 결과 모델이 STL/scad 파일을 찾을 때 워크스페이스 전체를 뒤져서 다른
프로젝트 파일까지 후보로 내놓는 혼동이 있었음(예: proj_scan_dental_diy
채팅에서 STL 요청했는데 proj_scan_dental 쪽 scad까지 같이 나열).
workshop.json의 activeProjectId를 읽어 "지금 프로젝트는 이거고 파일은
workshop/<id>/ 아래만 볼 것"을 callerContext에 주입하도록 수정.
실제 ws_ 세션으로 테스트해 크로스 프로젝트 혼동 없어진 것 확인.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- slicer-view.js: ?load=<경로1,경로2,...> URL 파라미터 지원 추가 —
cad-editor-view.js의 ?path= 패턴과 동일한 취지로, 페이지 진입 시
지정한 워크스페이스 STL들을 /api/cad/file로 받아 자동으로
슬라이서 판에 배치한다
- workshop-app.html: 파일 탭 STL 카드에 다중선택 체크박스 추가 +
"선택 STL 슬라이서로" 툴바 버튼 — 체크한 부품 STL들을 K2 슬라이서
팝업(slicer-app.html)에 한 번에 올려 같은 판에서 배치/슬라이싱
기존 slicer-app.html/PrusaSlicer 파이프라인은 그대로 재사용, 새 백엔드
라우트 없이 routes-cad.ts의 /api/cad/file만 재사용.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- routes-workshop.ts: 파일 목록/업로드/폴더생성/삭제 API 추가
(case-storage.ts 재사용, appType='workshop', caseId=projectId)
- workshop-app.html: 파일 탭 추가 — 드래그앤드롭 업로드, 폴더 그룹핑,
사진 썸네일, STL 클릭 시 Three.js CAD 편집기 팝업으로 연동
- cad-editor-view.js: ?path= 쿼리로 특정 STL 자동 로드 지원
- server.ts: registerWorkshopRoutes에 isPathInsideDir 전달
- stl-cad-core.ts: 평면뷰 렌더 카메라가 원점이 아닌 바운딩박스
중심을 look-at하도록 수정 — 원점에서 떨어진 모델 위쪽 잘림 해결
Co-Authored-By: Claude Code <noreply@anthropic.com>
APP_SESSION_PREFIXES 목록에 ws_(workshop-app.html)와 dr_(doctor-app.html)가
빠져 있어서 두 앱의 전용 채팅 세션이 메인 채팅 세션 목록에 그대로 섞여
보이고 있었음. 실제 8개 앱 HTML의 세션 프리픽스와 대조해서 누락분 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
한 번 검색해서 끝내지 말고(문구 바꿔 재검색), 스니펫이 부실하면 web_fetch로
실제 페이지를 읽으라는 일반 규칙(searchPersistenceRule)을 system-prompt에
추가. 기존 fileSearchRule은 겹치는 부분 덜어내고 파일검색 고유 내용(저장소
이름, 다운로드 버튼 차단은 정상)만 남김.
실측: K2 배기 덕트 STL 검색 재현 시 이번엔 검색 1회로 Printables/MakerWorld
실제 모델 3종+링크를 찾아 답변(이전엔 1회 검색 후 포기). 일반 쿼리("오늘
환율")는 과다검색 없이 기존과 동일하게 2회로 정상 답변 — 회귀 없음 확인.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
메인 채팅의 전체 설정 모달을 그대로 들여오는 대신, 같은 백엔드
엔드포인트(/api/settings/model, /api/settings/provider, /api/models/test)만
재사용해 채팅 헤더에 작은 pill+드롭다운을 추가. 클릭하면 ollama provider가
보유한 로컬+클라우드 모델 태그 목록을 보여주고 선택 시 전역 models.primary를
바꾼다. curl로 GET/POST 전 구간 실측 확인(목록 12개 모델 조회, 전환→원복).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
작업실 채팅(gemma4)이 K2 배기 덕트 STL을 검색 1회만 하고 포기하는 걸
로그로 확인(SEARCH-ABANDONMENT 가드가 재시도시켜도 결론은 동일). STL/3MF
등 다운로드형 파일을 찾는 요청에서는 검색어 2-3종 + 알려진 저장소
(Printables/Thingiverse/MakerWorld/Creality Cloud) 명시 검색을 시도하고,
직접 파일 링크를 못 찾아도 모델 페이지 URL을 주는 걸 성공으로 취급하도록
system-prompt에 메시지 게이트 규칙 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3MF를 미리보기 없이 원본 그대로 보내는 대신, PrusaSlicer --export-stl로
서버에서 STL로 변환한 뒤 기존 STL 파이프라인(미리보기+다중배치편집)에
그대로 태우도록 개선. 사용자 쪽에서도 '그냥 STL로 변환하면 안 되나' 제안.
- routes-k2.ts: POST /api/k2/convert-3mf-to-stl 신규(임시파일→변환→응답,
cleanupTemp로 정리)
- slicer-view.js: 파일선택 시 .3mf면 변환API 먼저 태운 뒤 기존
loadModel()/addInstance()로 합류 — STL과 완전히 동일하게 취급됨.
이전 커밋의 '미리보기 없는 패스스루'(pending3mf 상태/분기) 전부 제거.
print3d_model 채팅도구의 .3mf 다운로드 확장자 인식은 유지(변경 없음).
E2E 검증: 변환 API 단독 호출로 3MF→STL 정상 확인(삼각형 수 일치).
Co-Authored-By: Claude Code <noreply@anthropic.com>
- 파일 입력 multiple: 여러 STL을 한 번에 불러와 각각 별도 모델(색으로 구분)로 등록,
인스턴스 1개씩 배치. 모델/인스턴스 구조 분리(userData.modelIdx)로 서로 다른 기하 공존.
- ↕뒤집기 버튼: X축 180° 회전 후 reLiftInstance로 바닥이 베드에 붙도록 position.y 보정.
bake 시 mesh.matrix(전체 회전·배율·liftY 포함)를 정점에 적용 → 뒤집기 상태까지 보존.
- 전체삭제 버튼 추가. 복제는 회전·뒤집기·배율 전부 복사.
- 기존 좌표계 규약(three Z = BED - bedY) 유지, 다중 모델에서도 인스턴스·툴패스 정렬 유지.
Co-Authored-By: Claude Code <noreply@anthropic.com>
단일 stlMesh 대신 인스턴스 모델 도입 — baseGeo 공유 위에 다중 THREE.Mesh를
position/rotation/scale로 배치. 뷰에서 클릭 선택·드래그 이동, 사이드 패널에서
숫자 입력(X/Y/회전/배율) + 복제/↺90/↻90/중앙/자동배치/삭제 버튼.
슬라이스 시 모든 인스턴스를 단일 바이너리 STL로 구워(bake) 전송 — 각 정점을
베드 좌표(bedX=threeX, bedY=BED-threeZ, 높이=threeY)로 변환. 서버 /api/k2/slice
에 arrange=0 추가 → PrusaSlicer --dont-arrange 로 원본 XY 보존(기존 --duplicate
대체, --merge 세그폴트 회피).
실측 검증: 큐브 STL 0..20 을 arrange=0 슬라이스 → 베드 0..20 유지(재중앙화 ❌).
2-큐브 합본 STL → 두 클러스터가 각각 지정 베드 좌표에 배치됨 확인.
Co-Authored-By: Claude Code <noreply@anthropic.com>
PrusaSlicer가 모델을 베드 중심(130,130)에 두고 gcode 좌표를 베드 좌표계
(0~260) 그대로 내보내는데, 렌더링에서 거기에 +BED/2(+130)를 또 더해서 모델이
(260,260) 모서리 쪽으로 밀려 보였음("중심에서 벗어나 있네").
수정: gcode X → three X 오프셋 없이 그대로, gcode Z(높이) → three Y,
gcode Y → three Z = BED - gcodeY. Z를 뒤집는 건 STL 미리보기(rotateX -90)가
STL +Y를 three -Z로 보내는 것과 정렬하기 위함. 실측: 모델 three X/Z 둘 다
130.00 중심, 0~260 베드 안. STL 미리보기와 동일 위치로 겹침.
Co-Authored-By: Claude Code <noreply@anthropic.com>
단순 6파라미터 모달을 PrusaSlicer --load <ini> 기반 풀 에디터로 교체.
베이스 ini(K2_BASE_INI, 약 60키 큐레이션)를 TS 문자열로 임베드하고
buildProfileIni가 오버라이드 머지 + canonical start_gcode(13줄 K2 프라이밍
시퀀스, placeholder 온도 치환)를 강제 주입 — k2_config.ini 원본의 3줄
start_gcode를 그대로 쓰면 프라이밍이 날아가 출력이 망가지는 회귀 회피.
웹 UI: 9개 섹션(기본/소재·온도/서포트/브림/속도/팬/표면/리트랙션/고급)
약 40파라미터 편집 + 프로파일 저장/불러오기/삭제. 속도·소재 프리셋 연동.
라우트: /api/k2/profile/base·/profiles CRUD + /api/k2/slice?profile= 변경.
print3d_model 채팅도구는 기존 buildBaseArgs 경로 유지(동작 변경 회피).
Co-Authored-By: Claude Code <noreply@anthropic.com>
채팅으로 좌표를 말로 불러주는 게 너무 번거롭다는 피드백으로, stl_cad
채팅도구와 같은 기능을 드롭다운+버튼 UI로도 쓸 수 있게 함.
- stl-cad-core.ts: stl-cad.ts에서 OpenSCAD 실행/STL 파싱/렌더링 로직을
추출한 공용 모듈 + 워크스페이스 내 STL 파일 재귀 검색(findStlFiles)
추가. stl-cad.ts(채팅도구)와 routes-cad.ts가 공유
- routes-cad.ts: GET /api/cad/list(STL 목록), /api/cad/info(치수),
/api/cad/render(PNG 스트리밍), POST /api/cad/add-hole
- k2-app.html: "🔧 STL 확인" 모달 — 파일 드롭다운 선택 → 7방향 뷰 버튼 →
실시간 렌더 미리보기+치수, 접이식 "구멍 뚫기" 폼(x/y/z/지름/길이/축).
브라우저로 실제 렌더링/각도전환 확인함
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
"작업실"의 다음 단계로 남겨뒀던 CAD 연동을 시작. FreeCAD 대신 OpenSCAD를
백엔드로 씀 — 텍스트 스크립트 기반 CSG라 AI가 파라메트릭 스크립트를 짜서
바로 실행하는 이 워크플로에 훨씬 적합함(GUI/Python API가 무거운 FreeCAD
대비). 오늘 ESP32 카메라 하우징 렌즈 소켓 위치를 실제로 이 방식(OpenSCAD
--camera 렌더 + 메시 좌표 분석)으로 조사해본 뒤 정식 도구로 정리했다.
- info: 바운딩박스/치수/삼각형수 조회(바이너리 STL 직접 파싱, 의존성 없음)
- render: iso/top/front 등 각도로 PNG 렌더링, 모델이 직접 보게 함
(buildImageMarkdown 재사용). QT_QPA_PLATFORM=offscreen으로 헤드리스
렌더 확인(xvfb 불필요)
- add_hole: 지정 좌표에 원통 구멍을 뚫어 새 STL로 저장(difference()).
테스트 큐브로는 정상 작동 확인했으나, 오늘 다룬 ESP32 STL 자체가
CGAL이 못 받아들이는 미세 메시 결함이 있어(admesh로도 복구 안 됨)
그 파일에서는 실패 — 원인이 도구가 아니라 소스 STL 품질 문제임을
확인함
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
로봇 프로젝트 하나만 다루던 대시보드/도구를 로봇 외 다른 메이커 프로젝트
(3D프린팅, 밀링 등)도 같은 틀로 관리할 수 있게 재설계. 사용자가 이름을
"작업실"로 정함.
- workshop-storage.ts(구 robot-storage.ts): 단일 문서 대신
{projects:[], activeProjectId} 구조. 기존 실사용 중이던
robot-project.json이 있으면 빈 시드로 덮어쓰지 않고 그 실제 데이터를
그대로 첫 프로젝트("로봇 프로젝트")로 마이그레이션
- routes-workshop.ts(구 routes-robot.ts): GET/PUT /api/workshop/data
- workshop-project.ts(구 robot-project.ts) 채팅도구: project_name 파라미터
추가(생략시 활성 프로젝트 대상), list_projects/create_project/
set_active_project 액션 신설
- workshop-app.html(구 robot-app.html): 헤더에 프로젝트 선택 드롭다운 +
새 프로젝트/삭제 버튼 추가. 장비(K2/밀링) 탭은 프로젝트와 무관하게
전역 유지
- 세션 접두사 rb_→ws_(tool-scope.ts의 isWorkshopAppSession), 도구명
robot_project→workshop_project
- 마이그레이션+새 프로젝트 생성/전환/삭제+채팅 연동까지 브라우저로 실제
확인함
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
부품 카드에 추가한 참고링크(구매처/대체품/업그레이드/레포 등, 076a53b)를
이제 채팅으로도 붙이고 뗄 수 있다 — "이거 어디서 팔아?" 하고 물어서
web_search로 찾은 뒤 이 액션으로 바로 그 부품에 링크를 붙이는 흐름을
위한 것. delete_link는 라벨 일부일치로 찾는다(다른 액션들과 동일 패턴).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
각 부품 카드에 라벨+URL 쌍을 여러 개 붙일 수 있는 "정보" 섹션 추가 —
파는 곳, 대체품, 업그레이드 옵션, 관련 GitHub 레포 등 무엇이든 자유롭게
기록. URL을 입력하면 옆에 ↗ 열기 버튼이 자동으로 뜬다(타이핑 중 포커스가
끊기지 않게 그 버튼만 DOM에서 직접 갱신, 전체 리렌더 안 함).
- robot-storage.ts: RobotPart에 links: RobotPartLink[] 필드 추가(기존
저장된 데이터는 링크 없이 시작, 별도 마이그레이션 불필요)
- robot-project.ts: add_part 기본값에 links:[] 추가, get 응답에 부품별
링크 목록도 같이 표시
- robot-app.html: 카드 펼침 영역에 라벨/URL 입력 행 + 추가/삭제 버튼
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
K2 콤보 CFS 시스템은 출력 시작할 때 터치스크린에서 "필라멘트 삽입"을 직접
눌러 확인해야 실제 급지가 되는데, API로 원격 시작하면 아무도 그 확인을
못 눌러줘서 압출이 안 되고 Klipper 안전장치로 히터가 자동으로 꺼지는 걸
오늘 실제로 겪음(2026-09-20). 웹 UI 슬라이싱 모달과 print3d_model 채팅도구
둘 다 출력 시작 성공 메시지에 "터치스크린 확인하라"는 안내를 추가.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
로컬 PC의 STL 파일을 채팅 없이 K2 앱에서 바로 업로드→슬라이싱→K2 업로드까지
할 수 있는 모달 추가. print3d_model 채팅도구와 로직을 공유하도록
k2-slicer-core.ts로 공통 부분(프린터 프로파일, PrusaSlicer 실행, Moonraker
업로드, 시간 파싱)을 분리했다.
- k2-slicer-core.ts: print3d.ts에서 추출한 공용 슬라이싱 로직 + 오늘 실측한
FAST_SPEED_ARGS(퍼리미터/인필/트래블 속도업, 브릿지는 유지)
- routes-k2.ts: POST /api/k2/slice(STL 원본 바이트 바디+쿼리스트링 설정,
--duplicate로 동일 파츠 복제 지원 — 서로 다른 파츠 합치기(--merge)는 이
헤드리스 환경에서 세그폴트 나서 미지원), POST /api/k2/print-start(Moonraker
프록시, mixed-content 회피)
- k2-app.html: 헤더에 "🔪 슬라이싱" 버튼 → 모달(파일업로드/수량/소재/레이어
높이/인필/속도/서포트) → 결과+출력시작 버튼. 실제 파일 업로드로
슬라이싱→K2업로드까지 브라우저에서 확인함(출력 시작은 실제 프린트 중이라
테스트 안 함)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
행마다 항상 다 펼쳐져 있던 표 대신, 이름/상태뱃지/소계만 보이는 헤더를
클릭하면 수량·단가·상태·메모 편집 필드가 펼쳐지는 카드로 교체. 펼침
상태는 화면 전용(서버 저장 안 함)이라 다시 그려도 유지됨.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
부품 리스트를 보면서 바로 옆에서 로봇 프로젝트 얘기를 할 수 있게 리사이즈
가능한 채팅 패널을 대시보드에 붙임. 세션ID를 "rb_"로 시작하게 해서
tool-scope.ts가 이 세션 안에서는 키워드 매칭 없이 robot_project 도구를
항상 열어주도록 예외 처리(doctor_app의 'dr_'/lawyer_app의 'lw_'와 동일한
패턴) — "이 부품 지워줘"처럼 짧게 말해도 바로 동작한다.
채팅 응답이 끝나면 대시보드(부품표/예산/작업)를 자동으로 다시 불러와서
채팅으로 바꾼 내용이 즉시 화면에 반영되게 함.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
로봇 프로젝트("메카넘휠" 이동로봇) 대화에서 부품 상태 변경/추가/삭제, 작업
완료 체크, 새 단계/작업 추가, 메모 추가를 채팅으로 바로 할 수 있게 하는
도구. robot-app.html 대시보드와 같은 저장소(robot-storage.ts로 분리)를
쓰므로 채팅에서 바꾸면 대시보드에도, 대시보드에서 바꾸면 다음 채팅
조회에도 즉시 반영된다.
- robot-storage.ts: routes-robot.ts에서 인라인으로 있던 저장 로직을
분리(case-storage.ts와 같은 목적) — 라우트와 도구가 공유
- robot-project.ts: get/add_part/update_part/delete_part/add_task/
set_task_done/add_phase/add_note 8개 액션, 이름 일부일치 검색이라
내부 id를 몰라도 채팅으로 지칭 가능. 물리적 동작이 없어 confirm 불필요
- registry.ts/build-tools.ts/tool-scope.ts 3곳 등록. 게이트 키워드는
"로봇청소기"(기존 Valetudo 대화)와 안 겹치게 프로젝트 고유 어휘로만 매칭
- 실제 채팅으로 "예산 얼마야?+메카넘휠 주문완료로 바꿔줘" 동시 요청 테스트,
정확한 응답+저장 확인
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
로봇 프로젝트 대시보드에 실제 공작 장비(K2 3D프린터, CNC 밀링 프린터실
PTZ카메라) 상태를 보는 "장비" 탭을 추가. 우선 가벼운 주기적 스냅샷 방식으로
구현(실시간 WebRTC 상시연결은 자원 부담 커서 보류, 추후 업그레이드 검토).
- routes-k2.ts: GET /api/k2/status — Moonraker print_stats/virtual_sdcard를
요약해서 반환(온라인여부/상태/파일명/진행률/경과시간)
- nvr.ts: GET /api/nvr/frame/:channelId — go2rtc(127.0.0.1:1985)의
frame.jpeg를 프록시(밀링 PTZ카메라, channelId=1000 "프린터실"), go2rtc
포트를 브라우저에 직접 노출하지 않음
- robot-app.html: "장비" 탭 — K2는 20초 간격으로 짧게 WebRTC 연결→ontrack에서
캔버스 캡처→즉시 종료(상시 연결 안 함), 밀링은 위 프록시로 img 새로고침.
"장비" 탭을 보고 있을 때만 폴링, 다른 탭으로 나가면 타이머 정지
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
메카넘휠 이동로봇 프로젝트(파이5+RPLidar, 추후 팔 추가)의 부품리스트/
단계별 작업/예산을 관리하는 웹앱. doctor-app의 GET/PUT 단일문서 패턴을
재사용하되 케이스 목록 없이 단일 프로젝트 문서로 단순화했고, 첫 로드시
project_robot_dog.md 메모 기준 실제 진행상황을 기본 시드로 반환한다.
- src/gateway/routes/routes-robot.ts: GET/PUT /api/robot/project,
사용자별 workspace/.smallclaw/robot-project.json에 원자적 저장
- web-ui/html/robot-app.html: 부품/작업/메모 3탭, 실시간 예산 요약,
디바운스 자동저장
- index.html 🏠 홈 드롭다운에 🤖 로봇 링크 추가
장비연동/전용챗봇/외부데이터연동/CAD플러그인은 다음 단계로 보류.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CuraEngine CLI는 구조적으로 예상 출력시간 헤더가 항상 더미값(6666)만 남는
한계가 있어(33b5a25 참고) PrusaSlicer(apt, gcode-flavor=klipper 네이티브
지원)로 교체. DISPLAY 없이도 --export-gcode가 완전 헤드리스로 동작함을
확인했고, 슬라이싱 후 파일에 실제 "estimated printing time"이 직접
기록되므로 stderr 파싱 없이 파일에서 바로 읽으면 됨. K2 프로파일/시작-매크로
값은 그대로 유지. 더 이상 쓰지 않는 resources/cura/*.def.json 제거.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CuraEngine 5.0.0 소스 확인 결과, CommandLine 백엔드는 isSequential()이 항상
true라 슬라이싱 "시작 전"에 G-code 헤더를 미리 써버리고(processStartingCode
-> getFileHeader, print_time 인자 없음) 끝난 뒤 되돌아가 실제값으로 덮어쓰는
코드가 어디에도 없다. 그래서 ;TIME: 헤더는 항상 gcodeExport.cpp에 하드코딩된
더미값(6666)만 남는다 — 확률적 버그가 아니라 CLI 모드의 구조적 한계였음.
실제 값은 슬라이싱 완료 후 stderr(verbose 로그)에 "Print time (s): N"으로만
찍히므로, G-code 파일 헤더 대신 stderr를 파싱하도록 변경.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CuraEngine(apt cura-engine, 클로서버)로 STL을 슬라이싱해 K2 콤보(Moonraker)에
업로드하는 도구. action="prepare"(다운로드+슬라이싱+업로드, 출력은 안 함)와
action="start"(confirm=true 필수, 실제 출력 시작)로 분리 — 재료/시간 소모되는
물리적 동작이라 사용자 확인 없이는 출력 시작 안 되게 함.
프린터 프로파일(빌드볼륨/노즐/리트랙션/온도/START_PRINT·END_PRINT 매크로 호출)은
추측이 아니라 K2에 이미 있던 Creality Print 슬라이싱 샘플 gcode 헤더에서 실측값
그대로 추출. fdmprinter.def.json/fdmextruder.def.json은 설치된 CuraEngine
5.0.0과 버전 맞춰서 받아야 함(main 브랜치 최신판 쓰면 설정 스키마 불일치로
슬라이싱이 즉시 실패함 — resources/cura/에 버전 고정해서 커밋).
tool-scope.ts에 3D프린트 관련 키워드 게이트 추가(다른 니치 도구들과 동일 패턴).
end-to-end 테스트 완료(3DBenchy STL로 다운로드→슬라이싱→K2 업로드 확인, 테스트
파일은 삭제함).
onicecandidate가 ev.candidate===null(전체 gathering 종료)까지 기다린 뒤에만 오퍼를
보내고 있었는데, 이 네트워크에서 그 신호가 20~30초씩 걸림(가상 인터페이스들 순회+
타임아웃으로 추정). 실제 필요한 STUN srflx 후보는 1~2초면 잡히므로, 완전종료를
기다리지 않고 1.5초 타임아웃 뒤 그때까지 모인 후보로 바로 오퍼를 보내도록 변경.
브라우저 실측: 연결까지 20~30초 → 3초 이내로 단축.
- nvr-app.html: go2rtc WS URL을 페이지 오리진 기준 상대경로로 (하드코딩된 ws://LAN IP가
HTTPS 페이지에서 mixed-content로 막히던 문제)
- k2-app.html: 카메라 재연결 시 오래된 연결의 실패 콜백이 새 연결 상태를 덮어쓰던 레이스 수정
- routes-k2.ts: K2 시그널링 fetch에 타임아웃 추가(프린터 응답없음 시 무한대기 방지)
- nvr.ts/routes-comfyui.ts/routes-android.ts에 중복돼있던 raw WebSocket 업그레이드
프록시(connect→pipe→cleanup)를 ws-proxy-util.ts의 공통 헬퍼로 통합
Xiongmai NVR이 3rd파티 카메라(Mercusys)를 "Resolution too big" +
online=false로 계속 보고하는데, 실제로는 RTSP 패스스루가 정상 동작함
(/ch5_0.264 프레임 정상 수신 확인). 그런데 quadChannels 필터가 c.online만
보고 있어서 마당이 6분할에서 빈 슬롯으로 렌더됐음.
chnStreamable() = online || "resolution too big" 상태 → 이런 채널도
6분할·라이브 버튼에 포함. 진짜 죽은 채널은 img.onerror가 "영상 없음" 처리.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017yEadXvr9ZWp2S5iRB1B25
카메라 메인 스트림을 1920×1080으로 낮춰(ONVIF) NVR이 채널 5로
직접 물 수 있게 됨 → NVR 녹화 포함. standalone id 1000 불필요해져 삭제.
- nvr.channelLabels["5"] = "마당"
- nvr.channelStream에서 "1000" 제거 (extra camera 없어짐)
- nvr.extraCameras = []
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017yEadXvr9ZWp2S5iRB1B25
routes/nvr.ts의 extra 카메라 매핑이 preferredStream을 null로 하드코딩해서
nvr.channelStream(채널별 서브스트림 고정)이 독립 카메라엔 적용되지 않았음.
Mercusys 마당은 메인 스트림(/stream1) 동시 RTSP 세션을 1개만 허용해서,
4분할 "메인(고화질)" 체크 시 다른 세션과 겹치면 Operation not permitted로
거부 → 타일이 "영상 없음". (확대 모달은 4분할을 먼저 정지해서 됐음)
- getChannelStreamPrefs()[id]를 독립 카메라 preferredStream에도 반영
- config: channelStream["1000"]=1 (마당 서브 720p 고정) — 채널3과 동일 방식
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017yEadXvr9ZWp2S5iRB1B25