277 Commits
Author SHA1 Message Date
kimandClaude Code 03a462d72e fix: 작업실 채팅의 링크가 클릭 불가 평문이던 문제
- 작업실 채팅 renderMd에 링크 지원이 아예 없었다(굵게·표·리스트만 구현) — 모델이
  표에 넣어준 출처 링크([바로가기](https://www.printables.com/model/145094))를
  클릭해도 아무 일 없었음(2026-09-25 사용자 실측).
- [라벨](URL) → <a target=_blank> 변환 추가 + 벗겨진 벌주 URL도 자동 링크.
  이스케이프 이후 처리라 URL의 &가 &amp;로 href에 들어가는 것까지 확인.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 20:36:47 +09:00
kimandClaude Code a61086d93c feat: 도면 버튼 — 작업실 STL 카드·STL 편집기에서 3면도 즉시 생성
- 작업실 파일 카드(STL)에 📐 버튼: /api/cad/render?view=three 를 fetch→blob으로
  생성해 새 탭에 표시(Authorization 헤더 필요라 단순 링크 불가). 생성 중 표시 후
  결과로 교체. 카드 하단 좌측 오버레이 버튼.
- STL 편집기 파일 행에 📐 버튼(로드 없이 선택만으로 도면 가능).
- 3면도 실루엣 판정 수정: 옛 '모서리 색과의 차이' 방식은 원통처럼 뷰가 사각형을
  못 채우는 부품에서 bbox 모서리에 옛 배경(cream)이 사각형으로 남았다 — 흰 시트
  기준 임계로 판정하고 크롭 내부 잔여 배경도 흰색으로 통일(2026-09-25 turntable
  실측). 참고: PIL floodfill은 fill 값과 배경이 thresh 이내면 실행 자체를 안 함.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 19:45:12 +09:00
kimandClaude Code 1125f3afb9 feat: 부품 바닥 z=0 규칙 — 컴파일 자동 정렬 + 편집기 정렬 버튼
- 계기: 로봇팔 부품들이 center=true 모델링이라 바닥이 z<0으로 내려가 편집기에서
  파묻혀 보이고 좌표 감각이 어긋남(2026-09-25 사용자 지적, CAD 전부 실측 확인).
- .scad 작성 규칙에 7번 추가: 인쇄용 부품 바닥은 z=0(프롬프트 규칙문+SHORT).
- scad_to_stl/compile: 컴파일 후 bbox min z<-0.05면 바이너리 z 평행이동으로
  자동 정렬하고 안내 문구 출력(translateStlZInPlace, 원자적 재기록).
- STL 편집기에 '바닥 z=0' 버튼 — 외부 STL도 처리(현재 회전·배율 상태의
  월드 bbox 최저점을 0으로). 테스트 3건 추가.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 18:58:14 +09:00
kimandClaude Sonnet 5 2bdc5563dc feat: .scad 소스를 프로젝트 SCAD/ 폴더로 분리 (STL은 CAD/ 유지) + 렌더 미리보기는 사진/
사용자 지시(".scad 폴더를 따로"): 변형 .scad가 많은 프로젝트(로봇팔 등)에서 STL과 섞이면 고르기 어려움
- 규칙: workshop/<id>/SCAD/(.scad 소스만) · CAD/(STL 등 모델, CAD/참고, CAD/다운로드) · 출력/ · 사진/ · 영상/ · 조립설명서/
  옛 "STL을 .scad와 같은 폴더" 규칙은 폐지. 사용자가 만든 폴더의 .scad는 건드리지 않음
- 이동 스크립트가 .scad→SCAD/ 처리(조립 명세의 scad 경로도 갱신). 09-25 papa 적용: .scad 12개 + 렌더 PNG 5개 이동, 무손실 검증
- stl_cad 렌더 미리보기 PNG를 프로젝트 STL이면 사진/ 에 저장(CAD/에 이미지가 다시 쌓이던 문제)
- 채팅 프롬프트/도구 안내: 새 .scad는 SCAD/, STL 출력은 CAD/. 파일 탭 권장 폴더에 SCAD 추가
- 테스트: 규칙 표 갱신, apply/undo 왕복, previewPngPath

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 10:38:41 +09:00
kimandClaude Sonnet 5 86d541d928 fix: .scad 선택 시 "새 경로 + 옛 부품" 어긋난 상태로 렌더가 실패하던 문제
- 원인: 다른 .scad를 고르면 경로만 먼저 바꾸고 부품은 나중에(또는 취소하면 영영) 안 바뀌어, 그 사이 저장 타이머/자동 렌더가
  새 파일에 없는 모듈(옛 파일의 motor())로 렌더 → "부품 motor: .scad에 module motor이(가) 없습니다"가 화면에 남음
- 확인창을 먼저 띄우고, 자동 초안이 성공한 뒤에만 경로+부품을 한 번에 교체. 취소/초안 실패 시 아무것도 바꾸지 않고 선택 상자만 원복
  (경로만 바꾸려면 "경로 직접 입력")
- 편집하면 이전 렌더 오류 문구를 지움. 서버 오류 문구에 파일 이름과 해결 방법(자동 초안으로 부품 재생성/모듈 이름 수정) 안내
- 회귀 하네스: 취소/초안 실패/도중 어긋난 상태 저장 감시 (옛 코드에서 4개 항목 실패 확인)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 10:31:53 +09:00
kimandClaude Sonnet 5 b611c045b6 fix: 조립 탭 ".scad 파일 선택이 안 된다" — datalist를 선택 상자로 교체
- 원인: <input list=datalist>의 후보(서버 조회)가 화면을 그린 뒤 도착하는데 목록을 다시 안 그려 항상 비어 있었고(옵션 0개),
  입력칸에 값이 있으면 브라우저가 그 값과 일치하는 항목만 보여줘 다른 파일을 고를 수 없었다
- <select>로 교체(최근 수정 순, 현재 파일 선택 표시, 후보 도착 시 제자리 갱신), 직접 입력칸은 아래에 유지(목록 밖 경로는 "(목록 밖)")
- 다른 .scad를 고르면: 부품이 비어 있으면 자동 초안+렌더, 이미 있으면 교체할지 확인(취소하면 경로만 변경)
- 회귀 하네스 tests/manual/scad-select-check.ts (11개 항목)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 10:21:00 +09:00
kimandClaude Sonnet 5 f976182e0f chore: 작업실 분리 파일 머리말 주석의 옛 파일명 정정
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 10:04:29 +09:00
kimandClaude Sonnet 5 d235ee94f3 refactor: 작업실 핵심 스크립트를 기능별 파일로 분리 (core/parts/tasks/files/chat/init)
- workshop-app.js(83KB) → workshop-core.js(30KB: 인증·이스케이프·상태/저장/충돌/폴링·탭·지연 로더·모달/이력),
  workshop-parts.js(17KB), workshop-tasks.js(6KB: 작업+메모·개요), workshop-files.js(13KB: 파일·슬라이서),
  workshop-chat.js(18KB: 채팅·사진 첨부·리사이즈·모델 pill), workshop-init.js(1KB)
- 원래 한 파일의 섹션 순서 그대로 로드(core→parts→tasks→files→chat→init)해 로딩 순서 문제를 만들지 않음.
  escHtml/safeUrl은 여러 모듈이 쓰므로 core로 이동
- 무손실 검증: 분리 전후 코드 줄 비교(사라진 코드 줄 0, 함수 선언 118개 동일), 실브라우저 45개 항목 + 실서버 읽기 전용 확인
- vm 저장 로직 테스트는 workshop-core.js를 읽도록 변경

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 09:59:12 +09:00
kimandClaude Sonnet 5 0b7057f975 refactor: 작업실 스크립트에서 조립·장비 탭을 별도 모듈로 분리하고 지연 로딩
- workshop-app.js 114KB → 83KB(핵심), workshop-assembly.js(22KB)·workshop-equip.js(12KB)는 해당 탭을 처음 열 때만 로드
- 코드 내용 변경 없음(구간을 잘라 옮김). 모듈 밖에서 부르던 이름은 refreshAssemblyPane(shim)과
  startEquipTimers/stopEquipTimers 뿐이라 가드된 로더(loadWorkshopModule/withLazyModule)로 감쌈
- 로드 중엔 패널을 흐리게(.rb-loading)+클릭 차단, 로드 중 다른 탭으로 옮기면 실행 안 함,
  실패 시 안내 표시 + 다시 열면 재시도(같은 모듈은 한 번만 받음)
- 브라우저 하네스에 지연 로딩 5개 항목 추가(시작 시 미로드/탭별 로드/재요청 없음/실패 안내/재시도), 전체 43개 항목 통과

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 09:53:49 +09:00
kimandClaude Sonnet 5 6119be592d refactor: 작업실 HTML에서 CSS·JS를 별도 파일로 분리 — 내용 변경 없음
- web-ui/html/workshop-app.html 147KB → 9KB (구조만)
- web-ui/css/workshop-app.css(24KB), web-ui/js/app/workshop-app.js(114KB, classic script라 인라인 onclick 전역 핸들러 그대로)
- 분리 내용은 바이트 단위로 원문과 동일함을 검증, 실브라우저 하네스 38개 항목 통과
- vm 저장 로직 테스트는 분리된 JS를 읽도록 변경, 조립 하네스 기본 경로를 폴더 정돈 후 위치로 갱신

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 09:45:28 +09:00
kimandClaude Sonnet 5 ba193eec3c feat: 작업실 파일 폴더 규칙 통일 — STL을 .scad와 같은 폴더에, 프로젝트별 제각각 구조 정돈
규칙(workshop-layout.ts 한 곳에 정의, 도구·스크립트가 공유):
  workshop/<id>/CAD/(.scad+STL 같은 폴더) · CAD/참고/(비인쇄 참고 모델) · CAD/다운로드/ · 출력/ · 사진/ · 영상/ · 조립설명서/
  print3d/K2웹슬라이서/ · 모델검색/ · 일회성/ · profiles/
인쇄용 STL은 .scad 옆에 두되 참고용은 CAD/참고/로 분리 유지(09-22 "참고용을 인쇄 대상으로 착각" 방지)

- 이동 스크립트 scripts/migrate-workshop-layout.ts: 기본 dry-run, --apply(이동만·덮어쓰기 없음·충돌은 건너뛰고 보고),
  --undo <manifest>. .scad 경로 참조(조립 명세)와 문서 문구(CAD/print→CAD 등) 갱신, 빈 폴더만 정리. 멱등
- 도구가 같은 규칙으로 저장: print3d_model(모델→CAD/다운로드, G-code→출력, 프로젝트 없으면 print3d/일회성),
  model_download(CAD/다운로드 또는 print3d/모델검색), K2 웹 슬라이서(print3d/K2웹슬라이서). 옛 print3d/<프로젝트이름>/ 제거
- 채팅 프롬프트: STL은 .scad와 같은 폴더(옛 09-22 CAD/print 하위폴더 규칙 폐지), 프로젝트 폴더 규칙 안내
- 파일 탭: 권장 폴더를 비어 있어도 업로드 대상으로 표시하고 규칙 순서로 정렬
- 테스트 34건(규칙 표, 멱등, 충돌 미덮어쓰기, apply→undo 왕복 무손실, saveDirs, print3d 계획)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 09:37:55 +09:00
kimandClaude Sonnet 5 c4807c9400 fix: 작업실 전체 점검 — 저장 경쟁 상태(가짜 충돌)·팔레트 PNG 썸네일·빈 조립 명세·중복 응답 루프
- 저장 직렬화: 자동 흐름(초안→렌더의 flushNow)과 debounce 타이머가 saveProject를 동시에 불러 같은 기준 버전으로
  PUT → 두 번째가 자기 자신의 첫 저장과 어긋나 가짜 409("렌더 실패: 저장 충돌을 먼저 해결하세요"). 큐로 직렬화하고,
  409여도 서버 내용이 내 내용과 같으면 충돌로 보지 않는다. 회귀 테스트(이전 코드에서 재현 확인)
- 비전 썸네일: 팔레트(P) 모드 PNG가 "cannot write mode P as JPEG"로 실패해 큰 이미지를 모델이 못 보던 문제 → RGB 변환
- 빈 조립 명세(경로/부품/단계 없음)는 저장하지 않음(조립 탭을 열기만 해도 프로젝트에 남던 것)
- .scad만 연결돼 있고 부품/단계가 없는 프로젝트도 조립 탭에서 초안 자동 시도(중단된 자동 연결 복구)
- 중복 add 응답에 "이미 충족됨, 반복 호출 금지" 안내 — 모델이 같은 add_task를 반복하던 LOOP WARN 완화

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 09:20:34 +09:00
kimandClaude Sonnet 5 c625826325 feat: 조립 설명서 .scad 자동 연동 — 자동 연결·자동 초안·변경 시 자동 재렌더
- 자동 연결: 조립 탭을 열면 명세가 비어 있을 때 프로젝트에서 가장 최근 수정한 .scad를 자동 연결
- 자동 초안: .scad의 조립 블록(if (PART == "all") { … } 또는 최상위)이 호출하는 모듈을 부품으로 삼고,
  변수 인자(camera(+CAM_OFFSET))는 원본을 include한 probe로 값(80)으로 풀고, 부품별 bbox로 분해 offset
  (수평 중심 바깥 0.5배·수직 윗쪽 높이 기준 0.9배+층 간 여유)와 조립 순서(.scad에 나열된 호출 순서, 같은
  모듈은 첫 등장 위치에 한 단계)를 정한다. %/* 수식자·변환 붙은 호출·이름에 ghost/fov/axis/plane…·isGhost=true
  인자는 고스트로 제외하고 안내. 자동 흐름은 조립 블록이 있는 파일만 채워 데모/애니메이션 파일로 엉뚱한
  명세가 만들어지지 않게 한다(수동 ✨ 버튼은 허용)
- 자동 재렌더: write_scad가 .scad를 바꾸면 그 파일을 쓰는(렌더한 적 있는) 프로젝트 그림을 백그라운드로
  갱신(프로젝트별 합치기), 조립 탭을 열었을 때 stale이면 자동 재렌더. autoRender 체크박스로 끌 수 있음
- 채팅 도구 draft_assembly(apply=true면 저장), 라우트 POST /api/workshop/assembly-draft
- 테스트: 파싱/이름/실행(OpenSCAD)/자동 재렌더/도구 + 실브라우저 자동 연동 시나리오

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 09:06:48 +09:00
kimandClaude Sonnet 5 8677a090d5 feat: 작업실 "조립 설명서" — .scad에서 단계별 조립도·분해도·부품표를 자동 생성
- 명세(project.assembly): 부품(call=조립 모듈 호출, 분해 offset, BOM 연결, 수량) + 조립 단계(부품/체결부품/공구/설명).
  프로젝트 데이터라 이력·충돌감지·복제가 그대로 적용된다
- 렌더러: 원본 .scad를 수정하지 않고 `use <>` wrapper로 부품을 골라 OpenSCAD ortho 렌더. 안쪽 color가 우선이라
  색을 못 덮는 제약을 "전체 렌더 vs 부품 뺀 렌더"의 픽셀 차이로 해결 → 이번 단계 부품만 원색+주황 외곽선,
  나머지는 옅게, 부품 번호 풍선은 그 부품이 실제 보이는 영역에 배치(가림 처리 정확). 모든 그림 같은 카메라
  (bbox 기반 fit, 투영식 실측 검증), 공통 여백 크롭으로 단계 간 축척 유지
- call 검증: 모듈 1개 호출만(따옴표/세미콜론/중괄호/주석/import·surface 등 금지) + .scad 안에 있는 module만
- 대시보드 "조립" 탭: .scad 스캔→부품 추가, 분해 offset, 단계 편집, 그림 렌더, 작업 탭으로 가져오기
- 설명서 페이지 assembly-manual.html: 완성/분해도, 부품표(BOM 구매상태), 체결부품·공구 합계, STEP 카드, A4 인쇄/PDF
- 채팅 도구: scan_scad / get_assembly / set_assembly / render_assembly ("조립 설명서 만들어줘" → AI가 초안)
- 테스트: 순수함수·실제 OpenSCAD/PIL 렌더(가장자리 잘림 없음 검증)·라우트 HTTP 통합·실브라우저 하네스

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 23:03:43 +09:00
kimandClaude Sonnet 5 37630be619 feat: 작업실 대규모 업그레이드 — 이력/충돌감지/도구 확장/표·게이지·진행률/출력 감시 외
안전망
- 변경 이력: 저장 때마다 바뀌기 전 상태를 프로젝트당 30개까지 보존(45초 합치기), 대시보드 "🕘 이력"에서
  되돌리기, 삭제된 프로젝트 복구(첨부파일 제외). 되돌리기 직전 상태도 이력에 남는다
- 낙관적 락: 프로젝트 updatedAt으로 다른 탭/채팅 도구가 먼저 고친 프로젝트 저장을 409로 보류하고
  충돌 배너(내 변경으로 덮어쓰기/서버 버전 불러오기) 제공. 15초 폴링으로 외부 변경 반영(편집 중엔 미룸)
- 명령 주입 수정: resolveToolImageContent가 파일명을 셸에 그대로 넘기던 execSync → execFileSync

채팅 도구(workshop_project)
- set_notes(메모 전체 교체), update_task/delete_task, rename/delete_phase, add_parts(일괄), rename/
  archive/unarchive/duplicate/delete_project(confirm 필수), set_budget, check_price(다나와),
  list_history/restore_history, view_image(첨부 사진을 모델이 직접 봄)
- 프롬프트에 개요·진행률·남은 작업·예산 현황 주입

대시보드
- 부품 표 보기·상태 필터·정렬, CSV/MD 내보내기, 목표 예산 게이지, 작업 진행률·기한(지남/임박),
  프로젝트 복제(템플릿)/보관, 부품 시세 조회, STL 썸네일(OpenSCAD, 캐시), 채팅 사진 첨부(📷)

장비 연동
- print3d_model start 시 작업실 프로젝트/작업을 등록 → K2 출력 완료/실패를 감시해 작업 자동 완료 +
  메모 기록 + 텔레그램 알림(workshop-print-watch.ts, job 없으면 폴링 안 함)

테스트: 서버/도구/감시/프런트 vm 시뮬레이션 + 라우트 통합(HTTP) + 실브라우저 수동 검증 하네스(tests/manual)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 11:53:21 +09:00
kimandClaude Sonnet 5 8e9226e117 fix: 작업실 앱 전수 검토 결함 일괄 수정 — 채팅 저장 덮어쓰기·업로드 크래시·XSS 외
- 저장: saveDebounce 미해제로 채팅 도구 저장 내용이 새로고침 때 화면 옛 상태로 덮이던 버그.
  서버 동기 스냅샷 기반 dirty(바뀐 프로젝트만 PUT), 실패 시 재시도, 무변경 PUT 제거
- 업로드: 파일명 %(100%.stl)로 decodeURIComponent가 'end' 콜백에서 던져 프로세스가 죽던 버그 +
  수신 중 크기 제한, 콜백 예외 응답
- XSS: escAttr가 따옴표만 처리해 부품/프로젝트/파일명이 innerHTML에서 실행되던 것,
  항목 id 화이트리스트, 링크 href safeUrl
- 헛보고 가드: JS \b가 한글에 안 먹어 "저장했어/했다"가 통과하던 정규식, 명사 없는 후속 확인 질문
- 도구: 정확일치 우선 검색, 중복 생성/추가 방지, 세션(ws_<id>) 프로젝트 기본 대상
- 프로젝트 삭제 시 첨부파일·채팅 세션 정리, 손상 JSON 백업(.corrupt), 옛 전체문서 PUT 제거,
  PUT 본문 검증, 백그라운드 탭 장비 폴링 중지, 전송 중 프로젝트 전환 잠금
- 회귀 테스트 16건 추가(프런트 저장 로직은 vm 시뮬레이션)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 11:26:19 +09:00
kimandClaude Code 01c9de8374 fix: 작업실 채팅에 KaTeX 도입 — 앱 중 유일하게 수식 렌더러가 빠져 있었음
09-24 실측: "12MP $\times$ 60fps $\approx$ 수 Gbps"가 그대로 노출. 다른 앱들
(의사·법률·투자 등)은 전부 vendor katex + katex-inline.js를 실어 다음 앱들은
정상인데 작업실만 없었다. doctor-app과 동일 구성(스타일시트+3스크립트+
katex-inline.js)으로 도입하고 addMsg/스트리밍 완료 시 renderKatexIn 호출.

renderMd의 유니코드 폴백(\times→× 등, 가격 "$19..$29" 보호)은 KaTeX가 없는
환경에서만 발동 — KaTeX 있으면 $...$를 그대로 남겨 renderKatexIn이 맡는다.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 08:42:32 +09:00
kimandClaude Code 12f717c626 fix: 작업실 채팅 renderMd에 마크다운 표 지원 추가
모델 출력의 표는 세션에 줄바꿈까지 정상이었는데 renderMd에 표 처리가 없어
파이프 문자열이 평문 <p>로 뭉개져(HTML 줄바꿈 접힘) 한 줄로 합쳐 보였다
(09-24 실측: 카메라 모듈 스펙 표가 파이프soup로 복사됨). 구분행 |---| 있는
연속 파이프 라인만 <table>로 변환, 없으면 무사통과. 셀 안 굵게 유지,
단락 분기의 블록태그 검사에 table 추가.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 08:36:38 +09:00
kimandClaude Code e01b92966a feat: 작업실 장비 탭 밀링 카드에도 라이브 뷰
- ▶ 라이브 버튼 → go2rtc video-stream 커스텀 엘리먼트(WebRTC→MSE→MJPEG 자동폴백),
  nvr-app 라이브 모달과 동일 패턴. 릴레이 스트림 "cam"(config nvr.extraCameras)
- go2rtc Origin 검사를 통과하도록 같은 오리진 WS 프록시(/api/nvr/go2rtc-ws) 경유,
  LAN IP 하드코딩 없이 현재 오리진 기준 상대경로(https mixed-content 회피)
- 라이브 중엔 20초 스냅샷 폴링을 건너뛴다, 장비 탭을 벗어나면 연결 정리
  (stopEquipTimers → stopMillLive), 상태줄에 "● 실시간" 표시

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 07:52:38 +09:00
kimandClaude Code 75b887d295 feat: 작업실 장비 탭 K2 카드에 라이브 뷰
- ▶ 라이브 버튼 → 카드 안에서 상시 WebRTC 스트리밍(k2-app.html의 검증된 연결
  로직 이식: STUN 필수·오퍼 null-candidate/1.5초 중 빠른 쪽 퀴크 포함)
- 라이브 중엔 20초 스냅샷 그랩(WebRTC 풀협상)을 건너뛴다 — 비디오가 이미
  실시간이고 짧은 연결이 간섭하므로. 상태 조회(Moonraker)는 계속, 상태줄에
  "● 실시간" 표시
- 장비 탭을 벗어나면 stopEquipTimers가 라이브 연결을 닫는다(안 보는 동안
  자원 안 쓰기), ICE failed면 자동 종료 후 안내문

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 07:45:29 +09:00
kimandClaude Code eb516e2b74 feat: 작업실 저장 개편 — 전체문서 PUT을 프로젝트 단위 PUT으로 + LOW 2건
저장 개편(리뷰 #4 근본치료):
- PUT /api/workshop/project/:id — 서버가 디스크의 현재 문서에 해당 프로젝트만
  upsert. 대시보드 저장과 채팅 도구 저장이 겹쳐도 서로 다른 프로젝트 편집이
  통째로 사라지지 않음. DELETE /api/workshop/project/:id,
  PUT /api/workshop/active(포인터 단독) 신설. 옛 전체문서 PUT은 스테일 탭
  호환으로 유지
- 클라이언트: dirtyProjects 추적 → saveProject는 바뀐 프로젝트만 순차 PUT,
  활성 포인터는 putActive() 즉시 저장, 프로젝트 삭제는 DELETE 직접 호출,
  beforeunload flush도 프로젝트 단위 keepalive PUT으로 전환
- upsertProject/removeProject를 순수함수로 추출, 단위테스트 4건

LOW 2건:
- loadFiles에 요청 일련번호 — 빠른 프로젝트 전환 시 옛 응답이 나중에 도착해
  이전 프로젝트 파일을 새 판에 그리는 레이스 제거
- 슬라이서 인계 ?load=를 JSON 배열 인코딩으로 전환(파일명에 쉼표 있으면
  옛 쉼표 join이 깨지던 문제). 슬라이서 쪽은 JSON 우선, 옛 형식 폴백 유지

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 07:38:53 +09:00
kimandClaude Code f42a09b038 fix: 작업실 앱 리뷰 HIGH·XSS 수정 5건
- 새 프로젝트 생성 직전 syncLocalToProject — debounce 미완료 편집 유실 방지
- softRefreshWorkshop: 활성 프로젝트가 서버쪽에서 바뀐 경우에만 clearStlSelection/
  refreshChatForProject/loadFiles — 채팅에서 전환 시 옛 STL이 슬라이서로 오전송되던 문제
- beforeunload/pagehide에서 debounce 저장 flush(keepalive fetch) — 600ms 안에 탭 닫으면
  마지막 편집이 사라지던 문제
- XSS: escJs에 "·<·> 이스케이프 추가(속성 경계 탈출 차단), chatGreeting 프로젝트명/
  모델변경 실패 메시지 escHtml, 부품 참고링크 safeUrl로 javascript:/data: 스킴 차단

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 07:31:07 +09:00
kimandClaude Code 2da7b9fd98 feat: 작업실 개요를 파일 폴더 대신 개요 탭으로 + /api/files 텍스트 charset 수정
- workshop-app: 메모 탭과 동일 구조의 "개요" 탭(textarea) 추가 — project.description
  직접 편집, 채팅 set_description으로 쓴 내용은 응답 후 softRefresh로 갱신
- readme.md 자동생성(syncProjectReadmes) 철회 — 파일탭이 산만해져서 사용자 지정으로
  탭 방식으로 전환, 생성된 개요 폴더들도 삭제. description/set_description은 유지
- server.ts IMAGE_TYPES: 텍스트 계열(.md/.txt/.csv 등) charset=utf-8 명시 — charset
  없이 text/plain으로 뱉으면 브라우저가 latin1로 해석해 한글이 전부 깨짐(실측)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 16:48:12 +09:00
kimandClaude Code a815721976 docs: 게이트웨이 입력게이트·도구게이팅·출력가드 전체 지도 도표 (guard-map.html)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 12:16:27 +09:00
kimandClaude Code bff18650a5 feat: 작업실 채팅 세션을 프로젝트별로 분리
기존엔 앱 전체 공용 세션(ws_main/랜덤 id) 하나를 써서 프로젝트를 바꿔도
옛 프로젝트의 대화가 그대로 남았다. sessionId를 'ws_'+projectId로 결정적
파생하도록 바꿔 프로젝트마다 독립된 대화를 갖고, 새로고침/재접속에도 같은
프로젝트의 대화를 이어받는다. ws_ 접두어는 유지(tool-scope 도구 개방 +
메인 사이드바 제외 근거). 프로젝트 전환/생성/삭제 시 채팅창을 해당
세션 기록으로 리셋(refreshChatForProject), 연속 전환 레이스는 시퀀스
가드로 처리. 앱세션 저장소(app-sessions.json) 의존 제거.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 19:39:10 +09:00
kimandClaude Code 22cb075835 fix: 작업실 앱 버그 4종 — STL선택 프로젝트 오염, 부품 필드 인젝션, 파일탭 스테일, parts 섀도잉
- switchProject/createProject/deleteProject에서 stlSelection 초기화 누락 →
  프로젝트 전환 후 슬라이서 전송 시 옛 프로젝트 relPath가 현재 프로젝트
  경로로 재해석되어 같은 이름의 다른 파일이 슬라이싱될 수 있던 버그
- renderParts에서 status/qty/unitPrice 이스케이프 누락(채팅 도구가 임의
  문자열 쓰면 HTML 주입 경로) → escAttr 적용
- 프로젝트 생성/삭제 시 파일 탭이 열려있으면 loadFiles 미호출로 옛
  프로젝트 파일 목록 남던 버그 → switchProject와 동일 갱신 추가
- callChat 내 SSE 파싱 변수 parts가 전역 부품 배열 parts를 섀도잉 →
  sseParts로 개명

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 19:36:01 +09:00
kimandClaude Code 54263ae0b4 fix: 작업실 앱 6종 버그 수정 — renderMd 코드/리스트, softRefresh 덮어쓰기, escJs 인젝션, 테마 아이콘
renderMd: 코드펜스 Placeholder 추출→연속 목록 ul 그룹화→단락 분할→블록태그 p
래핑 제외→Placeholder 복원 순으로 재작성. softRefreshWorkshop 추가(저장 대기
플러시 후 변경시에만 재렌더, expandedParts/notes 보존). escJs 추가해 삭제/토글
onclick 인젝션 방지. 테마 토글 아이콘(🌙/☀️) 동기화.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 19:28:54 +09:00
kimandClaude Code 7485192e4d fix: 작업실 채팅의 scad→STL 변환을 끝까지 신뢰성 있게 만듦
하나의 요청("scad를 STL로 만들어줘")을 실제로 안정적으로 처리하기까지
여러 겹의 문제가 있었음 — 전부 실측(ws_ 세션 실제 호출)으로 확인하고 고침.

1. 진짜 원인: build-tools.ts에 stl_cad의 완전히 별개인 낡은 하드코딩
   스키마 사본이 있어서, stl-cad.ts의 jsonSchema를 아무리 고쳐도 모델에게
   전달 안 되고 있었음(registry.ts 등록과 별개로 이 파일도 갱신해야 함).

2. 액션 enum(stl_cad의 action="compile") 선택 자체가 경량모델에 어려운
   것으로 3연속 실측 확인 — 전용 단일목적 도구 scad_to_stl 신설(scad_path/
   output_path/part만 받음, 선택지 없앰). tool-scope.ts에 게이트 추가.

3. python_eval의 subprocess 차단 메시지에 대안 도구 힌트 추가 — 처음엔
   워드바운더리(\b) 정규식이 pattern.source 문자열 자체와 안 맞는 버그로
   한 번도 안 붙었던 것도 같이 고침.

4. hasStlCadKeyword가 짧은 재시도("다시 시도해봐")에서 원래 요청이 2턴
   윈도우 밖으로 밀려나며 도구가 사라지는 문제 — 직전 어시스턴트 답변도
   같이 보게 함(lastAssistantMsg를 tool-scope.ts까지 스레딩).

5. AUTO-RECOVER가 scad_to_stl의 정상 결과("200.0×110.0×277.5 mm", 방금
   만든 파일 실측치)를 "검증 안 된 스펙 주장"으로 오판해 웹서치를 강제
   재프롬프트하던 것 — retry-decisions.ts의 GROUNDING_TOOL_PATTERN에 추가.
   이 패턴이 "검색결과 읽는 법" 안내 트리거와 공유돼있어서, CAD 결과에
   그 안내가 붙어 모델이 엉뚱하게 답하는 부작용도 같이 분리해서 막음.

6. 워크숍 세션 컨텍스트(handle-chat.ts workshopProjectCtx) 확장: 활성
   프로젝트 안 scad 파일이 하나뿐이면 되묻지 않고 자동 사용, STL
   저장경로는 원본 scad 옆 print/ 서브폴더로 유도(CAD/print=인쇄용,
   CAD/reference=참고용 관례 유지).

7. workshop-app.html: 채팅으로 새 파일이 생겨도 파일탭이 이미 열려있으면
   자동 새로고침 안 되던 것 수정.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 17:05:45 +09:00
kimandClaude Code 71afd45959 fix: 앱 채팅 "대화 지우기" 버튼이 실제로는 안 지워지던 버그
DELETE /api/chat/sessions/:id가 세션 파일만 unlink하고 session.ts의
in-memory Map은 그대로 둬서, 같은 세션ID를 계속 재사용하는 앱(투자자·의사·
날씨·회계사·변호사·마음·작업실·탐정, 총 8개)에서 삭제 후 같은 세션에 새
메시지를 보내면 다음 saveSession() 때 메모리에 남아있던 옛 대화가 파일로
그대로 부활했음.

파일 삭제 후 clearHistory()(메모리+파일 둘 다 제대로 비우는 기존 함수)도
호출하도록 수정. 실제로 메시지→삭제→같은 세션 새 메시지 시나리오로 재현
검증(부활 안 됨 확인).

워크숍 앱 채팅 헤더 CSS도 방어적으로 수정 — 삭제 버튼에 flex-shrink:0이
없어서 좁은 폭에서 라벨/모델pill에 밀려 안 보일 수 있던 것 고침.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 16:19:35 +09:00
kimandClaude Code c79364312c feat: 작업실 앱 파일 탭에 K2 슬라이서 연동
- 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>
2026-09-22 14:28:49 +09:00
kimandClaude Code 80ea4322bc feat: 작업실 앱에 프로젝트별 파일 브라우저 + K2 CAD 편집기 연동
- 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>
2026-09-22 13:23:05 +09:00
kimandClaude Sonnet 5 dc76158c6f feat: 작업실 채팅에 경량 모델 pill 추가
메인 채팅의 전체 설정 모달을 그대로 들여오는 대신, 같은 백엔드
엔드포인트(/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>
2026-09-21 21:50:40 +09:00
kimandClaude Code c54c07338a refactor: K2 3MF 지원을 패스스루 대신 서버 변환 방식으로 전환
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>
2026-09-21 20:43:58 +09:00
kimandClaude Code b2285a8059 feat: K2 슬라이서에 3MF 파일 지원 추가
PrusaSlicer CLI가 3MF를 네이티브로 지원해서(확장자로 자동판별) 서버는
format 쿼리파라미터(stl 기본값, 3mf)로 저장 확장자만 바꾸면 됨.

- routes-k2.ts: /api/k2/slice에 format 파라미터 추가
- print3d.ts: stl_url 확장자가 .3mf면 3mf로 다운로드/슬라이스
- slicer-view.js: 파일선택 .3mf 허용. Three.js STLLoader로는 3MF를
  못 읽으므로 미리보기/배치편집 없이 원본 그대로 패스스루(arrange=1,
  STL 다중 인스턴스 편집과 상호배타적)

E2E 검증: 3MF 큐브 슬라이스 200 성공, 기존 STL 경로 회귀 없음 확인.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 20:39:23 +09:00
kimandClaude Code 77c573f4a5 feat: "STL 확인" → Three.js 3D STL 편집기로 전환
- 신규 팝업 cad-editor-app.html + cad-editor-view.js: Three.js 3D 뷰포트(원점 그리드+축),
  워크스페이스 STL 로드/측정, 변환(이동·회전·배율·뒤집기·거울·중앙정렬), 구멍(표면 클릭 픽),
  박스 컷, 두 STL 합/차/교집합, 새 파일로 저장.
- 좌표계: 모델 원본 프레임 유지(슬라이서처럼 베드 중앙정렬 안 함). STL Z-up↔three Y-up
  매핑 = rotateX(-90°), 역매핑 STL=(threeX,-threeZ,threeY). 변환은 클라이언트 bake, boolean은
  bake→서버 OpenSCAD→결과 바이트 재로드(변환 리셋).
- stl-cad-core: applyBooleanOp(op∈hole/cut/union/difference/intersection) 추가, addHoleToStl를
  그 위의 얇은 래퍼로 리팩터(채팅도구 하위호환 유지).
- routes-cad: GET /api/cad/file(원본 다운로드, dotfiles:allow), POST /api/cad/save(bake된 STL 저장),
  POST /api/cad/apply(바디=primary 바이트, ?op=&params, ?second=) 추가. 기존 list/info/render/
  add-hole은 건드리지 않음.
- k2-app: "🔧 STL 확인" 버튼 → "🔧 STL 편집" 팝업(openCadEditorPopup). 옛 PNG 모달 마크업·JS·CSS 제거.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-20 21:53:24 +09:00
kimandClaude Code 25a7fbb71d feat: k2 슬라이서에 STL 복수 로드 + 위아래 뒤집기 추가
- 파일 입력 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>
2026-09-20 21:38:10 +09:00
kimandClaude Code fedc03d5f4 feat: k2 슬라이서에 Cura식 모델 배치(복제/이동/회전/배율) 추가
단일 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>
2026-09-20 21:29:05 +09:00
kimandClaude Code d93e6bcdf9 fix: 슬라이서 툴패스가 빌드플레이트 중심에서 벗어나던 문제 수정
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>
2026-09-20 21:19:31 +09:00
kimandClaude Code 9a0026b225 refactor: k2 슬라이서를 k2-app 오버레이 → 전용 팝업창으로 분리
"🔪 슬라이싱" 버튼이 k2-app 위에 오버레이를 띄우던 방식을 window.open() 전용
팝업창(slicer-app.html)으로 분리. k2-app(카메라+Fluidd 모니터)과 슬라이서
(3D 편집 도구) 역할 분리 + 오버레이 블랙스크린 원인 해결.

- 블랙스크린 원인: 옛 방식은 #slicer-overlay 가 display:none 이다가 .show로
  토글되는데, 그 직후 initScene이 실행되면서 뷰포트 clientWidth/Height가 0이라
  renderer.setSize가 스킵→캔버스 0size. 전용 팝업은 #slicer-root 가 처음부터
  position:fixed;inset:0 이라 크기가 바로 잡힘.
- web-ui/html/slicer-app.html 신규: Three.js+슬라이서 모듈만 로드하는 풀뷰포트
  팝업 페이지. 인증은 같은 오리진 smallclaw_session 쿠키가 팝업에 자동 상속.
- slicer-view.js: openSlicer/closeSlicer 오버레이 토글 → initSlicer 자동 초기화
  (DOMContentLoaded). .sv-shell을 #slicer-root에 빌드. 캔버스 CSS width/height
  100%로 셀 채우도록 수정. ✕버튼=window.close().
- k2-app.html: #slicer-overlay div·Three.js 스크립트 제거, 버튼→openSlicerPopup().
  CAD 모달·카메라·Fluidd iframe은 그대로.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-20 21:13:21 +09:00
kimandClaude Code 8fbf581ca1 feat: k2-app Cura 스타일 3D 슬라이싱 GUI (Three.js 툴패스 레이어뷰)
폼 기반 프로필 에디터를 CuraEngine GUI처럼 풀뷰포트 3D 뷰로 교체.
Three.js r128 UMD 벤더(/vendor/three/)로 260³ 빌드 플레이트 위 STL을
3D로 띄우고, 슬라이스 후 gcode를 파싱해 ;TYPE:별 툴패스를 레이어별로 시각화.
슬라이스↔업로드 흐름을 Cura식으로 분리(slice≠전송) — 슬라이스만 하고
툴패스 검토 뒤 명시적으로 업로드/출력 시작.

- web-ui/js/k2/slicer-view.js: 3D 씬 + STL 로드 + gcode 툴패스 파서 + 레이어 슬라이더
  + 프로파일 패널(스키마·오버라이드·저장) 이전. ;LAYER_CHANGE 마커로 레이어 경계
  감지(Z변화 감지는 start_gcode 프라이밍 Z3→Z0.2→Z1이 가짜 층을 만드는 걸 회피).
- routes-k2.ts: /api/k2/slice 를 upload=0 기본(슬라이스만)으로 변경,
  POST /api/k2/upload·GET /api/k2/gcode/:filename 신규.
  gcode 라우트의 res.sendFile에 {dotfiles:'allow'} 추가 — 워크스페이스가
  .smallclaw(도트파일 디렉토리) 아래라 send 라이브러리 기본값 'ignore'가
  404 "Not Found"로 씹어버리는 걸 해결.
- k2-slicer-core.ts: parsePrusaFilament(mm/cm3) 추가.
- k2-app.html: 슬라이싱 모달→#slicer-overlay 컨테이너로 교체, Three.js 스크립트 로드.
  CAD 모달·카메라·Fluidd iframe은 그대로 유지.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-20 21:08:23 +09:00
kimandClaude Code 5920540689 feat: k2-app 슬라이싱 풀 프로필 에디터 추가
단순 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>
2026-09-20 20:47:31 +09:00
kimandClaude Sonnet 5 65d9325f8b feat: k2-app에 STL 확인 웹 UI 추가(드롭다운+미리보기+구멍뚫기)
채팅으로 좌표를 말로 불러주는 게 너무 번거롭다는 피드백으로, 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>
2026-09-20 20:21:02 +09:00
kimandClaude Sonnet 5 7ec72e776a refactor: "로봇 프로젝트" 단일 대시보드를 여러 프로젝트를 오가는 "작업실"로 일반화
로봇 프로젝트 하나만 다루던 대시보드/도구를 로봇 외 다른 메이커 프로젝트
(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>
2026-09-20 19:04:49 +09:00
kimandClaude Sonnet 5 076a53b8de feat: 로봇 부품 카드에 참고링크(구매처/대체품/업그레이드/레포 등) 추가
각 부품 카드에 라벨+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>
2026-09-20 18:06:28 +09:00
kimandClaude Sonnet 5 68c1ae9b21 fix: K2 원격 출력 시작 시 필라멘트 삽입 확인 안내 추가
K2 콤보 CFS 시스템은 출력 시작할 때 터치스크린에서 "필라멘트 삽입"을 직접
눌러 확인해야 실제 급지가 되는데, API로 원격 시작하면 아무도 그 확인을
못 눌러줘서 압출이 안 되고 Klipper 안전장치로 히터가 자동으로 꺼지는 걸
오늘 실제로 겪음(2026-09-20). 웹 UI 슬라이싱 모달과 print3d_model 채팅도구
둘 다 출력 시작 성공 메시지에 "터치스크린 확인하라"는 안내를 추가.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 17:55:19 +09:00
kimandClaude Sonnet 5 164ce6dcc1 feat: k2-app 웹 UI에 STL 슬라이싱 기능 추가
로컬 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>
2026-09-20 17:13:13 +09:00
kimandClaude Sonnet 5 9dfef8b568 refactor: robot-app 부품 리스트를 표에서 펼침형 카드로 변경
행마다 항상 다 펼쳐져 있던 표 대신, 이름/상태뱃지/소계만 보이는 헤더를
클릭하면 수량·단가·상태·메모 편집 필드가 펼쳐지는 카드로 교체. 펼침
상태는 화면 전용(서버 저장 안 함)이라 다시 그려도 유지됨.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 14:58:38 +09:00
kimandClaude Sonnet 5 54285c8a4c feat: robot-app에 내장 채팅 패널 추가 (doctor/lawyer 앱과 같은 사이드 패널 패턴)
부품 리스트를 보면서 바로 옆에서 로봇 프로젝트 얘기를 할 수 있게 리사이즈
가능한 채팅 패널을 대시보드에 붙임. 세션ID를 "rb_"로 시작하게 해서
tool-scope.ts가 이 세션 안에서는 키워드 매칭 없이 robot_project 도구를
항상 열어주도록 예외 처리(doctor_app의 'dr_'/lawyer_app의 'lw_'와 동일한
패턴) — "이 부품 지워줘"처럼 짧게 말해도 바로 동작한다.

채팅 응답이 끝나면 대시보드(부품표/예산/작업)를 자동으로 다시 불러와서
채팅으로 바꾼 내용이 즉시 화면에 반영되게 함.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 14:48:50 +09:00
kimandClaude Sonnet 5 b358cbc968 feat: robot-app에 K2/밀링 장비 연동 위젯(장비 탭) 추가
로봇 프로젝트 대시보드에 실제 공작 장비(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>
2026-09-20 14:07:54 +09:00
kimandClaude Sonnet 5 9641f689b8 feat: 로봇 프로젝트 관리 대시보드(robot-app) 추가
메카넘휠 이동로봇 프로젝트(파이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>
2026-09-20 13:46:37 +09:00