feat: 피드백 저장을 리뷰 서버 왕복으로 전환하고 에이전트 CLI 추가 - #7
Conversation
file:// 로 연 캔버스는 origin이 opaque라 File System Access API가 거부된다. 그래서 Save feedback의 직접 쓰기가 항상 실패하고 다운로드로 밀려났고, 에이전트는 사용자가 옮겨놓은 파일을 기다려야 했다. supercanvas view를 loopback 리뷰 서버로 바꿔 저장 한 번에 패키지의 feedback.json을 쓰고 재렌더한다. 디스크에서 직접 연 캔버스는 기존 모달로 폴백한다. - serve.mjs: 토큰 핸드셰이크를 주입하는 리뷰 서버, POST /api/feedback - feedback.mjs: 노드 측 피드백 파일 읽기·해결·요약·대기 - CLI feedback / resolve / discuss 추가, resolve는 재렌더까지 수행 - feedback --wait: 리뷰어 저장까지 블록 후 변경분만 출력하고 종료 - 저장과 클리어 분리: resolved는 체크 핀으로 남아 변경 요약을 보여주고, Clear resolved가 아카이브로 회전시킨다 - 서버 모드에서 에이전트 갱신을 감지해 버전 배지에 reload 안내 - test/review-roundtrip.mjs 왕복 검사와 CI 연결 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: rayim <rayim@fxy.global>
tonite31
left a comment
There was a problem hiding this comment.
리뷰 요약
file://의 opaque origin 제약을 loopback 서버로 우회한 진단과 해법이 정확합니다. 검증 규칙을 runtime/feedback.js 한 곳에 두고 Node에서 vm으로 로드해 재사용한 것, 저장이 resolved를 회전시키지 않는지를 verify.mjs 소스 검사로 고정한 것, 왕복 전체를 e2e로 덮고 CI에 연결한 것이 특히 좋습니다.
보안 쪽은 견고합니다. 127.0.0.1 바인딩에 실행마다 새 랜덤 토큰, 커스텀 헤더 요구로 다른 origin의 POST는 프리플라이트에서 막히고 CORS 헤더가 없어 GET 응답도 읽히지 않습니다. 라우트 3개 화이트리스트에 정적 파일 서빙이 없어 경로 탈출 여지가 없고, 본문 4MB 제한과 검증 실패 시 파일 무변경도 테스트가 덮고 있습니다.
머지 전에 하나는 반드시 고쳐야 합니다 — 낡은 페이지의 저장이 에이전트의 resolution을 덮어쓰는 lost update (serve.mjs). 나머지 3건은 고쳐두면 좋을 수준입니다.
확인해봤지만 문제 없던 것: waitForReview의 updatedAt 의존은 런타임이 편집·답글·상태 변경 모두에서 갱신하고 있어(runtime/board.js:1200,1210,1221) 괜찮습니다. --port 파싱도 값 누락·비수치 모두 NaN으로 떨어져 검증에 걸립니다. 2칸 K&R 스타일은 protocol.mjs·render.mjs·update.mjs와 같아 레포 컨벤션에 맞습니다. 서버 저장 경로가 verify를 건너뛰는 건 앞단 validateEnvelope가 막으니 의도적 선택으로 보고 넘겼습니다.
브라우저는 자기가 로드한 시점의 feedbackRevision을 envelope에 실어 보내는데 서버가 그 값을 파일의 현재 값과 비교하지 않았다. 그래서 페이지를 열어둔 채 에이전트가 resolve로 코멘트를 닫으면, 리뷰어의 다음 저장이 resolve 이전의 open 상태로 파일을 덮어써 resolution이 사라지고 feedbackRevision만 올라간 "rev는 새것인데 코멘트는 open"인 파일이 남았다. - serve.mjs: handleSave가 검증 직후 envelope의 feedbackRevision을 파일 값과 대조하고, 다르면 409로 거절하고 파일에 쓰지 않는다. revision이 아예 없는 payload는 어느 파일에서 만들어졌는지 증명할 수 없으므로 불일치와 동일하게 거절한다 — 쓰기를 막는 쪽이 안전한 해석이다 - runtime/board.js: postFeedback이 거절에 status를 실어 던지고, saveFeedback은 409일 때 다운로드 폴백 대신 "reload before saving again" 안내만 띄운다. markSaved를 부르지 않으므로 draft가 남아 리뷰어의 코멘트를 잃지 않는다 - feedback.mjs: resolveComments가 이미 resolved인 코멘트를 "Already resolved"로 거절한다. 기존 문구는 open 여부를 확인하지 않으면서 No such open comment라 말해 동작과 어긋났고, --wait 루프의 재시도가 조용히 resolution을 덮어썼다 - bin/supercanvas.mjs: 스레드 메시지 ID를 randomUUID로 바꿔 같은 밀리초 충돌 제거 - test/review-roundtrip.mjs: resolve 직후 낡은 envelope POST → 409와 파일 무변경 검사 추가, 종료 시 closeAllConnections로 keep-alive 소켓 대기 제거 - README·authoring-guide: 409 거절과 재-resolve 거절 동작 반영 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: rayim <rayim@fxy.global>
Closes #6
supercanvas view가dist/canvas.html을file://로 열던 것을 loopback 리뷰 서버로 바꿨다. opaque origin에서는 브라우저가 어디에도 쓸 수 없어서 직접 저장이 구조적으로 불가능했고, 같은 파일을 loopback으로 서빙하면 페이지가 쓸 수 있는 엔드포인트가 하나 생긴다. 디스크에서 직접 연 캔버스는 기존 다운로드·복사 모달로 폴백한다.변경
serve.mjs— 리뷰 서버. 페이지에 토큰 핸드셰이크를 주입하고POST /api/feedback으로 패키지의feedback.json을 쓴 뒤 재렌더한다. 다른 origin이 포트를 맞혀도 토큰 없이는 쓰지 못한다.feedback.mjs— 노드 측 피드백 파일 읽기·해결·요약·대기. 검증 규칙은runtime/feedback.js한 곳에 두고 vm으로 로드해 재사용한다.feedback/resolve/discuss— 에이전트가 손으로 JSON을 고치지 않고 왕복한다.resolve와discuss는feedbackRevision을 올리고 재렌더까지 수행한다.feedback --wait— 리뷰어가 저장할 때까지 블록했다가 변경분만 출력하고 종료한다. 백그라운드 작업으로 두면 프로세스 종료가 곧 신호라서, 에이전트가 "확인해봐"라는 말 없이 다음 라운드를 집는다.Clear resolved가 아카이브로 회전시킨다. 기존에는 저장할 때마다 자동 회전되어 사용자가 결과를 확인할 기회가 없었다.검증
test/review-roundtrip.mjs를 추가하고 CI에 연결했다. 예제 패키지 복사본에 서버를 띄워 토큰 없는 저장 거부(403), 저장의 파일·재렌더 반영,--wait의 저장 감지와 열린 작업 즉시 반환, CLI resolve의 상태·revision 변경, 클리어의 아카이브 회전, 잘못된 envelope 거부(422)와 파일 무변경까지 확인한다.실제 Chrome에서 댓글 작성 → 저장 → CLI resolve → 새로고침 → 변경 요약 확인 → Clear resolved 전 구간을 왕복해 동작을 확인했다.
🤖 Generated with Claude Code