이 킷의 어려운 부분은 슬라이드를 쓰는 일이 아니라 루프를 믿을 수 있게 만드는 일이다. 사용자가 보낸 수정 지시가 유실되지 않고, 엉뚱한 슬라이드가 고쳐지지 않으며, 반쯤 고쳐진 화면이 보이지 않아야 한다. 그 셋을 어떻게 보장했는지, 그리고 무엇을 기각했는지 적어 둔다.
직접 비슷한 것을 만들려는 사람에게는 기각 사유 쪽이 더 쓸모 있을 것이다.
브라우저 (덱 + 주입된 오버레이)
│ 문장 드래그 → 지시 입력 → POST /__feedback
▼
server.py queue.jsonl 에 1줄 append
▼
자동 깨우기 (queue.jsonl 감시) 새 줄마다 Claude 에게 알림
▼
Claude cursor 이후 줄만 처리 → 슬라이드 수정
▼ → 스냅샷 → published 토큰 갱신
브라우저 /__status 폴링 → 토큰 변경 감지 → 새로고침 + 위치 복원
깨우기 신호를 "플래그 파일 하나"로 두면 다음이 깨진다. 사용자가 피드백 A를 보내 플래그가 서고, Claude가 반영을 시작한다. 반영은 여러 번의 파일 수정이라 수십 초에서 수 분이 걸린다. 그사이 사용자가 피드백 B를 보낸다 — 플래그는 이미 서 있으므로 아무 변화가 없다. Claude가 반영을 마치고 플래그를 지운다. B는 사라졌다.
이건 예외적인 경합이 아니다. 사용자는 화면을 보며 계속 고칠 곳을 찾으므로, 반영 중에 다음 피드백을 보내는 것이 정상 사용 패턴이다.
신호를 상태로 착각하지 않는다.
- 상태(진실):
queue.jsonl(추가 전용) +cursor(처리한 줄 수). 미처리 =줄 수 − 커서. - 신호(힌트): 큐 파일 감시 알림. 유실돼도, 중복돼도, 늦게 와도 무해하다.
드레인은 멱등하다 — 커서 이후 줄만 처리하고, 없으면 아무 일도 하지 않는다. 그래서:
| 상황 | 결과 |
|---|---|
| 반영 도중 새 피드백 도착 | 큐에 쌓임 → 다음 드레인에서 처리. 유실 없음 |
| 알림이 중복 도착 | 두 번째 드레인은 no-op |
| 알림이 유실됨 | 다음 아무 계기(사용자 발화, 세션 재개)에 처리 |
| 세션이 끊김 | 큐는 디스크에 있음. 재개 시 일괄 반영 |
핵심은 커서를 "1단계가 알려준 값"으로 전진시키는 것이다. 완료 시점에 줄 수를 다시 세면
드레인 도중 도착한 피드백을 처리하지 않고 삼켜버린다. 그래서 CLI가 pending 에서 cursor_after 를
알려주고 done --cursor 로 되받는다.
- 플래그 파일 — 위 레이스. 애초에 N개의 사건을 불리언 1개로 뭉개는 표현이다.
- 락 파일 — 커서 방식에서는 보호할 임계구역 자체가 없다. 복잡도만 는다.
- 큐를 소비하며 삭제(pop) — 반영이 실패하면 원본이 사라진다. 추가 전용 + 오프셋이 안전하다.
편집 도구를 붙이면 산출물이 오염되기 쉽다. 이 킷은 오버레이 <script> 를 HTTP 응답에만 끼워 넣는다.
파일에는 쓰지 않는다.
- 디스크의 HTML은 언제나 배포 가능하다. 마감 절차가 없다.
- 파일을 더블클릭해 열면 편집 UI 없이 깔끔하게 열린다.
?clean=1로 서버 경유로도 원본을 볼 수 있다.
- 덱에 로더 스크립트를 심어두고 서버 없으면 무시 — 최종 파일에
<script src="/__editor/…">가 남는다. "마감 때 제거"라는 수동 불변식이 생기고, 그건 잊기 마련이다. 잊을 수 있는 규칙 대신 존재하지 않는 구조를 택했다.
"3번 슬라이드"라는 지목은 편집과 함께 낡는다. 5번을 둘로 쪼개면 그 뒤 번호가 전부 밀린다. 큐에서 대기 중인 피드백이 엉뚱한 슬라이드를 가리키게 된다. 그리고 그 오류는 조용하다 — 아무 에러도 나지 않고 멀쩡한 슬라이드가 망가진다. 이 시스템에서 가장 나쁜 실패다.
세 겹으로 막는다.
- 불변 ID — 각 슬라이드에
data-sid를 고정한다. 표시 번호는 런타임 계산이라 ID와 무관하다. 분할 시 앞쪽이 승계하고 뒤쪽이 새 ID(s07-2)를 받는다. 전체 재번호는 금지. - 선택 문장 자체를 전달 — 사용자가 긁은 원문과 앞뒤 문맥이 함께 온다. 번호가 아니라 내용으로 위치를 찾는다.
- 제목 대조 — 반영 직전, 그 ID의 현재 슬라이드 제목이 피드백에 기록된 제목과 맞는지 본다. 어긋나면 편집을 멈추고 되묻는다.
기존 덱에는 ID가 없으므로 입양 의식으로 한 번 부여한다. 속성만 추가하므로 내용·장수가 변하지 않고, diff로 검증하기 쉽다(실측: 22장 덱에서 속성 외 차이 0).
- 런타임 순번 자동 부여 — 지금 화면에서는 맞지만 큐에서 대기하는 동안 밀린다. 위 문제를 그대로 재현한다.
- CSS 선택자 경로 기록(
div:nth-child(3) > p:nth-child(2)) — 구현은 쉽지만, 슬라이드를 다시 쓰는 순간 경로가 죽는다. 그리고 Claude에게 필요한 건 선택자가 아니라 의미다. 그래서 "2번째 표" 같은 의미 힌트 + 원문 발췌를 보낸다.
한 배치에 슬라이드 다섯 장을 고치면 파일이 다섯 번 바뀐다. 파일 변경을 감시해 새로고침하면 사용자는 반쯤 고쳐진 화면을 네 번 본다.
게시 토큰을 따로 둔다. 오버레이는 파일이 아니라 feedback/published 의 내용만 본다.
Claude는 모든 수정을 마친 뒤 마지막에 한 번 토큰을 올린다. 새로고침은 배치당 정확히 한 번이다.
파일 수정시각(mtime)이 아니라 내용을 비교하는 이유: 되돌리기로 옛 파일을 복원하면 mtime은 올라가지만 내용 기준이면 의도한 시점에만 갱신된다. 오판 경로를 하나 없앤다.
지시 입력창이 열려 있는 동안에는 새로고침을 유예한다. 타이핑 중에 페이지가 날아가면 안 된다.
새로고침은 1번 슬라이드로 돌아간다. 그러면 "고치고 확인"이 아니라 "고치고 다시 찾아가기"가 된다.
- 표준 골격:
#sid해시를 네이티브 지원하므로 정확히 복원된다(딥링크는 덤). - 기존 덱: 해시를 모르는 덱이 대부분이다. 그래서 방향키 이벤트를 흘려보내 덱이 스스로 이동하게 한다. 덱의 내부 인덱스를 건드리지 않으므로 어떤 구현에서도 통한다.
- 되돌리기: 드레인 시작 시 편집 직전 상태가
versions/<타임스탬프>/에 자동 보존된다. 한 번에 복구된다. - 모호한 지시: 추측하지 않는다. 해당 항목만 보류로 표시하고 되묻되, 나머지는 반영한다(부분 드레인). 전체를 멈추면 나머지 아홉 건이 인질이 된다.
- 깨우기 실패: 반자동으로 강등된다. 오버레이가 대기 건수를 표시하고, 사용자가 아무 말이나 하면 턴 시작 검사에 걸려 처리된다. 데이터는 애초에 안전하다.
데모 덱 7장으로 전 경로를 실측했다.
| 항목 | 결과 |
|---|---|
| 오버레이 주입 / 파일 클린 유지 | 응답에만 주입, 디스크 __editor 흔적 0 |
| 하이라이트 → 큐 적재 | 선택 문장·앞뒤 문맥·요소 힌트("표 셀 #2") 정상 수록 |
| 자동 깨우기 | 전송 즉시 알림 도달, 내용 그대로 전달 |
| 반영 → 자동 갱신 | 수동 새로고침 없이 갱신, 보던 5·7번 슬라이드 유지 |
| 드레인 중 피드백 도착 | 유실 0 — 다음 배치에서 처리 확인 |
| 키 충돌 | 입력창에 o·n·방향키 타이핑 시 덱 무반응 (격리 성공) |
| 기존 덱 입양 | 22장 덱, 속성 추가 외 diff 0 |
| 되돌리기 | 훼손 후 1회 복구 성공 |