Folder Bot
ENKO
Docs / PRD & decisions

The app ships with a Korean interface and the documentation is Korean-only for now. English is coming.

This page is rendered from docs/PRD.md in dave-jin/folderbot and fetched at build time. It isn’t edited here.

Folder Bot — PRD

버전: v1.0 · 2026-09-12 · 구현 시작 (v0.1.0 테스트 빌드) · 초안 — Dave 와 함께 수정 중. 코드 작업 금지, 계획만. 이름: Folder Bot (2026-09-12 확정 · 가제 Project Bot 폐기) · 캐릭터·애칭 폴더봇. 리포는 dave-jin/folderbot (2026-09-12 개명 · 옛 rondo 주소는 리다이렉트). 목업: https://claude.ai/code/artifact/046fd71a-18f5-40df-980e-f7725380d2c2 (v2 — 폴더봇) 위치: 완전히 새로운 프로덕트. Rondo 알파(rondo-app)는 부품 창고로만 쓴다 (부록 A). 알파의 결정·이력은 이 문서에 적지 않는다. 1차 레퍼런스: xAI Grok Bot (2026-08 베타). 2차: Claude Code Remote Control · Codex Remote · Cursor Cloud Agents.


0. 한 줄 정의

내 Mac mini 위에서 사는, 폴더마다 하나씩 붙는 AI 동료들 — 그리고 그들을 PARA 로 관제하는 오케스트레이터.

Grok Bot 이 "봇마다 클라우드 컴퓨터 하나" 라면, Folder Bot 은 "내 컴퓨터(Mac mini) 하나에 폴더마다 봇 하나" 다. 봇을 설정하지 않는다 — **하네스가 이미 있는 PARA 폴더가 봇 후보이고, 에이전트와 함께 일할 폴더를 고르면 시작된다. 폴더를 새로 파고 시작·정지시키는 일은 사람이 직접 하거나 오케스트레이터에게 맡긴다. 어디서든(PC·iPhone·Android) 동료에게 말 걸듯 봇에게 말하고, 봇은 그 폴더의 지침·기억·자료를 들고 Mac mini 에서 일한다. 내 폰이 꺼져도 일은 계속된다.

1. 문제

  1. 환경이 곧 병목이다. 원격 세션(Claude Code on the web 등)은 매번 환경을 새로 만든다 — 클론·의존성·자격증명·볼트 접근. 내 Mac mini 에는 그 전부가 이미 있다.
  2. 세션이 폴더를 모른다. 지금은 세션을 열 때마다 사람이 폴더를 고르고 맥락을 다시 주입한다. 일은 이미 PARA 폴더로 나뉘어 있는데, 도구는 그 구조를 모른다.
  3. 관제탑이 없다. 프로젝트 N 개가 병행되는데 "지금 어디서 무엇이 돌고 있고, 무엇이 내 결정을 기다리나" 를 한 곳에서 못 본다.
  4. 책상을 떠나면 끊긴다. 이동 중·소파에서 승인 하나 누르고 한 줄 지시하는 것이 실제 사용 형태인데, 세션은 노트북 뚜껑과 운명을 같이한다.

2. 레퍼런스와 차이점

Grok Bot Claude Code Remote Control Folder Bot
봇이 사는 곳 xAI 클라우드 VM (봇당 1) 내 맥의 터미널 프로세스 내 Mac mini (호스트 1, 봇 N)
봇의 단위 사람이 이름 붙여 만든 봇 세션 하나 폴더 = 봇 (후보는 자동, 활성은 폴더를 고를 때)
봇의 기억 내부 메모리, 열람·내보내기 불가 세션 트랜스크립트 폴더 안의 파일 (CLAUDE.md·readme·todo·노트) — 열람·편집 가능
동시 세션 봇당 1 서버 모드 최대 32 봇당 N, 호스트 상한
관제 없음(봇들이 같은 VM 공유) 없음 오케스트레이터 1 — PARA 전체를 보고 봇을 부린다
스케줄 루틴(schedule) 없음 루틴(cron) — 봇별·오케스트레이터별
모델 xAI 고정 Claude Claude Code · Codex (봇마다 선택, 병행 가능)
접속 데스크톱·iOS·Android 앱 claude.ai · Claude 앱 웹(PWA) 어디서나 + 얇은 macOS 셸
자체 호스팅 불가 해당 없음 그 자체가 자체 호스팅
가격 $200/월 구독 포함 내 구독(Claude Max·ChatGPT) + 전기세

차별점 두 줄: ① 폴더가 봇이다 — 봇을 "설정"하지 않는다. 폴더의 하네스(CLAUDE.md·.claude/·readme·todo)가 곧 봇이고, 폴더를 고르는 것이 곧 시작이다. ② 오케스트레이터가 PARA 를 안다 — 폴더 생성·봇 깨우기/재우기·Inbox 분류·아카이브·주간 리뷰 같은 구조 작업을 사람이 안 한다.

3. 개념 모델

Host (Mac mini · 항상 켜짐)
 ├─ Orchestrator ★ 1개 — cwd = PARA 루트. 폴더를 파고, 봇을 시작·정지시키고, 부른다. 루틴을 돌린다.
 ├─ 봇 후보 = PARA 각 범주의 **2단계 폴더** (`2. Projects/<x>` · `3. Area/<x>` …) — 하네스가 이미 구축된 폴더
 └─ Folder Bot(폴더봇) ★ 시작한 폴더당 1개 (활성)
      ├─ 정체성 = 폴더의 하네스 (CLAUDE.md/AGENTS.md · .claude/ · readme.md · todo.md)
      ├─ Session 0..N (동시) — Claude Code 또는 Codex · 각자 --resume 가능
      ├─ Routine 0..n (cron) — 이 봇 안에서 주기적으로 도는 세션
      └─ 참조 — 볼트의 다른 폴더(Resources 등) · 연결된 코드 리포 (~/dev/*)

3.1 후보는 자동, 시작은 폴더를 고를 때 (봇 수명주기)

후보 (PARA 2단계 폴더) ──시작──▶ 활성 봇 ──정지──▶ 휴면 (기록 유지) ──Archive 이동──▶ 은퇴
        ▲                      (사람 또는 오케스트레이터)
        └── 새 폴더 만들기 + 하네스 스캐폴드 (사람이 피커에서 직접 · 또는 오케스트레이터)

어떤 폴더가 후보인지는 하드코딩하지 않는다 — 루트의 폴더 규칙(§3.5)이 정한다. PARA 프리셋일 때 규칙은 아래와 같다.

역할 (규칙 키) PARA 프리셋 기본값 봇
active — 봇 후보가 되는 폴더 2. Projects/* · 3. Area/* 시작하면 Project Bot / Area Bot (폴더의 종류일 뿐, 제품명은 Folder Bot)
inbox — 정리 대기 1. Inbox 봇 없음. 오케스트레이터가 규칙대로 배정
reference — 읽기 참조 4. Resources 봇 없음. 봇들이 --add-dir 로 읽는다
archive — 은퇴 5. Archive 봇 없음. 여기로 가면 봇은 은퇴
  • 후보는 파생(스캔)이고 활성 목록만 저장한다 (PARA/.folderbot/bots.yml). 봇이 많아지지 않도록 활성 상한(기본 8)을 둔다 — 넘기면 오케스트레이터가 재울 봇을 제안한다.
  • 시작하는 방법 넷: ① 사람이 피커에서 폴더 선택 ② 사람이 피커에서 새 폴더 만들기 → 하네스 스캐폴드 → 바로 시작 ③ 오케스트레이터에게 "X 폴더에서 시작해" ④ 오케스트레이터가 Inbox 분류·루틴 중 필요해서 제안(사람 승인).
  • 폴더가 없으면 만든다 — 피커의 각 PARA 범주 첫 줄 새 폴더 만들기, 또는 오케스트레이터가 PARA 규칙에 맞는 위치·이름으로 만든다. 어느 쪽이든 하네스 스캐폴드(CLAUDE.md·readme.md·todo.md·.claude/)를 깔고 시작한다. 하네스 없는 기존 폴더를 고르면 같은 스캐폴드를 깐다.
  • ⛔ "깨우기" 라는 말을 UI 에 쓰지 않는다 (Dave 2026-09-12). 문구는 "에이전트와 함께 일할 폴더를 선택하세요" · 버튼 "이 폴더에서 시작" · 정지는 "정지", 상태는 "휴면".
  • 폴더 안 .bot.yml(선택)로 vendor: codex · repo: ~/dev/foo(코드 세션의 cwd) · routines: · candidate: false(후보에서 제외)를 선언한다.
  • 볼트 밖 코드 리포는 PARA 폴더가 가리킨다(repo:). 문서는 PARA 에, 코드는 ~/dev 에 — Dropbox 가 node_modules 를 건드리지 않게.

3.2 오케스트레이터 (Orchestrator)

  • 단 하나. cwd = PARA 루트. 하네스는 PARA/.claude/orchestrator.md 에만 있어 프로젝트 봇이 상속하지 않는다.
  • 하는 일: ⓪ 폴더 규칙 관리(§3.5 — 프리셋 설치·인터뷰·개정) ① 후보·활성 봇 목록과 상태 파악 ② 폴더 생성 + 하네스 스캐폴드 ③ 봇 시작·정지·은퇴 ④ Inbox 정리 — 폴더·파일을 규칙대로 이름 짓고 배정, 해당 봇에게 위임 ⑤ 봇에게 작업 지시(세션 생성)·호출 관리 ⑥ 결과 취합·보고 ⑦ 완료 프로젝트 아카이브 제안 ⑧ 루틴 관리.
  • 도구 (호스트가 MCP 로 제공): bots_candidates · bots_list · bot_start/stop/retire · folder_create(PARA 위치·하네스 스캐폴드) · bot_status · bot_send(세션 생성/이어가기 · 오케스트레이터 전용) · orch_ask(폴더 봇 → 오케스트레이터 요청 · 받는 쪽 고정 · BH) · bot_sessions · routine_list/set · vault_search/tree/recent · inbox_list · folder_move/rename(되돌리기 스냅샷 동반) · rules_get/set.
  • 자율 범위 (Q5): 읽기·조사·분류 제안·봇 시작/정지는 알아서, 폴더 생성·파일 이동·삭제·외부 발송은 승인 후. 되돌리기(undo/ 스냅샷)가 있는 동작만 자동 허용.

3.3 세션

  • 봇에게 보낸 메시지는 세션 하나로 간다. 기본은 봇의 "메인 세션"(가장 최근 활성), 새 세션 은 명시적. 세션에는 이름이 있다(--name).
  • 동시 세션 상한: 봇당 살아 있는 워커 4 · 호스트 전체 15 (BE · 2026-09-27). 넘치면 거절하지 않고 가장 오래 안 쓴 쉬는 워커를 절전시킨다. 유휴 60분이면 워커를 내리고(절전) 다음 메시지에 같은 id 로 깨운다(--resume).
  • 코드 리포 봇은 세션마다 git worktree 옵션.
  • 상태 5종(단일 소스): 대기 · 실행 중 · 확인 대기(HITL) · 완료 · 오류. 모든 표면(목록·배지·푸시)이 이 하나를 본다.

3.4 루틴 (cron)

  • 정의는 파일: 봇 폴더의 .bot.yml → routines: [{name, cron, prompt, vendor?, approve?}]. 오케스트레이터 루틴은 PARA/.claude/routines.yml.
  • 실행 = 그 봇 안에 세션을 하나 띄워 프롬프트를 보내는 것. 결과는 봇의 대화에 메시지로 남고, 산출물은 폴더에 파일로 남는다.
  • 예: 오케스트레이터 매일 07:00 Inbox 분류 제안 · 주간 일요일 "이번 주 각 봇 진행 요약" · 제품_Rondo 봇 매일 todo.md 미완료 리마인드.
  • 루틴은 승인 정책을 따로 갖는다 — 사람이 없을 때 도는 것이므로 기본은 읽기 전용 + 제안.

3.5 폴더 규칙 — 오케스트레이터의 하네스가 정한다 (Dave 2026-09-12)

사용자마다 폴더 정리 규칙이 다르다. 그래서 규칙은 코드가 아니라 루트 폴더의 하네스 파일에 있고, PARA 는 기본 탑재된 프리셋일 뿐이다.

  • 정본 파일: 루트(볼트) CLAUDE.md(Claude) 또는 AGENTS.md(Codex) 의 ## 폴더 규칙 절. 첫 실행에 PARA 프리셋으로 채워 주고, 사용자가 그 파일을 직접 고친다. 오케스트레이터는 이 절을 읽고 행동한다 — 규칙이 바뀌면 코드를 안 고쳐도 행동이 바뀐다.
  • 두 층으로 쓴다: ① 사람·오케스트레이터가 읽는 산문 규칙(무엇을 어디에, 이름은 어떻게, 언제 아카이브) ② 호스트가 읽는 작은 구조 블록(같은 절 안의 ```yaml folder-rules 펜스). 후보 스캔·Inbox 감시·은퇴 판정처럼 프로그램이 해야 하는 일은 ②만 본다.
preset: para            # para | johnny-decimal | custom
roles:
  inbox:     ["1. Inbox"]
  active:    ["2. Projects/*", "3. Area/*"]   # 봇 후보 = 이 글롭에 맞는 폴더
  reference: ["4. Resources"]
  archive:   ["5. Archive"]
naming:
  project: "{YYYY}-{MM}_{이름}"                 # 새 폴더 만들기·Inbox 정리 때 적용
harness: ["CLAUDE.md", ".claude/"]            # "하네스 있음" 판정
  • Inbox 는 정리 상자다: 사용자는 정리하고 싶은 폴더든 파일이든 inbox 에 넣기만 한다. 오케스트레이터가 규칙을 읽어 "2026-09_예시고객 로 이름 바꿔 2. Projects 로 옮기고 하네스를 깔까요?" 처럼 제안하고, 승인하면 옮기고 필요하면 봇을 시작한다(자율 범위는 Q5). 파일은 맞는 폴더로 배정한다.
  • 프리셋: para(기본) · johnny-decimal · custom. 첫 실행에서 고르거나, "내 방식대로 할래" 를 고르면 오케스트레이터가 인터뷰해서 규칙 절을 대신 써 준다. 폴더 구조가 없는 빈 루트면 프리셋 골격을 만들어 준다.
  • 규칙 변경도 대화로: "이제 Area 대신 Domains 라고 부를래" → 오케스트레이터가 규칙 절을 고치고(승인), 필요한 폴더 이동을 제안한다. 파일은 사람이 직접 고쳐도 된다 — 파일이 진실.
  • ⚠ 루트 하네스는 모든 봇이 상속한다. 폴더 규칙 절은 봇들이 알아도 무해하지만(참조 폴더를 찾을 때 쓴다), 오케스트레이터 전용 지침(관제 도구 사용법·자율 범위)은 PARA/.claude/orchestrator.md 에 따로 둔다.

3.6 todo.md — 하나의 파일, 두 가지 쓰임 (Dave 2026-09-12)

각 봇 폴더에 todo.md 가 있다. 관리하는 사람은 사용자다 — 잊지 않으려고 적는다. 그런데 봇도 쓴다: 사용자에게 말하려고("Tailscale 폰 설치가 아직이에요"), 그리고 자기가 다음 세션에서 하려고("변경 이력 줄은 다음에 처리"). 이 둘을 하나의 심플한 목록이 감당한다.

  • 형식은 한 줄 = - [ ] 제목: 설명. 콜론 앞이 제목(굵게), 뒤가 설명(흐리게). 설명이 없어도 된다. 완료는 - [x], ## 완료 절로 내려간다. 중첩·우선순위·기한 문법은 두지 않는다 — 필요하면 설명에 쓴다.
  • 누가 적었나가 보인다. 봇이 적은 줄에는 폴더봇 표식(<!-- bot --> 한 줄 주석 또는 접미 — 봇), 사람이 적은 줄에는 없다. 체크는 누구나 할 수 있다.
  • 봇의 규약(하네스에 주입): ① 세션 시작 시 미완료 목록을 읽는다 ② 자기가 남긴 항목은 먼저 처리하거나 왜 못 했는지 갱신한다 ③ 사용자가 할 일이 생기면 목록에 적고 대화에서 한 줄로 알린다 ④ 완료한 항목은 체크한다.
  • 표면: 오른쪽 "이 폴더에서" 패널 상단에 미완료 4개까지 · 클릭하면 시트(전체 목록·추가·완료 절) · 대화 속 봇 메시지에 "할 일에 남겼어요" 카드 · 폰에서는 체크와 추가만.
  • 선택한 할 일을 봇에게 맡기기 — 시트에서 항목을 고르고 보내면 그 제목·설명이 지시가 된다.
  • 파일이 정본. Obsidian·에이전트·앱이 같은 파일을 본다. 호스트가 대신 쓸 때(체크·추가)도 바이트 보존 규칙을 따른다. 루틴(§3.4)의 아침 리마인드가 이 파일을 읽는다.

4. 사용자 경험 — 메신저처럼 심플하게 (Dave 2026-09-12: Grok Bot 형태)

기준은 Grok Bot 의 3분할 메신저다: 왼쪽 봇 목록 · 가운데 대화 · 오른쪽 봇의 작업 화면. 화면을 꽉 채우는 IDE 가 아니라, 동료와 채팅하는 창이다. 파일은 대화 안에서 클릭해 슬라이드로 본다.

4.0 캐릭터 — 폴더봇 (원형 아바타를 쓰지 않는다)

봇의 얼굴은 폴더다: 탭 달린 뒷판 + 얼굴이 있는 앞판. 색은 봇마다 하나(오케스트레이터는 항상 코랄), 표정은 상태 5종 — 쉼(눈 뜸) · 일하는 중(눈 내림 + 입 일자) · 확인해 주세요(입 벌림 + 앰버 느낌표) · 끝났어요(웃음) · 휴면(눈 감음 + z). 크기 3종(20 메타 · 30 헤더 · 44 목록). 목록·헤더·말풍선 옆·피커·알림 아이콘에 같은 캐릭터를 쓴다. 목업의 「폴더봇 캐릭터 시트」가 정본.

4.1 데스크톱 레이아웃 (3분할, 오른쪽은 접힘)

┌──────────────┬────────────────────────────────────────┬──────────────────┐
│ Folder Bot 🔍 │  🗂 제품_Rondo  PARA/3. Area/…  ⋯ ⤴ ≫  │ 이 폴더에서         │
│              │ ┌────────────────────────────────────┐ │ ──────────────── │
│ + 폴더 선택·시작│ │ 저장된 메시지 표시 중 · 연결 복구 시 갱신 │ │ ● 세션 · PRD 작성 │
│              │ └────────────────────────────────────┘ │   실행 중 · 12m   │
│ ● 오케스트레이터│   9월 12일 (금) 오후 2:10               │ ○ 세션 · 메인      │
│   확인 대기 1  │ ┌────────────────────────────────┐    │ ──────────────── │
│ ● 제품_Rondo  │ │ PRD v0.5 를 올렸어.               │    │ 📁 파일           │
│   PRD 올렸어  │ │ 📄 docs/PRD.md   📄 todo.md      │ ←칩│  ▸ docs/         │
│ ○ 강의_Found… │ │ 바뀐 건 §4 레이아웃…              │    │    PRD.md  ·2m   │
│   어제        │ └────────────────────────────────┘    │  ▸ 01_기획/       │
│ ○ 재무_CFO    │  [승인] Bash: npm run qa  [허용][항상][거부]│    todo.md       │
│  (2. PROJECTS)│                                        │    readme.md     │
│ ○ 강의_Found… │  비활성 루틴 ⏱ 아침 todo 리마인드         │ ──────────────── │
│              │ ┌────────────────────────────────────┐ │ ⏱ 루틴 2          │
│ 🏪 마켓       │ │ +  제품_Rondo 에게 메시지…      🎤 ⏎ │ │ ⚙ 하네스 요약      │
│ 👤 대연 진    │ └────────────────────────────────────┘ │                  │
└──────────────┴────────────────────────────────────────┴──────────────────┘
영역 내용
왼쪽 봇 목록 맨 위에 + 폴더 선택 · 에이전트 시작 (점선 상자, 후보 수 표시). 아래는 활성 봇 목록을 PARA 범주별 섹션(관제 · 3. Area · 2. Projects)으로 — 폴더봇 얼굴·이름·마지막 메시지 한 줄·시각·상태 점. 선택된 봇은 폴더 색 탭. 확인 대기는 섹션 안에서 맨 위. 봇이 많아질 때 (Dave 2026-09-12): ① 폭을 드래그 — 64px(접힘: 얼굴+상태 점+대기 배지) · 292px(기본) · 420px(넓게), 폭은 기억 ② 정렬 — 확인 대기 먼저 → 최근 활동(기본) · 최근 활동 · 이름 · PARA 순 ③ 묶기 — PARA 범주별(섹션 접기, 접히면 수만) · 묶지 않기 ④ 밀도 — 보통·촘촘 ⑤ 검색은 이름·경로. 활성 상한(Q3b)을 넘기면 정렬 메뉴 하단에 "휴면 N개를 은퇴시키면 가벼워져요" 한 줄.
가운데 대화 봇과의 대화 하나. 세션은 대화 안의 스레드로 접힌다(세션 전환 탭이 아니라 "메인 · PRD 작성 · +새 세션" 드롭다운). 도구 사용은 접힌 한 줄(⏵ Edit +42 −8), 승인·질문은 카드. 루틴 실행·중지는 시스템 줄. 컴포저는 +(파일·이미지 첨부) · 마이크 · 전송.
오른쪽 "이 폴더에서" (≫ 로 접힘) ① 세션 목록과 상태 ①′ 할 일(todo.md 미완료 4개까지, §3.6) ② 폴더 파일 트리(컴팩트) — 최근 변경 순, 봇이 만든/고친 파일에 점 ③ 루틴 ④ 하네스 요약(CLAUDE.md·스킬·MCP 개수). 이 패널은 보조다 — 접어도 대화만으로 일이 된다.

4.2 파일은 슬라이드로 본다 (IDE 가 아니다)

  • 대화 속 파일 언급은 칩(📄 docs/PRD.md)으로 렌더된다. 봇이 만들거나 고친 파일은 메시지 아래 자동으로 칩이 붙는다.
  • 칩·트리 항목을 클릭하면 오른쪽에서 시트가 슬라이드되어 열린다 — 앱 안 팝업. 대화는 뒤에 그대로 있고, 시트 폭은 화면의 40~60%, 드래그로 넓힘, Esc/바깥 클릭으로 닫힘. 여러 파일은 시트 상단 탭.
  • 시트에서 되는 것: md 렌더(위키링크·콜아웃·프론트매터) · 이미지·PDF·코드·csv 미리보기 · md 는 그 자리에서 편집(저장 시 바이트 보존) · "봇에게 이 파일로 말하기"(칩을 컴포저에 넣음) · Finder/기본 앱으로 열기(데스크톱) · Dropbox 동기화 점.
  • 전체 화면 편집이 필요하면 시트를 분리해 새 창(데스크톱)으로 띄운다. 기본은 시트다.

4.3 폰 (같은 클라이언트)

  • 목록 → 대화 두 화면. "이 폴더에서" 패널은 하단 시트(위로 끌어올림), 파일 뷰어도 전체 높이 시트. 승인 카드는 대화 안 + 푸시.
  • 폰에서 편집은 todo 체크·짧은 노트까지(Q8). 그 이상은 "데스크톱에서 열기" 링크.

4.4 시작 — "에이전트와 함께 일할 폴더를 선택하세요"

  1. 직접 고르기: + 폴더 선택 · 에이전트 시작 → 피커(제목이 곧 안내문). PARA 범주별로 2단계 폴더를 최근 수정순으로, 하네스 있는 폴더에 ✓ 배지, 이미 활성인 폴더는 흐리게. 각 범주 첫 줄에 새 폴더 만들기 — 이름을 치면 그 범주에 폴더를 만들고 하네스를 깐 뒤 바로 시작. 버튼은 이 폴더에서 시작.
  2. 오케스트레이터에게: "예시고객 폴더에서 시작해" → 후보에 있으면 즉시, 없으면 "2. Projects/2026-09_예시고객 로 만들고 하네스를 깔까요?" → 승인 → 생성·시작.

정지·은퇴는 봇 헤더 ⋯ 또는 오케스트레이터에게.

4.5 알림 — 알파 Rondo 의 알림·트레이·HUD 를 승계한다 (Dave 2026-09-12)

무엇을: 확인해 주세요(승인·질문 — 끌 수 없다, 사람 차례를 놓치면 세션이 서 있다) · 끝났어요 · 문제 있어요(세션 실패·미니 인증·연결) · 할 일 남김(봇이 사용자 항목을 적었을 때) · 루틴 결과(기본 꺼짐, 아침에 묶어서). 어디로: ① Mac 알림 센터 + 소리 — 호스트 셸(미니)과 원격 셸(맥북) 모두. 배너에 [허용][열기][거부] 가 있어 앱을 안 열고 승인할 수 있다. 클릭하면 그 대화로 점프(folderbot://) ② 메뉴바 · Dock 배지(§4.6) ③ 폰 푸시(PWA Web Push, 잠긴 화면에서 허용까지) ④ 앱 안 알림 센터(왼쪽 하단 종 → 팝오버: 모든 봇의 이벤트, 확인 대기 필터, 읽음 처리) ⑤ 메일 요약(하루 한 번, 기본 꺼짐). 언제: 조용한 시간(기본 23:00–07:00, 확인해 주세요만 통과) · 봇별 끄기(봇 헤더 ⋯). 규칙: 같은 이벤트는 한 기기에서 처리하면 모두에서 사라진다(호스트가 단일 소스). 알림은 상태 머신(§3.3)의 전이에서만 나온다 — 화면이 알림을 만들지 않는다.

4.6 메뉴바 — 폴더봇이 상태를 얼굴로 말한다 (Dave 2026-09-12: "폴더봇 디자인으로 더 센스있게")

  • 아이콘 = 흰색 단색(템플릿) 폴더봇. 표정 = 모든 봇을 합친 상태: 한가함(쉼) · 일하는 중(눈 아래) · 당신 차례(입 벌림 + 배지에 확인 대기 수) · 방금 끝남(2분간 웃음) · 문제 있어요(X 눈) · 조용한 시간(눈 감음). 승인이 오면 두 번 콩콩 뛰고 멈춘다 — 깜빡이지 않는다, 배지가 남으면 충분하다.
  • 팝오버: 머리에 오케스트레이터 얼굴 + 한 문장("둘이 당신 차례를 기다려요") · 봇 얼굴 줄(상태 점) · 확인 대기 카드 — 여기서 바로 허용/거부 · 지금 하는 일 · "오케스트레이터에게 한 줄" 입력 · 앱 열기 · 조용히 1시간 · 미니 상태(연결·메모리).
  • 같은 팝오버가 미니(호스트 셸)와 맥북(원격 셸) 에 뜬다. 어느 맥에서 눌러도 한 번이면 끝.
  • Dock 배지 = 확인 대기 수. 플로팅 HUD(알파)는 메뉴바 팝오버로 흡수 — 항상 위에 떠 있는 창은 두지 않는다(Q14).

4.7 첫 실행 (Mac mini)

호스트 설치 → 루트 폴더 선택 → 폴더 규칙 프리셋 선택(PARA 기본 · Johnny.Decimal · 내 방식대로) → 루트 CLAUDE.md 에 규칙 절 설치(이미 있으면 절만 덧붙임) → 규칙으로 스캔 → 후보 목록이 채워진다 → 오케스트레이터가 "지금 활발한 폴더 3개에서 시작할까요?" (최근 수정 기준) → Tailscale 로그인(구글) → 폰에서 주소 열기. "내 방식대로" 를 고르면 오케스트레이터가 인터뷰로 규칙을 써 준다.

4.8 비주얼

  • 메신저의 단순함은 유지하되 남의 앱처럼 보이지 않게 — 원형 아바타 열·말풍선 스타일을 그대로 베끼지 않는다. 우리 것은 폴더봇 캐릭터, PARA 범주 섹션, 폴더 색 탭, 경로 브레드크럼이다. 다크 기본, 봇마다 색 하나, 상태는 점.
  • 알파 Rondo 의 표면 토큰(검은 유리·팔레트·상태 언어)은 가져오되 종속되지 않는다 — 이 제품의 정본 디자인은 이 절이고, 토큰은 부품이다 (Q10).

5. 시스템 구성

[ Mac mini ]  Host 앱 (로그인 항목으로 자동 실행 · 창 없음 · 트레이)
  ├─ Bot Registry     폴더 규칙(folder-rules)로 스캔·감시 → 후보(파생) + 활성 목록(bots.yml) · 수명주기 · Inbox 변화 감지
  ├─ Session Engine   Claude Code: -p --stream-json 상주 워커(resume · 절전/기상 · stdio 권한)
  │                   Codex: codex app-server (JSON-RPC 상주 · thread/resume)
  ├─ Orchestrator     = 특별한 봇 하나 (cwd PARA 루트 · 전용 하네스 · 관제 MCP)
  ├─ Scheduler        cron → 세션 생성 (루틴)
  ├─ Vault MCP        vault_* · bots_* (봇·오케스트레이터가 쓰는 도구)
  ├─ File API         트리·읽기/쓰기·감시 · Dropbox content_hash 판정
  ├─ Gateway          HTTP + SSE · 기기 토큰 · 쓰기 정책 · 정적 서빙
  ├─ Push             Web Push (VAPID) — 확인 대기·완료
  └─ Auth Watchdog    claude 자격증명 3갈래 판정(로그인·로그아웃·이 문맥에선 못 읽음) → 푸시
        ▲ tailnet HTTPS (tailscale serve) — 전송은 추상화해 두고 릴레이는 이음매만
[ 어디서나 ]  Client (PWA) — 폰 3층 / 데스크톱 3열      [ Mac ]  얇은 셸 (알림·파일 대화상자·트레이)

원칙

  1. 파일이 진실. 봇의 기억·설정·산출물은 폴더 안 파일. 호스트가 죽어도 claude --resume 이 터미널에서 된다. 호스트 전용 상태(기기 토큰·푸시 구독·세션 인덱스)만 ~/Library/Application Support/projectbot/.
  2. 호스트가 유일한 주인. 볼트를 쓰고 세션을 띄우는 프로세스는 하나. 클라이언트는 파일을 직접 안 만지고 세션을 직접 안 띄운다 → 동기화 충돌이 정의상 없다.
  3. CLI 네이티브. 세션은 벤더 CLI 가 소유. Folder Bot 은 스폰·관찰·지휘만. 벤더가 발전하면 그대로 얻는다.
  4. 로컬은 원격의 특수 경우. 호스트와 클라이언트는 프로토콜 하나로만 만난다. 같은 맥에서 띄워도 경로는 같다.
  5. "아직 모른다" 상태를 둔다. 인증·동기화·락 판정은 참/거짓 + 제3값.

5.1 인증 벽 — 해결 방식

Mac mini 의 헤드리스 프로세스는 claude 자격증명(키체인)을 못 읽을 수 있다. 해결은 우회가 아니라 운영이다.

  1. 호스트는 로그인 항목으로 뜨는 GUI 앱(창 없음)이다 — 사람이 GUI 로 로그인한 세션의 자손이 되도록. 미니는 자동 로그인 + 절전 0.
  2. 호스트가 30분마다 claude auth status 를 3갈래로 판정한다. "못 읽음" 이면 폰으로 푸시: "미니에서 /login 이 필요합니다."
  3. Dave 가 Jump Desktop 으로 들어가 /login 한다. 호스트는 그 뒤 자동 복귀. (Dave 확정: 이 수동 개입은 허용.)
  4. 런북을 docs/RUNBOOK-mini.md 로 둔다 — 재부팅·CLI 갱신·키체인 ACL 사고 시 절차.
  5. ⛔ API 키(--bare)로 우회하지 않는다 — 구독이 아니라 종량 과금이 된다.

5.2 접속과 로그인 — 결정 필요 (Q2)

안 구글 로그인 폰 설치물 서버 운영 남이 쓸 때
A. Tailscale (추천) Tailscale 계정 = 구글 Tailscale 앱 없음 "Tailscale 부터 까세요" 가 문턱
B. Tailscale Funnel + 자체 구글 OAuth 직접 구현 없음(브라우저) 없음(공개 URL 은 생김) 됨. OAuth·세션 관리 책임
C. 자체 릴레이 + 구글 OAuth 직접 구현 없음 있음 제품형. 한 달 추가

A 로 시작하고 전송 계층을 추상화해 B/C 를 나중에 끼운다. Dave 전용 기간엔 A 로 충분하다.

5.3 Dropbox

  • 볼트 = 미니의 Dropbox 폴더. 오프라인 사용 가능 고정(온라인 전용 파일은 봇이 빈 껍데기를 읽는다).
  • 동기화 판정: Dropbox 공식 content_hash(4MB 블록 SHA-256) 로컬 계산 ↔ API 메타데이터 비교 → 같음/다름/모름. 문서·트리에 점으로 표시. "Dropbox 반영됨" 을 완료 알림에 덧붙일 수 있다.
  • (conflicted copy) 감지 → 봇 배지. 고빈도 상태는 볼트에 쓰지 않는다(동기화 소음).
  • 경로는 realpath + NFC 정규화.

6. 데이터

무엇 어디 형식
봇 후보 파생 (PARA 2단계 폴더 스캔) — 저장 안 함 —
활성 봇 목록 PARA/.folderbot/bots.yml YAML (경로·깨운 시각·벤더)
봇 설정·루틴 <폴더>/.bot.yml (선택) YAML
폴더 규칙 루트 CLAUDE.md/AGENTS.md 의 ## 폴더 규칙 절 (산문 + yaml folder-rules 블록) md — 사용자가 직접 편집
오케스트레이터 하네스·루틴 <루트>/.claude/orchestrator.md · <루트>/.claude/routines.yml md · YAML
세션 트랜스크립트 ~/.claude/projects/<cwd-slug>/*.jsonl · Codex 스레드 벤더 소유
봇 대화 뷰(세션 인덱스·읽음 표시) 호스트 userData JSON
기기·푸시·토큰 호스트 userData JSON (토큰은 keychain)
되돌리기 스냅샷 PARA/.folderbot/undo/ JSON

7. 마일스톤

단계 내용 검증
M0 스파이크 (1주) ① 미니: 로그인 항목 GUI 앱이 claude 를 띄워 loggedIn:true ② Tailscale serve → 폰 브라우저 접속 ③ codex app-server 왕복 ④ Web Push 가 잠긴 iPhone 에 도착 ⑤ 폴더 규칙 블록 파싱 → 후보 목록 파생(하네스 유무 판정 포함) 다섯 실측이 문서에 적힘. ①이 안 되면 §5.1 을 고친다
M1 호스트 코어 Bot Registry · Session Engine(Claude) · Gateway · 상태 머신 · 인증 워치독 픽스처 볼트 + stub CLI 로 원격 왕복 테스트 그린
M2 클라이언트 3분할 메신저(봇 목록·대화·봇의 작업) · 파일 칩 + 슬라이드 시트 · 후보 피커 · 폰 2화면 iPhone·PC 에서 같은 봇에 지시·승인·파일 열람
M3 오케스트레이터·루틴 폴더 규칙 프리셋·인터뷰 · 관제 MCP(시작·정지·폴더 생성·이동·스캐폴드) · Inbox 정리 · 스케줄러 · 승인 정책 Inbox 에 폴더를 던지면 규칙대로 이름·위치·하네스 제안이 온다 · "X 폴더에서 시작해" 한 마디로 폴더+하네스+봇 · 아침 루틴이 폰에 Inbox 제안을 보낸다
M4 문서·Dropbox·Codex 문서 뷰/편집기 이식 · content_hash · Codex app-server 실 PARA 볼트에서 일상 작업 1주
M5 셸·배포·런북 얇은 macOS 셸 · 호스트 패키징·자기 업데이트 · 미니 런북 새 미니에 30분 안에 설치
V1.0 Dave 의 일상 작업 전부가 여기서 돈다 2주 실사용 후 판정

8. 결정할 것 (Dave)

# 질문 추천 대안
Q1 이름 Folder Bot (2026-09-12 확정 — 가제 Project Bot 폐기). 리포 dave-jin/folderbot 로 개명 완료 ✅
Q2 접속·로그인 A. Tailscale + 전송 추상화 B · C (§5.2)
Q3 PARA 프리셋의 active 기본값 2. Projects/* + 3. Area/* (사용자가 규칙 파일에서 바꿈) Resources 까지
Q3b 활성 봇 상한 8 (넘기면 오케스트레이터가 재울 봇 제안) 상한 없음
Q3c "하네스 있음" 판정 CLAUDE.md 또는 .claude/ 존재 (규칙의 harness: 로 변경 가능) readme.md 만으로도 후보
Q3d 규칙 파일 루트 CLAUDE.md 의 ## 폴더 규칙 절 (산문 + yaml 블록) 별도 structure.yml 만 · AGENTS.md 우선
Q3e Inbox 정리 자율 범위 제안 → 승인 → 이동 (되돌리기 스냅샷) 명백한 경우 자동 이동
Q4 봇당 동시 세션 4 (호스트 15 · 넘치면 LRU 절전 — BE) 무제한 + 메모리 거절만
Q5 오케스트레이터 자율 범위 읽기·분류 제안 자동, 이동·삭제·발송은 승인 되돌리기 가능한 건 전부 자동
Q6 루틴 기본 승인 정책 읽기 전용 + 제안 (사람 없을 때) 봇별 "항상 허용" 규칙
Q7 벤더 Claude Code 기본, Codex 봇별 선택 Claude 만
Q8 폰 문서 편집 범위 todo 체크·짧은 노트만 편집기 전체
Q9 기획 정본 이 리포 docs/PRD.md PARA
Q10 비주얼 Grok Bot 식 메신저 단순함이 정본, 알파 토큰은 부품 알파 DESIGN.md 그대로
Q11 파일 시트 기본 폭 50%, 드래그 가능 40% · 전체
Q12 왼쪽 목록 기본 정렬 확인 대기 먼저 → 최근 활동 최근 활동만 · PARA 순
Q13 알림 기본 채널 맥 알림 센터 + 메뉴바 + 폰 푸시 켬, 메일 끔 확정(2026-10-19 Dave «계획대로»): 폰 푸시는 «확인해 주세요» 만
Q14 플로팅 HUD 메뉴바 팝오버로 흡수 (별도 창 없음) 알파처럼 always-on-top 창 유지

9. 열린 질문

  • Mac mini 현황: macOS 버전 · 자동 로그인 · Dropbox·Claude·Codex·Tailscale 설치 · 절전 설정.
  • 가장 자주 쓸 기기 하나(iPhone?). 그 경험을 먼저 완성한다.
  • 오케스트레이터가 Inbox 를 자동으로 옮겨도 되는 순간이 있나(예: 명백한 영수증→재무_CFO).
  • 첫 루틴 3개를 정해 달라 — M3 의 검증 기준이 된다.

10. 구현 현황 — v0.1.0 (2026-09-12)

설치: curl -fsSL https://raw.githubusercontent.com/dave-jin/rondo-releases/main/folderbot/install.sh | bash · 런북 docs/RUNBOOK-mini.md

시나리오 상태 비고
S1 첫 실행 ✅ folderbot init 이 규칙 절 설치·후보 스캔, 화면 온보딩(프리셋 3종), 페어링 코드. QR 은 아직 없음(주소 직접 입력)
S2 폴더에서 시작 ✅ 피커 · 새 폴더 만들기(naming 규칙·하네스 스캐폴드) · 오케스트레이터 bot_start/folder_create
S3 이동 중 승인 ✅ 승인 카드·AskUserQuestion 카드·SSE 실시간·Web Push(VAPID). ⚠ 푸시는 HTTPS(tailscale serve) + 홈 화면 설치 필요
S4 파일 ✅ 칩 → 슬라이드 시트(md 렌더·이미지·PDF) · 편집(tmp+rename) · 경로 가드. Dropbox content_hash 는 미구현
S5 Inbox ✅ 호스트가 Inbox 감시 → 패널 "정리 제안 받기" → 오케스트레이터 inbox_list/folder_move(되돌리기 스냅샷)
S6 루틴 ✅ .bot.yml cron(croner) · 승인 정책 3단(plan/acceptEdits/bypass) · 루틴 편집 시트 · 결과 푸시
S7 관제 ✅ MCP 16종 — bots_list·bot_status·bot_send(위임 세션 · 오케스트레이터 전용)·orch_ask(폴더 봇 → 오케스트레이터 · 답은 원래 세션으로 · BH)·bot_sessions·상한(봇 4·호스트 15, 넘치면 LRU 절전)
S8 끊김·복구 ✅ 30분 워치독 3갈래(loggedin/loggedout/unreadable) · 앰버 배너 · 푸시 · 대기열 → 복구 시 재전송 · SSE 오프라인 표시
S9 정지·은퇴 ✅ 정지(휴면) · Archive 이동(은퇴, 되돌리기)
S10 할 일 ✅ - [ ] 제목: 설명 <!-- bot --> · 패널 4개 · 시트 · 봇에게 맡기기 · 봇 규약을 시스템 프롬프트에 주입 · todo_add MCP
S11 목록·알림 ✅ 폭 드래그(64/240–420)·정렬·묶기·밀도·알림 센터·조용한 시간 · 맥북 셸(desktop/): 메뉴바 폴더봇 아이콘 + 대기 배지, 네이티브 알림 클릭 점프, Dock 배지, 로그인 시 자동 실행, folderbot:// 딥링크. 메뉴바 팝오버(카드에서 바로 승인)는 아직 메뉴 형태

맥북 앱: GitHub Actions(macOS 러너)가 desktop/ 변경마다 DMG(arm64, ad-hoc 서명)를 빌드해 dave-jin/folderbot Releases desktop-v<n> 에 올린다. 앱은 호스트 주소 + 페어링으로 미니에 붙는 셸이다.

v0.1.1 (desktop-v2, 2026-09-12): 앱 안에 호스트 내장 — 미니에서도 「이 맥에서 호스트 실행」으로 터미널 없이 상주(로그인 항목 → GUI 세션이라 키체인 벽 없음) · claude setup-token 장기 토큰 모드(설정 › Claude 토큰) · 인증 실패 문구 감지 → 배너·푸시 · 파일 첨부(기기 업로드 → 첨부/, 폴더에서 고르기) · 앱 자기 업데이트(공개 프리릴리스 → 조용히 받아 두고, 미니는 세션이 전부 유휴일 때 자동 적용 · 맥북은 확인창).

v0.2.0 (2026-09-12) — V3 미니멀 UI 최종 빌드: 네 열(목록·대화·문서·패널) 44px 통일 헤더 · 문서 열(탭·미리보기/고정·폴더 안 ↑↓·자동 저장·봇 수정 배너·⌘⇧D 숨기기) · 모든 경계 드래그(열 3·섹션 2) · 좁은 창은 아이콘 열로 접힘(겹침 없음) · 실제 폴더 트리(지연 로드·정렬·필터·우클릭·이름 바꾸기·봇이 쓴 파일 비추기) · 진행 줄(펄스·활동·경과·중단) · 서브에이전트(줄→펼침→드릴인) · 생각 접힘 · 도구 4연속 접힘 · TodoWrite 위젯(멈춤) · 대기열 · 질문/권한 카드 · 기본 모델 Opus 5+높음(설정, 다음 세션부터). 목업 캔버스 「V3 정리」 페이지가 정본.

v0.3.0 (2026-09-12) — 컴포저·모바일·유리·파일 가져오기 (목업 V4·V7·V8·V9 승인분): ① 컴포저 아래 한 줄 — 모드(자동·편집 자동 수락·계획·항상 허용, 1~4키) · 모델(Fable 5.1 기본 · Opus 5 · Sonnet 5 · Haiku 4.5) · 노력(5단 슬라이더) · 컨텍스트 링(result.usage, 80%↑ 주황 + /compact) — 세션 단위, 바꾸면 워커를 내리고 같은 id 로 --resume(턴 중이면 끝난 뒤) · 슬래시 자동완성(.claude/skills|commands 봇→루트→사용자 + CLI init slash_commands) · 직전 질문 고정(헤더 아래 유리, 질문이 위로 사라졌을 때만) · ↓ 최근으로 · 답변 액션 줄(복사·다시). ② 모바일 재설계 — 탭바 없이 홈(카드 4 + 봇 목록 + 알약) → 대화(둥근 뒤로·봇 알약·폴더) → 폴더 → 문서를 밀어 넣음 · 큰 글자(17/16) · 입력 16px+maximum-scale=1(자동 줌 금지) · 100dvh · 키보드 열리면 칩 줄 숨김. ③ 새 팔레트 #141414 사다리(커서 실측)와 유리 — 떠 있는 것만(헤더·고정 질문·↓·컴포저·팝업), 스크롤 컨테이너의 형제로 두고 본문에 여백, prefers-reduced-transparency 면 불투명. ④ 파일 가져오기 — @ 언급(열린 문서 우선 퍼지 검색, 폴더째) · Finder 드래그(첨부/ 로 복사) · 트리 드래그(폴더 포함) · 우클릭 «폴더째 첨부»·«@ 로 언급» · + 메뉴 «열린 문서 첨부» · 스크린샷 붙여넣기 — 어디서 오든 같은 칩 줄, 보내는 형식(절대경로 목록)은 그대로.

v0.3.1 (2026-09-12) — 자기 업데이트 실동작: 업데이터가 이 리포(공개)의 desktop-v* 릴리스를 직접 본다(버전은 zip 이름에서, desktop/update-pick.js 순수 함수 + vitest). PAT·공개 리포 단계 제거. 화면 왼쪽 아래 버전 칩 = 확인 · 받는 중 % · 「재시작해서 적용」 · 호스트 모드 「세션 N개 끝나면 적용」(IPC fb:update-*). 받는 건 묻지 않고 적용만 묻는다 · 미니는 세션이 전부 유휴일 때 자동 적용 · 세션이 도는데 누르면 «N개 중단» 확인.

v0.3.2 (2026-09-12) — 어디서든 에이전트 (목업 V10 승인분): 폴더 선택 = PARA 트리(접기·펼치기, 활성 폴더는 펼친 채 시작, 아무 깊이든, 역할 배지 활성·참조·보관·정리 대기, 하네스/봇 있음, 펼친 폴더 안 «새 폴더 만들기», 검색은 평평하게, ↑↓ → ← ⏎, 푸터 한 줄·경로 앞 말줄임) · Resources·Archive 도 시작(보관은 확인 한 줄) · createFolder 가 루트 안 어느 폴더든 · 봇 섹션 = 1단계 폴더, 깊은 봇은 이름 아래 경로 · 트리 우클릭 «여기서 에이전트 시작 / 봇 열기 / 새 폴더 만들기 → 시작»(봇 트리는 «상위 봇과 겹쳐요» 확인) · ls 가 폴더에 harness·botId·role 을 붙임 · 메인/원격: 호스트 맥 자체 창(this-mac)은 «메인», 그 외 «원격 · 기기이름». 이름은 설정 › 이름(hostName · devices[].name), 기본값 컴퓨터 이름(scutil)·페어링 기기 종류. «Mac mini» 문구 전부 변수로 · 할 일: 제목 누르면 그 자리에서 편집(⏎ 저장 ⎋ 취소), 설명, 삭제(되돌리기 5초), 완료 보기 — todo/edit·todo/delete 는 todo.md 의 그 줄만 바꾼다(editLine·deleteLine, vitest).

v0.3.3 (2026-09-12) — 폰 홈화면 앱 마감: iOS 가 키보드 뒤 레이아웃 뷰포트를 안 돌려주는 문제는 interactive-widget 을 빼고 키보드를 visualViewport(--kb)로만 다뤄 해결. 위 여백은 상태바 블러 아래로 한 뼘 더(--sat + 26px), 아래는 홈 인디케이터에 바짝. 루트 아래가 비쳐도 줄이 안 생기게 폰의 html/body 배경을 앱 그라데이션 끝(bg2)과 같게, standalone 은 #root 를 100vh 로 고정. 업데이트 확인 30분 + 창 띄울 때, updates/log.txt. DMG 에 「검역 해제 (우클릭-열기).command」.

v0.3.4 (2026-09-12) — 레일 호버 카드 · 할 일 행(V11) · 가운데 말줄임 · 생각 표시: 펼친 목록의 봇 행에 마우스를 올리면(300ms) 오른쪽에 카드 — 이름·경로·상태·세션(모델·컨텍스트 %·절전)·할 일/루틴 수·마지막 말·시간(HoverCard, title 툴팁 대체). 할 일 행은 글이 폭을 다 쓰고 도구는 호버 때 글 위 오른쪽(폰은 항상), 긴 행은 2줄에서 «…더»/«접기» 행별 펼침(scrollHeight 측정). 폴더·파일 이름은 Finder 식 가운데 말줄임(Mid — 앞은 줄이고 꼬리 8자는 남김: 레일·트리·폴더 선택·폰 목록). 생각(thinking) 스트림을 길이 나머지로 던지던 버그(큰 청크면 한 번도 안 보임) → 150ms 시간 기준, 최종 메시지의 thinking 블록으로 빈 본문 보충, 비어 있으면 «생각 중…». DMG 의 .command 는 「처음 설치 후 읽어주세요.txt」(터미널 한 줄 복사) 로 교체.

v0.3.5 (2026-09-12) — 권한 관문 (알파 Rondo r408·r412·r423 승계): 데스크톱 첫 실행·업데이트 뒤 필수 권한이 빠지면 화면 전체 관문 — 전체 디스크 접근(호스트 맥만 필수 · 보호 폴더를 실제로 열어 보는 프로브 여러 개, ENOENT 는 «모름» 이지 «없음» 이 아님) · 알림(모든 맥 필수 · Electron 이 상태를 못 주므로 테스트 한 발 → «보였어요» 대답을 perm-ack.json 에 기억, 재서명에도 유지). 판정은 desktop/perm-core.js 순수 함수(vitest), 재기·설정 창·대답은 desktop/perms.js, 화면은 client/Perms.tsx. 창 포커스마다 다시 재서 밀어 준다(«돌아오세요» 로 끝남). 개발 실행에선 막지 않는다. 설정 › macOS 권한 › [권한 다시 확인]. 버전 칩 클릭 → 확인 결과 토스트. 스모크: 화면 검사 실패를 «건너뜀» 으로 삼키던 것을 실패로(브라우저 없음만 건너뜀).

v0.3.6 (2026-09-12) — 알림이 안 오던 원인: 번들이 서명되지 않았다: electron-builder 의 identity: null 은 «서명 생략» 이지 ad-hoc 이 아니다 — v17 까지 zip 안 번들에 _CodeSignature 가 0개였고(실측), macOS 알림 센터는 서명 없는 앱을 등록하지 않아 테스트 알림도 실 알림도 조용히 버려졌다. desktop/afterPack.js 가 dmg/zip 을 만들기 전에 codesign --force --deep --sign - 로 ad-hoc 서명하고 검증한다(알파 Rondo scripts/dmg.sh 승계). CI 는 zip 안에 _CodeSignature/CodeResources 가 있는지 확인하고 없으면 실패한다. ⚠ 서명이 생기면 cdhash 가 바뀌어 전체 디스크 접근이 풀린다 — 권한 관문이 그걸 받는다.

v0.3.7 (2026-09-13) — 폰 키보드: 루트 = 시각 뷰포트: v14 의 «standalone 100vh 고정» 이 키보드 아래에 컴포저를 묻었다(Dave 스크린샷: 입력칸이 안 보임). useKeyboard 가 visualViewport 높이·offsetTop 을 --vvh·--vvt 로 쓰고 폰 CSS 의 #root 가 그걸 높이로 쓴다 — 키보드가 뜨면 루트가 그만큼 줄어 컴포저는 늘 키보드 바로 위, 닫히면 돌아온다(하단 띠도 없음). 열림 판정은 «입력칸 포커스 + 최대 높이보다 140px 이상 축소». 스크롤 컨테이너가 줄면 맨 아래를 유지(ResizeObserver). 스모크가 가짜 visualViewport 로 키보드를 흉내 내 루트 높이·컴포저 위치·헤더 숨김·마지막 말 노출·복원을 잰다(test/tmp/phone-kb.png).

v0.3.8 (2026-09-13) — 생각 내용은 CLI 가 주지 않는다 (실측): claude -p --output-format stream-json --include-partial-messages (2.1.269) 를 Opus 5·Sonnet 5·Haiku 4.5 로 직접 돌려 보니 thinking 블록·delta 는 오지만 thinking 은 전부 빈 문자열, 서명만 있다. headless 출력은 생각 내용을 주지 않는다 — 모델 문제가 아니라 CLI 의 동작. 그래서 빈 «생각 · (내용 없음)» 행을 도구마다 남기던 것을 없앴다: 생각 항목은 본문이 생겼을 때만 채팅에 들어가고(thinkShown), 빈 채로 끝나면 조용히 버린다(endThinking). 생각 중임은 상태줄 «생각 중» 으로만 보인다. CLI 가 내용을 주기 시작하면 코드 수정 없이 다시 보인다.

v0.3.9 (2026-09-13) — 원격 맥 앱에서 이름 바꾸기·새 폴더가 안 되던 원인: window.prompt() 를 썼는데 Electron 은 prompt() 를 지원하지 않아 아무 반응 없이 끝났다. 앱 안 이름 모달(askName·AskHost, 확장자 앞까지 선택)로 교체 — 맥 앱·브라우저·폰이 같은 경험. 스모크가 트리 우클릭 › 이름 바꾸기 → 모달 → ⏎ → 트리 반영을 확인한다. (교훈: 브라우저 전용 동기 대화상자 prompt는 셸에서 금지 — confirm 도 장기적으로 같은 길.)

v0.3.10 (2026-09-13) — 폰 상·하단 여백을 elon-bookclub 방식으로: 상태바를 시스템에 맡긴다(apple-mobile-web-app-status-bar-style: default — 불투명, theme-color 색). 웹뷰가 상태바 아래에서 시작하므로 위쪽 안전영역·블러를 앱이 계산할 일이 없고, 헤더는 보통 여백(위 1012px, 높이 ≈ 64px)만 둔다. 아래는 env(safe-area-inset-bottom) + 46px 로 홈 인디케이터에 바짝(elon 의 pb-[env(safe-area-inset-bottom)]). 핀치·더블탭·ctrl+휠 확대는 useViewportLock 으로 2중 차단(elon ViewportLock). --sat 는 식에 남겨 두어 투명 모드로 되돌려도 계산이 맞다.

v0.3.11 (2026-09-13) — 열 최소 폭 · NFD 파일명: 대화 열이 ~100px 로 눌려 글이 한 글자씩 접히던 것(Dave 스크린샷) → CHAT_MIN 360px. 저장된 열 폭(lay)은 그대로 두고 그리는 값(fit)만 창에 맞춰 줄인다 — 부족분은 문서(≥380) → 오른쪽 패널(≥200) → 왼쪽 목록(≥200) 순으로 양보. 드래그는 대화 열이 내줄 수 있는 만큼(slack)에서 멈춘다. CSS min-width 가 마지막 방어선. 오른쪽 패널의 세션·할 일 높이는 파일 트리 160px 을 남기는 선까지만. 가운데 말줄임 Mid 는 NFC 로 합친 뒤 코드포인트로 자른다 — macOS 의 NFD 파일명이 «전ㅊ ㅔ구조» 로 갈라지던 것.

v0.3.12 (2026-09-13) — 레일 순서 고정: 활동·상태로 행이 오르내리던 것을 없앴다. 섹션은 관제 → PARA 번호 오름차순, 행은 이름 내림차순(날짜 접두 폴더가 최신부터). 확인 대기·실행 중은 점·배지로만 표시.

v0.3.13 (2026-09-13) — 폰 아래 빈 띠(~100pt): iOS 26 이 키보드·입력 보조 막대를 내린 뒤 시각 뷰포트를 덜 돌려줘 루트(--vvh)가 짧게 남았다. 입력 중이 아니면 키보드가 있을 수 없으므로 그때는 시각 뷰포트 대신 최대 높이(maxH = vv·innerHeight·clientHeight 의 최댓값)를 쓴다. 포커스가 빠진 뒤 50·300·700ms 에 다시 잰다. 스모크: 입력 중 아닐 때 vv 가 60px 짧아도 루트는 전체 높이.

v0.3.14 (2026-09-13) — 폰 실행 줄 · «실행 중» 고착: ① 폰에서 «생각» 라벨이 세로로 서던 것 — CJK 는 글자마다 끊길 수 있어 flex 아이템의 min-content 가 한 글자였다 → 라벨 nowrap; flex:none. 실행 줄(생각·도구·서브에이전트·상태줄·도구 묶음)은 13px, 본문 16px 유지. 폰에선 말(실행 중·끝남·열기·중단·도구 n회) 대신 아이콘(.w 숨김 / .ic 표시). ② 호스트 재시작(자동 업데이트 포함)·워커 종료 뒤 스트리밍 답·결과 없는 도구·run 서브에이전트가 영원히 스피너로 남던 것 → core/chat.closeOpenItems(items, reason) 를 부팅 복원·worker exit·result 에서 호출(vitest). 중단 사유(«호스트가 다시 떠서 중단됨» 등)를 항목에 남긴다.

v0.3.15 (2026-09-13) — 백그라운드 서브에이전트 생애: CLI 2.1.269 를 직접 돌려 잰 순서 — Agent{run_in_background} → system/task_started{tool_use_id,is_backgrounded} → 즉시 tool_result «Async agent launched…» → result(턴 끝) → 부모 id 단 줄들 → system/task_notification{tool_use_id,status,summary} → CLI 가 스스로 새 턴(init→답→result). 종전엔 즉시 tool_result 로 «끝남» 을 찍어 돌고 있는데 끝난 듯 보였다. 이제 bg 표시 → 턴이 끝나도 «실행 중», 알림이 오면 끝남+요약. 세션 정보 bg(도는 수) — 유휴 회수와 데스크톱 업데이트 적용은 bg=0 일 때만. 스텁이 같은 순서를 흉내 내고 스모크가 launch→running→notification→done→후속 턴을 검사한다. 호스트가 재시작되면(자동 업데이트 포함) 그 에이전트는 사라지므로 «호스트가 다시 떠서 중단됨» 으로 마감된다 — 이게 Dave 스크린샷의 원인(업데이트 재시작 뒤 «실행 중 · 도구 42회» 고착).

v0.3.16 (2026-09-13) — 진행 애니메이션 1번 · 표정 C · 자잘한 3건: (Dave 선택) 상태줄의 점을 8px 로 키우고 물결(ripple), 활동 문구는 시머. 폴더봇은 눈·입·표식 그룹에 CSS 키프레임(깜빡임 4.2s · 시선+타이핑 · 눈썹+! · z · 미소 2회 · 흔들림) + 모서리 상태 배지(일하는 중 숨쉬기 · 확인 튕김). 레일·스트립·폰 목록의 별도 점은 배지가 대신. prefers-reduced-motion 이면 전부 정지, ≤20px 는 입 애니메이션 생략. 자잘한 것: 신호등을 헤더 44px 중앙에(trafficLightPosition), 업데이트 칩 문구 축약+말줄임, 데스크톱 대화 헤더 불투명 + 아래 28px 페이드 띠(글이 헤더에 걸치지 않음).

v0.4.0 (2026-09-13) — 할 일 2.0 (목업 V15 승인분): 패널이 todo.md 의 거울이 됐다. ① 문서의 ## 제목(절)이 그대로 섹션 — parseTodo 가 각 줄에 section 을 붙인다(맨 위 «# todo» 는 문서 제목이라 절이 아니다). «완료» 절은 접힌 채 시작. ② 목록은 제목 한 줄만, 설명은 더블클릭(폰은 탭)으로 그 행을 펼칠 때만 — 설명이 있는 행에만 › 표식. ③ 편집은 펼친 자리에서 한 칸에 «제목: 설명»(첫 «:» 앞이 제목 — 파일 형식 그대로). ④ 체크하면 «완료» 절 끝으로 내려간다(toggleAndMove), 풀면 제자리. ⑤ 끌어서 순서·절 이동(moveLine + POST todo/move) — 그 줄만 옮기고 절 제목·빈 줄은 보존. ⑥ 새 할 일은 각 절 끝에 인라인(addLine(…, section)). ⑦ 헤더의 「todo.md」 로 원문을 열어 자유롭게 고칠 수 있다. 🔴 교훈 둘: 한글에는 정규식 \b 가 성립하지 않아 «완료» 절 판정이 영원히 거짓이었다(스모크로 잡음) · 새 버튼에 .doc 클래스를 붙였다가 «문서 열이 열렸나» 검사가 그 버튼을 잡았다 — 화면 검사가 쓰는 클래스 이름은 고유해야 한다.

v0.4.1 (2026-09-13) — 라이트 테마 · 폰 상단 경계선: ① 폰에서 상태바 아래(루트 배경 bg2)와 헤더(bg)의 색이 갈려 가로선처럼 보였다 → 폰 배경을 한 색(--bg)으로 통일하고 .app 의 세로 그라디언트를 없앴다. ② 라이트 테마 — 색은 전부 토큰이라 :root[data-theme="light"] 한 벌로 갈린다. 하드코딩돼 있던 페이드·단색 유리·그림자도 토큰으로 뽑았다(--fade0/--fadeA/--fadeB/--solid/--hover0/--shadow/--onAccent). 「시스템·라이트·다크」는 설정 › 화면에서 고르고 fb:theme 에 저장한다. «시스템» 은 JS 가 matchMedia 로 지금 값을 정해 data-theme 에 넣는다(같은 팔레트를 CSS 에 두 벌 두지 않으려고) — 시스템 설정이 바뀌면 즉시 따라간다. theme-color(상태바 색)도 함께 바뀐다. ⚠ 헤드리스 크로뮴은 prefers-color-scheme: light 라 QA 화면은 fb:theme=dark 로 고정해 찍는다.

v0.4.2 (2026-09-13) — 폰 «쓸어서 처리» (목업 V16 승인분): 폰 할 일 행에 붙어 있던 도구 아이콘(연필·맡기기·삭제)을 없애고 좌·우 × 짧게·길게 네 동작으로 바꿨다. 기본값은 → 짧게 편집 · → 길게 완료 · ← 짧게 메뉴(시트) · ← 길게 삭제. 임계는 행 너비의 25%(짧게)·45%(길게)이고, 끄는 동안 뒤 배경이 그 동작 색으로 차고 아이콘이 따라오며 길게 구간에 들어가면 원이 채워지고 짧게 진동한다 — 손을 떼는 순간의 예고가 실행된다. 판정은 client/swipe.ts 의 순수 함수(slotOf·actOf, vitest 4건)에만 있고 화면은 결과만 그린다. 설정 › 할 일에서 네 자리에 편집·완료·메뉴·삭제·맡기기·펼치기·없음 중 무엇을 놓을지 고르고 진동을 끌 수 있다(fb:swipe). 세로 움직임이 먼저면 스와이프로 보지 않아 목록 스크롤이 걸리지 않는다. 데스크톱은 그대로 호버 도구. ⚠ setPointerCapture 는 합성 이벤트에서 던질 수 있어 try/catch — 제스처가 거기서 멈추면 안 된다.

v0.4.3 (2026-09-13) — 입력창이 화면 밖으로 나가던 것: v0.3.13 이 «키보드 뒤 시각 뷰포트를 덜 돌려주는» 문제를 풀려고 본 적 있는 최대 높이를 기억해 루트 높이로 썼는데, 한 번이라도 크게 잡히면(주소창 변화·회전·부분 스크롤) 루트가 화면보다 커져 컴포저가 아래로 밀려났다(Dave 스크린샷). 최대값은 지어낸 숫자다 — 닫힌 상태의 정답은 브라우저가 아는 100dvh 이고, 열린 상태의 정답만 시각 뷰포트다. 이제 --vvh 는 키보드가 열렸을 때만 설정하고 닫히면 지운다(→ 100dvh 로 복귀). 그리고 #root { max-height:100dvh } 가 마지막 방어선이라 어떤 계산이 틀려도 입력창이 화면을 넘지 않는다. 스모크가 시각 뷰포트를 실제보다 크게 보고하는 상황까지 흉내 내 컴포저가 화면 안에 남는지 검사한다.

v0.4.4 (2026-09-13) — 폴더 찾기가 «한글을 아예 못 찾던» 것: Dave 보고 — "폴더 이름의 이니셜부터만 검색이 되네요 … '트레바리' 같은 경우에는 아예 검색이 안 돼요." 원인은 둘이고 둘 다 검색을 통째로 죽인다. ① NFD: 맥은 파일 이름을 자모가 분리된 NFD(ㅌ+ㅡ+…)로 저장하는데 입력기가 만드는 질의는 NFC(트)다 — 코드포인트가 달라 includes 가 무조건 실패한다. 그래서 날짜·영문 같은 ASCII 조각만 걸렸고 그게 «앞머리만 된다» 로 보였다. ② 색인이 끊겨 있었다: 피커가 files?depth=4 를 색인으로 썼는데 그 함수(tree)는 파일까지 세며 400개에서 끊는다 — 볼트 앞쪽 400개만 색인되어 뒤쪽·깊은 폴더는 애초에 목록에 없었다. 처방: 판정을 core/search.ts 한 곳으로 모으고(비교 전 양쪽 NFC, 가운데 일치 60점 · 앞머리 80 · 전체 100 · 경로 40 · 초성 30(ㅌㄹㅂㄹ→트레바리) · 띄엄띄엄 10), 색인은 폴더만 담는 allDirs(깊이 6 · 상한 4000)로 바꿨다. 걸린 자리는 브랜드 코랄로 굵게 칠하고 결과 행은 «이름 먼저, 경로는 아랫줄 흐리게» 로 바꿨다. 같은 NFD 버그가 있던 @ 파일 붙이기 · 문서 열 찾기 · 컨텍스트 패널 거르기도 같은 함수로 옮겼다. 검사: vitest 10건(NFD 재현 포함 — 옛 색인이 실제로 못 닿았음을 단언한다) + 스모크가 픽스처 볼트에 NFD 이름의 깊은 폴더를 두고 «트레바리»·«ㅌㄹㅂㄹ» 로 찾아 강조 색까지 잰다. ⚠ 정규화는 비교에만 쓴다 — 파일에 손대는 경로는 원문 그대로여야 ENOENT 가 안 난다(host/files.ts 의 guard() 머리말).

v0.4.5 (2026-09-13) — 키보드가 «다시» 올라올 때 타이핑 자리가 안 잡히던 것: Dave 스크린샷 — 키보드 위에 컴포저 대신 iOS 의 입력 보조 막대와 잘린 대화가 있다. 원인은 열림 판정식 하나다. 종전에는 키보드가 먹은 높이를 innerHeight − vv.height − vv.offsetTop 으로 쟀는데, iOS 는 포커스된 칸을 보이게 하려고 시각 뷰포트를 아래로 민다 — 그때 offsetTop 이 커지면서 이 값이 문턱(140) 아래로 떨어지고, 앱이 «키보드가 닫혔다» 고 판정해 --vvh 를 지웠다. 루트가 100dvh 로 되돌아가니 컴포저가 키보드 밑에 묻힌다. 키보드가 먹은 높이는 레이아웃 뷰포트와 시각 뷰포트의 차이 하나이고, 밀린 만큼은 이미 --vvt 가 갚고 있다 — 그래서 offsetTop 을 빼는 항을 없앴다. 함께 ① 뷰포트 메타에 interactive-widget=resizes-content 를 넣어 지원하는 판에서는 브라우저가 레이아웃 뷰포트를 줄이게 했고(그러면 100dvh 가 곧 정답이라 JS 가 할 일이 없다), ② 입력칸에 포커스가 들어오면 대화를 맨 아래로 붙인다 — 읽으려고 위로 올려 둔 채 답을 쓰면 무엇에 답하는지가 안 보였다. 스모크가 visualViewport.offsetTop 을 300px 밀어 놓고 루트·컴포저가 보이는 띠 안에 남는지 재고(옛 식으로 되돌리면 실제로 빨개진다 — 확인함), 포커스 뒤 대화가 바닥에 붙는지도 잰다.

v0.4.6 (2026-09-13) — 레일 폴더봇 크기 · 마우스 오버 확대: Dave — "좌측 폴더 아이콘이 크게 보기 작게 보기 옵션이 있으면 좋겠어. 현재는 너무 작게 보여서 귀여운 폴더 표정이 잘 안 보여." + "마우스 오버했을 때는 크게 보이면 더 좋아." 표정은 이 제품의 상태 표시인데 16px 에서는 눈·입이 안 보인다. 설정 › 화면에 작게(16) · 보통(24) · 크게(32) 를 두고 기본값을 보통으로 올렸다(fb:icon, 이 기기에만 남는다). 고른 값은 CSS 변수 --fbi 로도 나가 줄 높이가 아이콘을 따라간다. 마우스를 올리면 transform:scale(1.45) 로 한 번 더 커진다 — transform 이라 줄이 밀리지 않는다(레이아웃은 그대로, 그림만). 호버 상세 카드의 폴더봇도 18 → 28px. 움직임을 줄이는 설정에서는 전환 없이 1.2배만. 스모크가 «크게» 를 눌러 아이콘·줄 높이가 함께 커지는지, 호버에 1.2배 이상 커지는지 잰다.

v0.4.7 (2026-09-13) — 세션 삭제: Dave — "세션에 세션 삭제 기능이 없네." 호스트에는 DELETE /sessions/:id 가 이미 있었고(워커를 내리고 기록을 지운다) 화면에만 길이 없었다. 세션 행에 ✕ 를 달았다 — 데스크톱은 마우스를 올렸을 때(또는 선택된 행에), 폰은 호버가 없으니 늘 보인다. 되돌릴 수 없으니 한 번 묻고, 돌고 있는 턴이 있으면 "지금 돌고 있는 턴이 중단돼요" 를 함께 말한다. 지운 게 보고 있던 세션이면 남은 첫 세션으로 옮긴다. 🔴 붙이는 김에 되살아나던 버그를 잡았다 — remove() 는 파일에 «지웠다» 표식을 남기는데 부팅 때 그 표식을 아무도 안 읽어서, 호스트를 다시 띄우면 지운 세션이 목록에 그대로 돌아왔다(스모크가 실제로 재현했다). 이제 생성자가 deleted 를 건너뛴다. 검사: 스모크가 세션을 만들어 지우고 → 목록·기록에서 사라지는지 → 호스트를 죽였다 다시 띄워도 안 돌아오는지 재고, 화면에서는 «취소하면 안 지워지고 확인하면 지워지며 보던 세션은 남는지» 를 잰다.

v0.4.8 (2026-09-13) — 키보드 3차: 문턱을 버렸다 · interactive-widget 되돌림: Dave — "여전히 모바일 채팅 영역이 바로 키보드 위로 올라오지 않아." 이번 원인은 한 라운드 전에 내가 넣은 뷰포트 메타다. interactive-widget=resizes-content 를 넣으면 iOS 26 이 레이아웃 뷰포트까지 줄이는데, 그러면 innerHeight − vv.height 가 0 이 되어 «키보드가 140px 이상 먹었나» 라는 문턱이 «닫혔다» 로 떨어진다. 그 길로 가면 루트가 100dvh 로 돌아가는데 그 dvh 는 줄어든 만큼을 반영하지 않아 루트가 화면보다 커지고 대화·입력창이 키보드 위로 안 올라온다. 교훈은 두 가지다. ① 키보드 높이를 정하는 장치는 하나여야 한다 — 메타와 JS 가 서로 다른 답을 내는 순간 이 버그가 난다. 메타는 되돌렸고, index.html 에 «다시 넣지 마라» 를 남겼다. ② 문턱으로 «열렸나» 를 판정하지 않는다 — 세 라운드 연속 이 문턱이 틀렸다(최대 높이 기억 → offsetTop 빼기 → 레이아웃 축소). 정답은 «입력 중이냐» 하나다. 입력 중이면 보이는 영역이 곧 시각 뷰포트이고, 키보드가 닫혀 있었다면 vv.height 가 화면 높이와 같아 100dvh 와 같은 값이 되니 해가 없다. 문턱은 헤더 숨김 같은 화장에만 남겼다. 스모크에 «레이아웃 뷰포트까지 함께 줄어드는 판»(__kbBoth)을 추가했다 — 옛 문턱으로 되돌리면 루트 844 · 보이는 영역 508 로 실제로 빨개진다(확인함).

v0.5.0 (2026-09-13) — 폰 할 일 다시 짓기 · 폰 폴더 고르기 · 사용량 · 에이전트 제공자: Dave 의 세 가지 불만(«끌어서 순서 변경이 안 돼 · 디자인이 예쁘지가 않아 · 우측 여백이 너무 많아»)과 이어진 요구를 한 라운드에 처리했다. ① 폰 할 일 — 동그란 체크 22px, 제목이 남은 폭을 전부 쓰고 두 줄까지, ↳ 설명 한 줄, 오른쪽 꺾쇠 제거(그 자리가 여백의 원인이었다), 태그·칩 없음(Dave), 줄 높이 5262px. 탭하면 펼쳐져 상세와 할 일거리가 나오고, 0.35초 길게 눌러 끌어 옮긴다. 편집은 아래에서 올라오는 시트 하나(제목·상세·절·삭제)이고 «새 할 일» 도 같은 시트다. 쓸기·집기·스크롤 판정은 client/gesture.ts 순수 함수 한 곳(유닛 5건). ② 폰 폴더 고르기(V17 B안) — 한 단계씩 들어간다. 이름이 폭을 전부 쓰고 상태는 아랫줄, 시각 표시를 빼고, 푸터는 두 줄(경로 + 48px 버튼), 취소는 헤더 ✕. ③ 사용량 — 실측 결과 요금제 한도는 로컬에 없어 «내 예산 − 쓴 양» 으로 그린다. Stop 훅이 쌓는 `/.folderbot/usage.jsonl과 기록 직접 훑기를 합쳐GET /api/usage하나로 내주고, 메뉴 막대·앱·폰이 그 하나를 읽는다. **뺄셈은 호스트에서 한 번만**. 기록이 없는 도구는 줄 자체를 안 그린다(Dave). ④ **에이전트 제공자** — 실행 파일이 실제로 있는 CLI 만GET /api/agents로 내준다. 🔴 이 라운드가 잡은 **조용한 버그 셋**: 같은 이벤트를 두 핸들러가 받아 **쓸기가 통째로 죽은 것**(JSX 스프레드 순서),position:fixed 인 시트가 **키보드 밑에 깔린 것**(--kbh신설), 그리고 **클래스 이름.sub` 겹침**으로 ↳ 와 설명이 두 줄로 갈린 것 — 셋 다 스모크가 잡았고 각각 회귀 검사를 남겼다.

미구현·다음: Codex(codex app-server) · 메뉴바 팝오버(카드에서 바로 승인) · Dropbox 동기화 점 · QR 페어링 · Johnny.Decimal 실제 스캔 규칙 · 규칙 인터뷰(custom) · 개발자 서명·공증.

11. 사실 확인 (2026-09-12)

  • Grok Bot — xAI, 2026-08 베타. 봇당 영속 클라우드 VM(브라우저·파일·터미널·커넥터·MCP). 데스크톱·iOS·Android. 루틴(스케줄)·다중 봇 협업. 승인: 한 번 허용/항상/거부 + Auto Review 규칙. 메모리 열람·내보내기 불가, 자체 호스팅 불가. $200/월 또는 SuperGrok Heavy·Cursor Ultra 포함.
  • Claude Code Remote Control — GA 2026-08. claude remote-control 서버 모드(동시 32, 4시간 내 복귀). 로컬 프로세스 종료 시 오프라인.
  • Codex — Codex Remote(ChatGPT 앱, GA 2026-06, 호스트 로그인 상태 필요, API 없음) · codex app-server JSON-RPC(stdio/WebSocket, thread/resume).
  • Claude Agent SDK — 세션 파일 ~/.claude/projects/<encoded-cwd>/*.jsonl, resume/fork, listSessions().
  • Tailscale Serve — tailnet HTTPS, identity 헤더, 구글 로그인, iOS/Android 앱.
  • Dropbox — 동기화 상태 공식 API 없음. content_hash 알고리즘 공개.
  • Mac mini 헤드리스 인증 — SSH·launchd·osascript 에서 claude 가 키체인을 못 읽는 실측 있음(2026-09-06). 사람이 GUI 로 띄운 셸 + /login 은 됨.

부록 A. Rondo 알파에서 가져올 부품 (이력 아님 — 부품 목록)

부품 알파 위치 규모 용도
게이트웨이 main/RemoteGateway.ts 685줄 tailnet 바인드·토큰·SSE·페어링·정적 서빙 → Gateway
Tailscale 연동 main/Tailnet.ts 98줄 상태·up
세션 워커 main/ClaudeSession.ts · 절전/기상 · core/sessionOwnership.ts · core/idleReclaim.ts · main/claudeProjects.ts(NFC/NFD) L Session Engine
Codex 어댑터 main/CodexSession.ts 272줄 이벤트 변환 참고 (app-server 로 교체)
원격 계약 core/remoteContract.ts 243줄 프레임·쓰기 정책의 출발점
채팅 동기화 core/chatSync.ts · composerSync.ts S 두 화면 동시 열람
문서 편집기 renderer CM6 라이브 프리뷰 + churn 0 하네스 L 데스크톱 문서 편집
폰 3층 레이아웃 renderer CSS (JS 0) S 폰 화면
디자인 시스템 DESIGN.md — 표면·팔레트·상태 언어
볼트 MCP·하네스 스캔 main 내장 MCP · .claude 정적 스캔 M Vault MCP · 봇 정체성 표시
알림·트레이·HUD main M macOS 셸
QA 관문 scripts/qa.mjs · 픽스처 볼트 · stub CLI · 화면 검사 L 그대로
배포 publish-update.sh · 자기 업데이트 · 테스트 갈래 M 호스트 갈래 추가

변경 이력

날짜 버전 내용
2026-09-12 v1.0 구현 시작 — v0.1.0 테스트 빌드 · §10 구현 현황 신설 · v0.1.1 호스트 내장 앱·토큰·첨부 · v0.2.0 V3 미니멀 UI
2026-09-12 v0.9 Dave 지시 — 왼쪽 목록 폭 조절·정렬·묶기·밀도(§4.1) · §4.5 알림(알파 승계: 알림 센터·배너 승인·폰 푸시·앱 안 알림 센터·조용한 시간) · §4.6 메뉴바(폴더봇 표정 아이콘·팝오버에서 바로 승인·HUD 흡수) · Q12~Q14 · 목업 S11
2026-09-12 v0.8 Dave 지시 — §3.6 todo.md 신설: 사람과 봇이 같이 쓰는 심플 목록, 한 줄 = 제목: 설명, 봇 표식, 봇 규약 4가지, 표면(패널·시트·카드) · 목업 S10 추가
2026-09-12 v0.7 Dave 지시 — §3.5 폴더 규칙 신설: 규칙은 루트 CLAUDE.md/AGENTS.md 의 절(산문 + yaml 블록), PARA 는 프리셋, 사용자가 수정·인터뷰로 커스텀 · Inbox = 정리 상자(폴더·파일을 넣으면 오케스트레이터가 규칙대로 배정 제안) · 후보 판정을 규칙 기반으로 · Q3d·Q3e 추가
2026-09-12 v0.6 Dave 피드백 — 폴더봇 캐릭터(§4.0) · "깨우기" 폐기 → "에이전트와 함께 일할 폴더를 선택하세요"/"이 폴더에서 시작"/"정지" · 피커에서 새 폴더 만들기 로 직접 생성·시작 · 왼쪽 목록을 PARA 범주 섹션으로 · 목업 v2 링크
2026-09-12 v0.5 §4 전면 교체 — Grok Bot 식 3분할 메신저 · 파일은 칩 + 슬라이드 시트(IDE 아님) · 봇 깨우기 두 갈래(PARA 트리 피커 / 오케스트레이터) · Q10·Q11 추가
2026-09-12 v0.4 Dave 정정 반영 — 봇은 자동 생성이 아니라 필요할 때 깨운다(후보=PARA 2단계 폴더·활성 상한) · 오케스트레이터가 폴더 생성·하네스 스캐폴드·깨우기/재우기·호출 관리를 맡음 · Q3b·Q3c 추가
2026-09-12 v0.3 새 프로덕트로 다시 씀 — Project Bot · 폴더=봇 · 오케스트레이터 · 루틴 · Grok Bot 대조 · 인증 벽은 운영(Jump Desktop /login)으로 해결 · 알파 이력 제거, 부품 목록만 부록으로
2026-09-12 v0.2 (폐기) 알파 연장선 관점의 초안
2026-09-12 v0.1 (폐기) 첫 초안

v0.5.1 (2026-09-13) — 에이전트 고르기(V24) · 설정 다시 짓기(V25) · 맥 사용량은 메뉴바로: 승인된 두 목업을 구현했다. ① 에이전트 고르기 — 폴더를 시작할 때 «누구와 일할까요» 를 먼저 묻고, 그 아래에 어떤 환경으로 뜨는지(폴더 · 지침 · 모델 · 권한)를 함께 보여 준다. 회사 표식으로 가르고, ⛔ 안 깔린 에이전트는 칸 자체가 안 나오며(호스트가 실행 파일을 확인한다) 하나뿐이면 화면을 아예 건너뛴다 — 고를 게 없는데 묻는 건 문턱만 하나 더 만드는 것이다. 고른 값은 봇의 vendor 로 남고 «시작할 때 고른 것» 이 .bot.yml 보다 이긴다. ② 설정 — 한 장에 여덟 덩어리가 쌓여 있던 것을 왼쪽 목차 아홉 칸(일반 · 호스트·연결 · 에이전트 · 하네스 · 사용량 · 화면 · 할 일 · 알림 · 권한·보안)으로 갈랐다. 가르는 기준은 «언제 만지나» 이고, 줄은 늘 제목 · 설명 · 조작 세 칸이라 설명이 길어져도 조작 자리가 안 흔들린다(스모크가 잰다). 검색은 제목과 설명을 함께 찾고, 되돌리기 어려운 것은 칸의 맨 아래에 빨간 글씨로. 폰에서는 목차가 목록이 되고 누르면 그 칸만 한 화면으로 들어간다. ③ 커넥터 · 스킬 · 하네스 — Dave 의 질문(«폴더별 하네스는 어떻게 보여져야 할지 고민») 에 두 층으로 답했다. 모두가 함께 쓰는 것은 설정 › 에이전트(범위 칩: 사용자 · 볼트 · 폴더 · 내장), 폴더마다 다른 것은 그 폴더 패널의 «지침 · 하네스» 칸. 설정의 하네스 칸은 한눈에 훑는 보기 전용 표다 — 하네스는 폴더에 딸린 것이라 설정에 목록으로만 두면 «지금 어느 폴더 이야기인가» 가 사라진다. 🔴 «붙어 있다» 는 설정에 있다는 뜻뿐이라 실제 연결을 확인하기 전에는 회색 점으로 둔다. ④ 맥의 사용량은 메뉴바에서만(Dave: "맥에서는 그냥 메뉴바 안에서만 이게 보이면 좋겠어") — 앱 상태바의 칩과 팝오버를 걷었다. 폰은 첫 화면 맨 위 한 줄 띠 하나이고 누르면 카드가 시트로 올라온다. 두 표면에 다 두면 같은 숫자가 두 번 보이고 왼쪽 아래가 또 비좁아진다. ⚠ 이 라운드에도 클래스 이름 겹침이 하나 있었다 — 새 설정 줄을 .srow 로 지으려다 그게 이미 패널의 세션 줄이라 .setr 로 바꿨다(r508 의 .sub 와 같은 사고를 쓰기 전에 잡은 것).

v0.5.2 (2026-09-13) — 시작한 뒤에도 누구인지 보인다 · Codex 로 진짜 띄운다: V24 목업의 마지막 칸(«시작한 뒤 — 누구인지 늘 보인다»)과 Codex 연동을 붙였다. ① 회사 표식 — 레일 · 폰 목록 · 대화 머리 · 호버 카드의 폴더봇 오른쪽 아래에 Claude/Codex 마크가 붙는다. ⛔ 깔린 제공자가 하나면 배지를 아예 안 그린다 — 모든 봇에 같은 배지가 붙으면 아무것도 안 알려 준다(«Codex가 없으면 아예 안보여야 해» 와 같은 원칙). ② 같은 폴더에 형제로 — 폴더 하나에 에이전트 하나가 원칙이지만 Claude 와 Codex 는 읽는 지침(CLAUDE.md · AGENTS.md)이 달라 나란히 둘 수 있다. 그래서 «이미 있나» 판정을 rel 에서 rel + vendor 로 바꿨고, 형제가 있을 때만 이름 뒤에 «· Codex» 가 붙는다. ③ Codex 워커 — codex exec --json 의 줄을 host/codex.ts 에서 Claude 의 stream-json 모양으로 옮긴다. 옮기는 층이 하나라 화면·저장·알림은 어느 CLI 인지 모른다. Claude 와 다른 두 가지를 계약으로 적어 뒀다: 턴마다 새 프로세스(codex exec 는 상주하지 않는다 — 두 번째 턴부터 resume <세션 id>)이고, stdio 승인 프로토콜이 없다(우리가 못 묻는 것이라 기본을 --sandbox read-only 로 둔다). 🔴 모르는 줄은 버리지 않는다 — Codex 의 이벤트 이름은 판마다 바뀌므로 표에 없는 줄은 활동 줄로 흘려보내고 첫 줄을 lastError 에 남긴다. ⚠ 남은 확인 하나: 이벤트 이름은 문서 기준이고 이 컨테이너에 Codex 가 없어 가짜 CLI(test/fixtures/stub-codex.mjs)로만 검증했다 — 옮기는 층·세션 이어가기·도구 카드는 초록이지만, 진짜 이름이 다르면 «모르는 줄» 로 떨어진다. Dave 의 맥에서 한 턴 돌려 보고 이름을 맞춘다.

v0.5.3 (2026-09-13) — 알림 버튼 줄은 바닥에 · 표식은 배경 없이 폴더 옆에: Dave 의 두 지적을 고쳤다. ① 폰 알림 — 목록이 화면 중간에서 끊기고 그 아래가 통째로 비어 있었다. 원인은 NotifyCenter 가 본문에 인라인으로 걸어 둔 maxHeight: 55vh 다. 폰 미디어쿼리의 .modal-b { flex:1 } 을 그 상한이 이겨, 모달은 화면 높이인데 내용만 중간에서 멈췄다. 🔴 높이를 컴포넌트 안에서 인라인으로 묶지 않는다 — 상한은 데스크톱 전용 CSS 규칙(@media (min-width:761px) .nmodal .modal-b)으로 옮기고, 폰은 본문이 아래까지 차고 버튼 줄이 안전 영역 바로 위에 붙는다. 세 버튼이 들쭉날쭉 감기던 것도 두 칸 격자로 눕혀 높이를 맞췄다. 스모크가 «버튼 줄 아래 빈 곳 ≤ 24px · 본문이 모달의 절반 이상» 을 잰다. ② 회사 표식 — 배지 뒤에 깔던 검은 원판(background: var(--bg) + 링)이 목록마다 얼룩처럼 보였다(Dave: "배경 투명으로 폴더 옆에 붙어야 해"). 원판을 걷고 표식만 폴더봇 오른쪽 아래에 붙였다. ③ 그러면서 이름이 잘리는 것을 둘 잡았다 — Mid(가운데 말줄임)의 앞뒤 조각이 각각 inline-flex 아이템이라 경계의 공백이 접혀 «제품_Rondo· Codex» 로 붙어 있었고(white-space:pre 로 조각 안 공백을 지킨다), 형제 이름의 «· Claude» 는 떼었다 — 좁은 레일에서 정작 폴더 이름이 «제품_R…» 로 잘렸다. 어느 쪽인지는 표식이 이미 말해 주므로 글자는 기본이 아닌 쪽(Codex)에만 쓴다(V24 목업이 그렸던 모양 그대로다 — 내가 양쪽에 붙였던 게 틀렸다).

v0.5.4 (2026-09-13) — 다시 붙으면 «본 것» 을 전부 다시 읽는다 (폰만 낡던 버그): Dave — "왜 수정된 파일이 원격 모바일(맥 원격은 됨)에서 바로 적용이 안 돼? 할 일도 적용이 안 되어 있네." 🔴 원인은 둘이고 둘 다 폰에서만 난다. ① 놓친 프레임을 다시 안 읽었다 — 화면이 사는 길은 SSE 한 줄뿐인데, 맥은 그 줄이 안 끊겨 todo·chat·files 프레임을 계속 받는다. 폰은 잠그거나 다른 앱에 갔다 오는 사이 줄이 끊기고 그동안 온 프레임은 영영 없다. 다시 붙을 때 하던 일은 /api/state 새로 읽기 하나뿐인데 그 응답에는 할 일도 대화도 문서도 없다 — 그래서 봇 목록·알림만 최신이고 할 일 패널과 열린 문서는 옛날 것 그대로였다. 이제 다시 붙으면 화면이 들고 있던 것(할 일 · 열어 둔 대화)을 이름으로 하나씩 다시 읽고 파일을 읽는 화면(트리·문서 탭)에는 filesTick 을 한꺼번에 올린다. ⛔ «다시 붙었다» 를 «상태 새로고침» 과 같은 뜻으로 쓰지 않는다 — 끊긴 동안 놓친 것은 프레임이지 상태가 아니다. ② 조용히 죽은 줄을 몰랐다 — iOS 는 백그라운드에서 돌아오면 이 스트림이 오류도 종료도 없이 멈춘다. 읽기는 영원히 기다리고 화면은 붙어 있다고 믿는다. 그래서 호스트가 8초마다 보내는 박동(: hb)을 마지막으로 받은 시각으로 재고, 30초 넘게 아무것도 안 오면 끊고 다시 붙는다(HB_DEAD_MS). ⚠ 호스트의 박동 주기를 늘리면 이 문턱도 함께 늘려야 한다. ⚠ 새 화면이 «프레임으로만 최신이 되는» 것을 들고 있게 되면 재접속 목록에도 넣어야 한다 — 안 넣으면 맥에서는 멀쩡하고 폰에서만 조용히 낡는다(이 버그의 모양 그대로다). 스모크가 «오프라인 동안 todo.md 를 고치고 → 다시 붙이면 그 줄이 화면에 온다» 를 잰다(고치기 전 판으로 돌려 보면 실제로 빨개진다 — 확인함).

v0.5.5 (2026-09-13) — 표식은 이름 줄로(C 안) · 키보드 문턱의 네 번째 사고 · 원격에는 «메인 것» 을 안 보여 준다: ① 표식 위치 (V24 C 안, Dave 선택) — 폴더봇은 오른쪽 아래 모서리에 상태 배지를 이미 그린다(FolderBot.tsx: "레일 18px 에서도 읽힌다. 그래서 행 옆의 별도 점은 뺐다"). 내가 회사 표식을 같은 모서리에 붙여 실행 중·확인 대기에서 표식이 배지에 먹혔다. 네 안(대각선·탭·둘레 링·이름 줄)을 실제 크기·실제 줄에서 그려 Dave 가 이름 줄을 골랐다. 🔴 모서리는 «시간이 걸린 신호(상태)» 의 자리이고, 회사는 안 변하는 정체라 조용한 자리로 물러난다 — 아이콘 모서리에 다시 붙이는 변경은 이 결정과 충돌한다(스모크가 .bface 부활을 잡는다). ⚠ 이름 칸은 말줄임이 걸린 자리라 표식은 Mid 의 형제로 둔다(안에 넣으면 같이 잘린다). 배지는 그대로 둔다 — «표정이 있는데 배지가 필요하냐» 는 물음에 대한 답은 «크기가 다르다» 다: 눈은 64 좌표계의 6×10 이라 24px 에서 2.2×3.8px 이고, 그 크기에서 실눈과 보통 눈은 구분되지 않는다. 색은 형태가 안 읽히는 크기에서도 읽힌다. ② 키보드 문턱 — 네 번째 (Dave: "키보드랑 입력창 사이에 여백이 너무 넓어"). 레이아웃 판정은 r0.4.8 에서 «입력 중이냐» 로 고쳤는데, 화장(머리·모델 칩 접기)에는 문턱(covered > 140)이 그대로 남아 있었다. 홈 화면 앱·타사 키보드에서는 레이아웃 뷰포트까지 함께 줄어 innerHeight − vv.height 가 0 이라 그 문턱이 «안 열렸다» 로 떨어진다. 그래서 칩이 안 접히고 .chat-foot 이 --sab(홈 인디케이터 34pt)를 계속 비워 둬 입력창과 키보드 사이가 통째로 떴다 — 이미 키보드가 덮은 자리를 비워 둔 것이다. 🔴 화장에도 문턱을 쓰지 않는다. 판정은 하나뿐이다. ⚠ 위아래 화살표·체크 막대는 iOS 가 그리는 입력 도우미 막대라 웹에서 없앨 수 없다(네이티브 WKWebView 라면 inputAccessoryView = nil; Safari·PWA 에는 그 API 가 없다) — 할 수 있는 건 그 위를 안 낭비하는 것뿐이다. ③ 원격/메인 가르기 (Dave: "원격에서는 메인에 필요없는 내용들은 보여질 필요가 없어") — 기준은 «이 기기에서 할 수 있나». 눌러도 아무 일이 안 일어나는 줄은 원격에서 줄 자체를 안 그린다: 페어링 코드(호스트가 루프백에서만 연다) · macOS 권한(볼트를 읽는 메인 맥의 것인데 원격 맥 앱에도 folderbotDesktop 이 있어 새고 있었다) · 훅 설치(메인의 ~/.claude/settings.json 을 고친다 — 원격에는 상태만). 반대로 «메인을 원격에서 고치는» 것이 존재 이유인 줄(장기 토큰 · 기기 끊기)은 남긴다 — 헤드리스 메인을 밖에서 손보라고 만든 기능이다. ⚠ 새 줄을 만들 때 «메인에서만 되는가» 를 먼저 정한다 — 안 정하면 원격 화면에 죽은 버튼이 하나 는다.

v0.5.6 (2026-09-13) — 목업 먼저(계약) · 실행 중에도 호버 확대 · 지침·하네스 줄 살리기: ① CLAUDE.md 신설 — «목업 먼저, 제품은 그 다음» (Dave 확정: "앞으로 항상 목업 먼저 그리고 제품 만들 거야"). 화면에 보이는 것을 바꾸는 일은 코드를 쓰기 전에 /design 으로 먼저 보여 주고, 방향이 갈리면 안을 2~4개 내되 각각에 솔직한 대가를 적는다(내 favorite 에만 근거를 붙이면 고르는 게 아니라 짜고 치는 것이다). ⛔ «간단해 보여서» 로 건너뛰지 않는다 — 이 규칙이 생긴 이유가 그거다: 표식을 아이콘 모서리에 붙이는 «한 줄» 이 상태 배지와 겹쳐 두 라운드를 먹었다. 목업 없이 바로 고쳐도 되는 것(버그 · 승인된 목업의 구현 · 안 보이는 것 · 지정 변경)도 함께 못박아 범위가 번지지 않게 했다. ② 실행 중인 폴더봇이 호버에 안 커지던 것 (Dave: "애니메이션이 동작하는 폴더봇은 마우스 오버를 할 때 확대가 안 되네") — 🔴 돌고 있는 CSS 애니메이션은 같은 속성의 일반 선언을 늘 이긴다. .fb-work 가 transform 으로 흔들고 있어서 호버의 transform:scale(1.45) 이 통째로 무시됐다(그래서 실행 중인 봇만 안 커졌다). bob 을 개별 속성 translate 로 바꾸고 호버를 scale 로 주었다 — 둘은 독립 속성이라 겹치지 않고, 흔들면서 커진다. ⛔ 둘 중 하나를 transform 으로 되돌리면 그 순간 다시 겹친다. ③ 패널 «지침 · 하네스» 가 깨져 있던 것 (Dave 스크린샷) — 원인은 단순하다: 컴포넌트에 .hz·.hsec 클래스만 붙이고 CSS 를 안 썼다. 규칙이 없으니 아이콘이 제 줄로 빠지고 이름과 범위가 «CLAUDE.md지침 · 폴더» 로 붙었다. 패널의 .kv 와 같은 리듬(한 줄 · 26px · 말줄임 · 범위는 오른쪽 끝)으로 채웠고, «모두 보기» 가 스킬 88개까지 가므로 max-height:46vh 로 그 안에서 스크롤하게 했다. ⚠ 세 검사 모두 고치기 전 판에서 실제로 빨개지는 것을 확인하고 넣었다 — 초록인 검사를 넣고 «회귀 방어 완료» 라고 적는 것이 가장 흔한 거짓말이다.

v0.5.7 (2026-09-13) — 쓰다 만 메시지는 안 날아간다: Dave — "작성중인 채팅 텍스트 메시지가 앱을 껐다가 켜면 날라가." 입력창의 글은 useState('') 하나여서 새로고침·재시작에 그대로 사라졌다. 이제 localStorage 에 fb:draft:<botId>:<sessionId> 로 담고 300ms 뒤 저장한다. 🔴 세션마다 따로 둔다 — 하나로 두면 세션을 바꿔 놓고 돌아왔을 때 남의 초안이 입력창에 들어와 있고, 그걸 모른 채 엔터를 치면 엉뚱한 대화에 엉뚱한 말이 간다. 아직 세션이 없으면 new 로 담았다가 첫 메시지에서 지워진다(그 순간 키가 new → 실제 id 로 바뀌므로 둘 다 명시적으로 치운다 — 지연 저장이 새 키에 대고 지우는 경합이 있다). ⚠ 첨부도 함께 저장한다 — 본문의 @파일 만 살아나고 첨부가 비면 파일 없이 보내진다(조용히 틀린다). ⚠ localStorage 는 사생활 보호 창·저장 차단에서 던지므로 읽기·쓰기를 전부 try 로 감쌌다 — 초안 보관은 편의지 입력의 전제가 아니다. ⚠ 키를 바꾸는 이펙트가 setText 보다 먼저 draftRef 를 옮긴다(안 그러면 저장 이펙트가 옛 글을 새 키에 쓴다). 스모크가 «치고 → 새로고침 → 돌아오는가 → 비우면 키가 지워지는가» 를 재고, 기능을 빼면 실제로 빨개진다(확인함).

v0.5.8 (2026-09-13) — 채팅 「A · 문서처럼」 (Dave 확정): 세 방향(문서처럼 · 카드 · 검은 유리)을 목업으로 내고 Dave 가 A 를 골랐다. 요구가 둘이었고 서로 당겼다 — "Rondo 처럼 화려해지지 않게" + "단순함을 지키되 고급스럽게". 🔴 «고급스럽게» 를 더하기로 풀면 반드시 앞의 요구와 부딪힌다. 그래서 빼기로 풀었다. ① 기계는 언제나 접힌다 — 도구를 한 번만 써도 한 줄(도구 N회 · 파일 M개)이다. 종전에는 «4회 이상일 때만» 묶어 짧은 턴일수록 대화가 로그처럼 보였다. ⛔ 문턱으로 접기를 정하지 않는다 — «몇 개부터 묶나» 는 늘 틀린 질문이고, 기계는 언제나 접히고 펼치는 것은 사람이 정한다(문턱으로 판정해 네 번 틀린 키보드와 같은 병이다). ⛔ 접힌 줄에 도구 이름을 늘어놓지 않는다(Read 3 · Bash 2) — 그 줄이 길어지는 만큼 대화가 로그가 된다. 접힌 상태의 질문은 «얼마나» 이고 «무엇을» 은 펼쳤을 때 답한다. ⚠ 실패만은 접힌 채로도 말한다 — 조용히 접으면 사람이 실패를 영영 못 본다. ② 상자는 «차례» 를 뜻한다 — 사람 말의 배경 상자를 걷어냈다. 배경·테두리는 답해야 진행되는 것(승인·에이전트의 질문)에만 남긴다. 상자를 아껴야 보이는 순간 눈이 간다. ③ 읽기 폭 58ch · 행간 1.85 · 턴 사이 26px — 넓은 화면에서 줄이 끝까지 늘어나면 한 줄을 눈으로 되짚게 된다. 고급스러움이 가장 크게 갈리는 지점이다. ⚠ A 의 약점(턴 경계가 약하다)은 실재한다 — 여백과 1px 선 하나로만 푼다. 선을 굵히거나 배경을 깔면 그 순간 B(카드)가 된다. ⚠ 접기의 대가는 도는 동안의 상태 한 줄(.live)이 갚는다 — 그게 유일한 장치라 지우면 안 된다. 스모크가 «사람 말에 상자가 없다 · 오른쪽 정렬 · 리듬 26px · 읽기 폭 · 도구가 펴져 있지 않다» 를 재고, 되돌리면 실제로 빨개진다(확인함).

v0.5.9 (2026-09-13) — 답변 속 경로가 칩이 된다 · 문서 읽기 폭: A 안 2라운드. ① 채팅에서 문서로 바로 (Dave: "채팅에서 문서 선택으로 바로 문서로 이동") — 봇의 답 안에서 경로처럼 보이는 글자를 눌러서 여는 칩으로 만든다. 🔴 실제로 있는 것만 칩이 된다 — 후보는 core/paths.ts 가 내고 있는지는 호스트가 답한다(POST /api/bots/:id/exists, 한 번에 40개까지·루트 밖은 guard 가 막는다). 확인 없이 만들면 죽은 링크가 대화에 쌓이고, 한 번 눌러 본 사람은 다시 안 누른다. 🔴 렌더된 HTML 문자열을 정규식으로 건드리지 않는다 — 태그 안쪽을 잘못 물면 마크업이 깨진다. 그린 뒤에 텍스트 노드만 걸어 바꾸고 code·pre·a 안은 건너뛴다. ⚠ 스트리밍 중에는 하지 않는다(글자가 바뀌는 동안 DOM 을 갈면 선택이 튄다). ⚠ PARA 폴더 이름에는 공백이 있다(3. Area) — 공백을 통째로 허용하면 앞 문장까지 삼키므로 공백 없는 뼈대를 먼저 잡고 왼쪽으로 두 낱말까지 넓힌 변형을 함께 낸 뒤, 어느 쪽이 진짜인지는 호스트가 고르게 했다(한 자리에서 여러 개가 걸리면 긴 것이 이긴다). 유닛 11건 + 스모크가 «있는 파일은 칩이 되고 없는 경로는 글자로 남는다» 를 재며, 배선을 끊으면 실제로 빨개진다(확인함). ② 문서 읽기 폭 58ch · 행간 1.9 — 채팅과 같은 활자 사다리를 문서에도 적용. 탭의 밑줄·미리보기 기울임은 이미 A 안 그대로여서 손대지 않았다. ⚠ 목업과 다르게 간 것 하나 — 목업은 봇 답의 첫 줄을 제목처럼 그렸지만, 그건 봇이 스스로 쓴 문장이지 우리가 붙일 수 있는 위계가 아니다. 없는 제목을 지어내느니 활자·여백만으로 갈랐다.

v0.6.0 (2026-09-13) — 라이브 프리뷰 마크다운 편집기 (문서 기능 A · 1단계): 마크다운을 서식이 보이는 채로 그 자리에서 고친다. Rondo 알파의 편집기를 옮기는 첫 단계로 CodeMirror 6 + @codemirror/lang-markdown 을 올리고 src/client/MdEditor.tsx 를 만들었다. 🔴 churn 0 — 열고 저장해도 바이트가 그대로다. 공짜인 이유는 파싱도 직렬화도 안 하기 때문이다: 문서는 CodeMirror 의 문자열 그대로이고 데코레이션은 «보여주기» 일 뿐 문자를 하나도 안 바꾼다. ⛔ «마크다운을 파싱해서 다시 써 주는» 기능을 여기 붙이면 그 순간 이 계약이 깨진다. 스모크가 프론트매터·강조·목록·인용·위키링크가 섞인 파일을 열어 한 글자 쳤다 지우고 바이트를 비교한다. 🔴 커서가 없는 줄의 마커만 숨긴다 — 숨김은 Decoration.replace({}) 라 문자가 doc 에 그대로 남아 선택·복사·좌표가 순정 textarea 와 같다. 그래서 «모드» 가 없다. 🔴 숨긴 마커 위에 커서가 서면 서식이 조용히 깨진다 — 숨긴 범위를 atomicRanges 로 원자화하고, 그래도 떨어지는 경우를 transactionFilter 가 밀어낸다. 두 겹이 필요하다(원자 범위만으로는 클릭으로 들어오는 캐럿을 못 막는다). 제목 크기는 줄 클래스(.lp-h1)로 준다 — 토큰에 걸면 # 를 치는 순간에는 아직 안 커진다. CRLF 는 표시용으로 \n 으로 맞추고 저장할 때 되돌린다(혼합 EOL 은 LF 수렴 — 한계로 남긴다). ⚠ 지연 로드가 계약이다 — 편집기는 511KB 짜리 별도 청크이고 본체는 438KB 그대로다. 문서를 한 번도 안 연 폰이 마크다운 파서를 받으면 안 된다. 스모크가 «편집기가 별도 청크인가 · 본체에 CodeMirror 가 섞였나» 를 잰다. 🔴 이 라운드가 잡은 진짜 버그: 처음에는 편집기 생성 이펙트의 의존성에 value 를 뒀는데, onChange 가 부모의 draft 를 올리면 그 value 가 바뀌어 한 글자 칠 때마다 편집기가 통째로 다시 만들어졌다 — 포커스·커서·되돌리기가 그때마다 날아간다. 스모크가 «Backspace 가 안 먹는다» 로 잡았고, 재 보니 포커스가 body 로 빠져 있었다. ⛔ 그 이펙트에 value 를 다시 넣지 마라 — 밖에서 바뀐 글은 별도 이펙트가 넣는다. ⚠ 아직 위젯이 없다(표 리치 에디터 · 프론트매터 속성 패널 · 콜아웃 카드 · 이미지 임베드) — 2단계다.

v0.6.1 (2026-09-13) — 편집기 위젯: 체크박스 · 위키링크 · 이미지 (문서 기능 A · 2단계): 파서가 모르는 것들은 줄을 직접 훑는다 — lezer 는 [[ ]] 를 모르고, 체크박스는 위치가 정확해야 해서 줄 스캔이 더 맞다. ① 체크박스 — 눌러서 [ ] ↔ [x] 를 바꾼다. 🔴 한 글자만 갈아야 한다. 줄을 다시 쓰면 churn 이 나고 커서·되돌리기가 튄다. 스모크가 «누른 뒤 파일 길이가 그대로이고 다른 글자가 정확히 1개» 인지 재고, 다시 눌러 원상복구되는 것까지 본다 — Folder Bot 은 todo.md 가 중심이라 이 한 줄이 제품의 핵심 동작이다. ② 위키링크 — 대괄호는 숨기고 글자만 링크로(Rondo 의 확정 표기), 누르면 문서 탭에서 연다. ③ 단독 줄 이미지 — ![alt](경로) 만 있는 줄은 그림으로 바꾼다. 경로는 /api/bots/:id/raw 로 돌려 폰·브라우저에서도 뜬다. ⚠ 셋 다 커서가 그 줄에 들어오면 원문으로 돌아간다(체크박스만 예외 — 위치가 고정이라 늘 위젯이다). ⚠ 아직 표 리치 에디터와 프론트매터 속성 패널이 없다 — Rondo 에서 표만 690줄로 이식 대상 중 가장 큰 덩어리다. 3단계다.

v0.6.17 (2026-09-13) — Finder 급 파일 조작 (A1~A5): 트리 우클릭에 Finder 에서 보기 · 새 노트 · 새 폴더 · 복제 · 이미지 복사 · 경로 복사(절대/폴더 기준) 를 넣었다. ⚠ 「Finder 에서 보기」는 「기본 앱으로 열기」와 다른 일이다 — 저것은 파일을 열고 이것은 어디 있는지 보여 준다(open -R). 파일을 옮기거나 다른 앱에 끌어다 놓으려는 사람이 원하는 것은 늘 이쪽이라 따로 뒀다. ⚠ .md 는 자동으로 붙인다 — 사람이 확장자를 기억하게 하지 않는다. ⚠ 만드는 자리는 «누른 것이 폴더면 그 안, 파일이면 그 옆». 🔴 덮어쓰지 않는다 — 같은 이름이 있으면 이름 2 로 비킨다(새로 만들기·복제는 한 번 누르면 끝인 동작이라 덮어쓰면 되돌릴 방법이 없다). 🔴 이미지는 «그림 그대로» 클립보드에 넣는다 — 경로를 복사해 봐야 붙여넣는 쪽은 글자를 받는다. ⚠ 브라우저가 받아 주는 것은 PNG 뿐이라 다른 형식은 캔버스로 한 번 굽는다. 🔴 새로 만드는 것은 guard(roots(bot)) 로 모자랐다 — roots 에는 볼트 루트·참조 폴더가 들어 있어서 dir: '../..' 가 통과했다(스모크가 잡았다). 읽기는 넓게 허용해도 새로 만드는 것은 자기 폴더 안이어야 해서 inBot() 으로 따로 막는다.

v0.6.16 (2026-09-13) — Canvas 를 통째로 · 서식은 외우지 않아도 된다: ① Obsidian 의 .canvas 를 보고 고친다 (Dave: «편집 기능까지 다 만들어줘») — JSON Canvas 1.0: text·file·link·group 노드와 엣지(화살표·색·라벨), 팬·줌·열 때 맞춤, 노드 끌기·크기 조절·면에서 끌어 엣지 잇기·글 상자 더하기·지우기·색. 🔴 모르는 필드를 버리지 않는다 — 우리가 모르는 값도 Obsidian 에는 뜻이 있어서, 읽을 때 들고 있다가 저장할 때 그대로 돌려준다. 저장 모양도 탭 들여쓰기로 맞춘다(다르게 쓰면 열기만 해도 파일이 통째로 바뀐 diff 가 난다). 🔴 화면 변환은 판 하나의 transform 뿐 — 노드마다 좌표를 다시 계산하지 않는다. ⚠ 그룹을 옮기면 안에 완전히 들어 있는 노드가 함께 간다(Obsidian 동작). ⚠ 저장은 손을 뗀 뒤에만 — 끄는 동안 매 프레임 저장하면 파일이 수백 번 쓰인다. 갈래는 kindOf 가 .canvas 를 TEXT_EXT 보다 먼저 가른다(안 그러면 JSON 원문이 보인다). ② 서식 — 마크다운 글자를 외우지 않아도 된다 (Dave: «# 같은 마크다운 단축어, 선택시 보이는 상단 메뉴»): 줄 앞에서 / 를 치면 제목·체크박스·목록·표·인용·콜아웃·코드·구분선 메뉴가 뜨고, 글을 고르면 그 위에 서식 막대(H 순환 · B · I · S · 코드 · 링크 · 인용 · 체크박스)가 뜬다. ⌘B·⌘I·⌘K 도 붙였다. 🔴 단추가 하는 일은 «글자 넣기» 뿐 — 파일에는 **굵게** 가 그대로 들어가서 churn 0 이 그대로 산다. Obsidian 의 콜아웃(> [!note])도 표시를 숨기고 줄에 색을 준다. ⛔ CodeMirror 의 update() 안에서 화면 치수를 읽지 않는다 — coordsAtPos 를 거기서 불렀더니 «Reading the editor layout isn't allowed during an update» 로 플러그인이 통째로 내려가 막대가 조용히 사라졌다. 재는 것은 requestMeasure 의 read, 고치는 것은 write 다. ⚠ / 메뉴의 후보 범위는 슬래시 다음부터다 — 슬래시부터로 주면 걸러 낼 글자에 / 가 들어가 라벨과 하나도 안 맞고 목록이 통째로 빈다(그래서 메뉴가 안 떴다).

v0.6.15 (2026-09-13) — 팝업은 절을 넘어서 · 일하는 폴더봇 · ↑↓ 는 보고 있는 칸의 것 · --- 는 가로줄: ① 🔴 팝업이 잘렸다 (Dave 제보 · 세션 벤더 고르기). 패널의 절(.sec)은 접었다 폈다 하는 칸이라 overflow:hidden 으로 자기 높이 안에서 자르는데, 그 안에서 연 메뉴도 같이 잘린다. ⛔ z-index 로는 못 푼다 — 잘림은 쌓임 순서가 아니라 부모의 자르기라 부모 밖으로 나가는 수밖에 없다. Float(포털 + position:fixed)를 만들어 레일 우클릭 · 트리 우클릭 · 봇 메뉴 · 세션 벤더 고르기 · 문서 ⋯ 를 전부 body 로 올렸다. ⚠ 좌표는 열 때 한 번 재서 고정하므로 스크롤·리사이즈에는 닫는다 — 엉뚱한 자리에 떠 있는 메뉴보다 닫힌 메뉴가 낫다. ② 진행 줄을 뒤집었다 (승인된 채팅 v2 목업). 폴더봇 · 2m 48s · 짧은 행동 순서에 중단은 오른쪽 끝, 물결은 바로 위 접힌 줄로 옮겼다. 🔴 마스코트가 상황에 따라 바뀐다 (Dave 확정 — 셋 중 하나를 고르는 게 아니라 셋을 다 쓴다): 생각·명령은 숨쉬기, 읽기·쓰기·찾기는 서류 넘기기, 답 쓰기는 타이핑. 판정과 말은 순수 함수(core/work.ts)라 유닛이 고정한다 — ⛔ 진행 줄에 도구·파일 이름을 쓰지 않는다(줄이 길어지는 만큼 대화가 로그가 된다). 맥박 점(·)은 뺐다 — 마스코트가 이미 같은 말을 한다. ③ 🔴 ↑↓ 가 문서 안에서 파일 선택을 움직였다 (Dave 재보고). 종전 판정이 «textarea·input 이 아니면 글 쓰는 중이 아니다» 였는데 편집기는 contenteditable 이라 그 그물에 안 걸렸다. 이제 두 겹으로 본다 — 글 쓰는 중인가(isContentEditable 포함) · 포커스가 문서 열 안인가. ⌘F 도 같은 규칙을 쓴다(문서면 편집기 찾기, 파일 칸이면 이름 거르기). ④ --- 가 가로줄이 된다 — 줄 전체가 마커라 통째로 바꾼다. ⚠ 프론트매터의 --- 는 구분선이 아니라 경계라 건드리지 않는다.

v0.6.14 (2026-09-13) — 누른 자리에 커서 · 링크 파비콘 · 외부 열기 아이콘 · 메뉴바 게이지: ① 🔴 문서를 처음 클릭하면 커서가 엉뚱한 데 섰다 (Dave 제보). 원인은 «커서가 든 줄은 원문을 드러낸다» 는 규칙 자체다 — 드러나는 순간 줄이 통째로 오른쪽으로 밀리는데, 클릭 위치는 밀리기 직전 배치로 정해진다. 그래서 Rondo 처럼(livePreview.tsx 2455~) 서식 마커를 커서가 있어도 계속 숨긴다: ## > ** ~~ 백틱. ⚠ # 만 친 상태(뒤에 공백 없음)는 그대로 보여 준다 — 아직 제목이 아니고 사람이 서식에 들어서는 중이다. ⛔ 주소(URL)는 예외로 커서 줄에서 드러낸다 — 그건 서식이 아니라 내용이라 안 보이면 오타 난 주소를 고칠 길이 없다. 곁들여 맨 URL 이 통째로 사라지던 버그를 고쳤다(URL 노드를 무조건 숨기고 있었는데, [글](주소) 의 주소와 달리 맨 URL 은 그 자체가 보이는 전부다 — 여는 괄호 뒤인지로 가른다). 그리고 이미지 높이를 기억한다 — 늦게 도착한 그림이 아래 글을 밀어 같은 어긋남을 만들었다. 스모크는 «커서가 어느 글자냐» 가 아니라 줄이 안 밀리느냐를 잰다(커서는 원래 가장 가까운 글자 경계로 붙는다 — 제목은 한 글자가 24px 라 그만큼 떨어진다). ② 링크 앞 파비콘 — 채팅 · 문서 편집기 · 입력창이 같은 캐시 하나를 본다(각자 받으면 한 화면 안에서 아이콘이 서로 다른 순간에 나타난다). 받아 오는 것은 호스트다: 화면이 폰일 수도 원격 맥일 수도 있는데 각자 받으면 기기 수만큼 두드리고 폰이 볼트 밖 사이트를 직접 찌른다. ⚠ 먼저 /favicon.ico 를 찔러 보고 없을 때만 문서를 받아 <link rel=icon> 을 고른다 — 빠르고 우리가 보는 것도 적다. 없다는 사실도 캐시한다. ⚠ 입력창은 textarea 라 안에 그림을 못 넣는다 — 쓰는 중인 주소를 칸 위 칩으로 올려 보내기 전에 확인할 수 있게 했다. ⚠ 어디서나 자리표시자(지구본)를 먼저 놓는다 — 비워 두면 아이콘이 도착할 때마다 글줄이 밀린다. ③ 외부 앱으로 열기를 ⋯ 밖으로 꺼냈다. ④ 🔴 원격 맥의 메뉴에 사용량이 아예 없었다 — pollUsage 를 startHostMode() 안에서만 시작해서, 호스트에 붙어 쓰는 맥은 영영 안 받아 왔다(받아 오는 쪽은 원래부터 두 모드를 알고 있었다). 이제 모드와 무관하게 돌고, 메뉴와 메뉴바 제목에 막대 게이지를 그린다 — 네이티브 메뉴는 HTML 을 못 그리므로 폭이 고른 블록 문자(▰▱)가 정답이다.

v0.6.13 (2026-09-13) — 채팅에 쓴 한 줄이 그대로 루틴이 된다: Dave: «루틴을 폴더 채팅에서 바로 채팅으로 생성할 수 있으면 좋겠어». 컴포저의 + → 「루틴으로 만들기」 를 누르면 방금 쓴 문장에서 주기·이름·시킬 일을 뽑아 루틴 편집 화면이 채워진 채로 뜬다. ⛔ cron 을 사람이 외우게 하지 않는다 — 「매주 월요일 아침 8시에 지난주 한 일 정리해 줘」 가 0 8 * * 1 이다(core/routineText.ts 의 순수 함수, 유닛 4건). 시각 말(새벽·아침·점심·오후·저녁·밤), 오전/오후, 분, 평일·주말·요일·매월 N일을 읽는다. 🔴 저장은 사람이 누른다. 주기는 어디까지나 추측이고, 틀린 추측을 조용히 저장하면 엉뚱한 시각에 봇이 혼자 일한다 — 그래서 하는 일은 «폼을 미리 채워 주는 것» 까지다(스모크가 «안 눌렀으면 저장 안 됨» 을 잰다). ⚠ 못 읽으면 매일 오전 9시로 두되 matched: false 로 알린다 — 틀린 값을 맞은 척하지 않는다. ⚠ 입력칸이 비어 있으면 직전에 보낸 말을 쓴다(「아까 그거 매일 해 줘」 가 자연스러운 흐름이다). + 메뉴의 설명 줄에 읽어낸 주기(「매주 월요일 08:00」)를 미리 보여 줘서, 누르기 전에 맞는지 알 수 있다.

v0.6.12 (2026-09-13) — 맥 기본 단축키를 전 영역에: ⌘, 설정 · ⌘/ 단축키 표 · ⌘K 폴더 고르기 · ⌘N 새 세션 · ⌘1…9 n번째 폴더 · ⌘[ ⌘] 이전·다음 폴더 · ⌘⇧U 알림 (기존 ⌘B · ⌘⇧B · ⌘⇧D · ⌘W 유지), ⎋ 로 시트 닫기. 🔴 전부 ⌘(⌃) 를 낀다 — 맨 글자 단축키를 두면 입력칸에 글자를 치는 순간 명령이 돈다(이 앱은 거의 모든 화면이 글 쓰는 화면이다). 스모크가 «글자를 쳐도 명령이 안 돈다» 를 함께 잰다. ⛔ 텍스트 편집 단축키(⌘Z·⌘C·⌘V·⌘A·⌘F)는 가로채지 않는다 — 맥 기본과 편집기(CodeMirror)의 것이 이겨야 한다. 가로채는 순간 «실행 취소가 안 되는 앱» 이 된다. ⚠ 표가 정본이다 — KEYS 한 배열이 동작과 ⌘/ 화면을 함께 먹여서, 표와 실제가 갈리지 않는다. 곁들여 맥 메뉴바를 붙였다(Menu.setApplicationMenu) — 맥 앱이면 메뉴가 있어야 하고, 없으면 사람은 «이 앱엔 단축키가 없나» 로 읽는다. ⛔ 편집 메뉴는 role 로만 둔다(undo·redo·cut·copy·paste·selectAll) — 손으로 만들면 그 순간 맥 기본 동작이 깨진다. 우리 항목은 fb:cmd 로 화면에 넘겨 동작을 한 곳에 둔다.

v0.6.11 (2026-09-13) — Codex 를 Claude 와 같은 높이로: Dave: «현재 codex 실행 반영이 전혀 안 되어 있어. codex도 claude와 같이 영구 토큰 에이전트 모델 기본 설정등이 있어야 해». ① 기본값을 벤더마다 따로 둔다 — 🔴 하나로 두면 Codex 세션이 claude-fable-5-1 로 떠서 첫 턴에 죽는다(이름 체계가 다르다). sessions.defaults 가 {claude, codex} 가 되고, 세션은 자기 벤더 칸에서 값을 가져간다. ② 노력은 플래그가 아니라 설정 키다 — Codex 에는 --effort 가 없고 -c model_reasoning_effort="…" 로 준다. ⛔ 아는 값만 넘긴다: Claude 의 xhigh·max 를 그대로 넘기면 CLI 가 그 자리에서 죽으므로 모르면 안 넘기고 CLI 기본값에 맡긴다(같은 규칙을 모델 이름에도 쓴다 — fitsProvider). ③ 샌드박스가 곧 권한 정책이다 — Codex 에는 stdio 권한 프로토콜이 없어 우리가 승인 화면을 못 띄운다. 그래서 설정에 「읽기만 · 이 폴더 쓰기 · 전부 허용」 을 두고 그 값이 --sandbox 로 간다. ④ 영구 토큰의 Codex 판 — codex login 은 브라우저를 여는 대화형이라 우리가 대신 못 한다. 그래서 상태만 읽고(~/.codex/auth.json · OPENAI_API_KEY), 헤드리스면 API 키를 받아 워커 환경에만 넣는다(⛔ 사용자의 ~/.codex 설정 파일은 건드리지 않는다). ⑤ 화면의 모델·노력 목록은 그 세션의 벤더가 정한다. Codex 모델은 CLI 판마다 달라서 직접 입력을 함께 준다 — 우리가 목록을 «정하지» 않는다. ⚠ 스모크는 가짜 CLI 가 남긴 argv 로그로 모델·노력·샌드박스·키가 CLI 까지 실제로 갔는지 잰다(가짜는 플래그를 안 쓰므로 화면에는 흔적이 없다). 🔴 곁들여 포트 선점 가드를 넣었다 — 앞선 실패로 남은 호스트가 같은 포트를 잡고 있으면 우리는 «건강한 응답» 을 받고 옛 코드를 재게 된다(이번에 실제로 그래서, 고친 것이 안 고쳐진 것처럼 보였다).

v0.6.10 (2026-09-13) — 레일 우클릭으로 폴더 삭제: 좌측 폴더 레일에서 우클릭하면 정지 · 은퇴 · 폴더 삭제가 나온다 (Dave 요청). ⚠ 셋은 서로 다른 일이라 한 항목으로 합치지 않았다 — 정지는 목록에서만 내리고(기록 유지), 은퇴는 Archive 로 보내 볼트의 일부로 남기고, 삭제는 볼트에서 치운다. 🔴 삭제는 지우지 않고 옮긴다 — 되돌릴 수 없는 일을 우클릭 한 번 뒤에 두지 않는다. 폴더는 볼트 안 .folderbot/trash/<시각>_<이름> 으로 통째로 가고, 파인더에서 꺼내면 그대로 돌아온다(확인창에 그 말을 적는다). 오케스트레이터 줄에는 이 메뉴가 없다 — 있으면 볼트 자체를 지우게 된다. ⚠ 스모크는 화면에서 지우지 않고 API 로 잰다(화면에서 지우면 뒤따르는 검사가 쓰는 봇이 사라진다). 곁들여 표 편집 검사의 고정 대기를 폴링으로 바꿨다 — 자동 저장은 «멎고 800ms 뒤» 라 느린 날에 갈렸다(실제로 한 번 갈렸다). ⛔ 「그날 운이 나빴다」로 넘기지 않는다.

v0.6.9 (2026-09-13) — 벤더는 폴더가 아니라 세션의 성질이다: 🔴 회사 표식을 왼쪽 폴더 레일에서 떼어 세션 목록으로 옮겼다 (Dave: «해당 폴더에는 여러 세션이 claude 혹은 chatgpt 구분없이 생길 수 있기 때문»). 그래서 모델이 바뀐다 — ① SessionInfo.vendor 신설, 워커 종류는 이제 세션이 정하고 봇의 값은 «안 고른 세션» 의 폴백이다. ② POST /bots/:id/sessions 와 /bots/:id/send 가 vendor 를 받는다. ③ 폴더 하나 = 줄 하나 — 종전에는 «이미 있나» 를 rel + vendor 로 봐서 같은 폴더가 Claude/Codex 두 줄로 섰다(V24 의 «형제»). 이제 rel 만 본다. ⛔ 폴더 이름 뒤의 «· Codex» 도 뺐다 — 폴더 이름에 회사를 박으면 그 폴더가 한 회사 것처럼 보인다. ④ 세션 목록의 + 가 Claude/ChatGPT 를 묻는다. 세션이 0 이면 종전의 「메시지를 보내면 생겨요」 는 막다른 안내였다(누가 맡을지 고를 자리가 어디에도 없었다) — 이제 그 자리가 「Claude 나 ChatGPT 로 시작」 단추다. ⚠ 깔린 에이전트가 하나면 묻지 않는다. 채팅 머리의 표식도 폴더가 아니라 지금 이 세션의 것을 그린다. 스모크는 «벤더가 달라도 봇은 하나» · «한 폴더 안에 Claude 세션과 Codex 세션이 섞여 산다» 를 잰다.

v0.6.8 (2026-09-13) — 보는 화면이 곧 고치는 화면 · 채팅은 cursor 스타일로 되돌림: ① 🔴 문서에서 「편집 모드」를 없앴다 (Dave: «rondo 와 같이 보기화면과 편집화면이 완벽하게 싱크»). 마크다운은 언제나 라이브 프리뷰 편집기 하나로 그린다 — 눈·연필 토글(v0.6.4)도, 더블클릭 진입도, 「편집 중」 배지도 함께 지웠다. ⛔ 읽기용 Md(marked)와 편집용 편집기를 둘 다 두지 않는다 — 둘을 오가는 순간 «싱크» 가 사람 몫이 되고, 실제로 그 왕복에서 «떠날 때의 저장이 새 문서를 덮는» 사고가 났다(v0.6.3). 모드가 없으니 알릴 상태도 없어서 저장 중·실패일 때만 한 마디 한다. ⚠ 잘려서 받은 파일(truncated)만 읽기 전용으로 남긴다 — 앞부분만 들고 저장하면 뒷부분이 날아간다. 「편집 중」 대신 「아직 안 낸 글이 있다」(dirty) 로 충돌 배너를 판단한다. ② 채팅 외양을 cursor 스타일로 되돌렸다 (Dave: «이전 스타일이 더 나»). 「A · 문서처럼」의 26px 리듬 · 오른쪽 정렬 · 58ch · 머리줄(17/600) · 턴 경계선을 전부 되돌리고, 사람 말은 다시 입력칸 모양 상자로 답 위에 놓인다. ✅ 남긴 것은 둘뿐 — 기계 접기(.mach) 와 문서 링크칩(.pchip). Dave 가 콕 집어 원한 것이 링크칩이고, 기계 접기는 «Rondo 처럼 화려해지지 않게» 라는 요구 그 자체라서 남겼다(외양이 아니라 조용함의 장치다).

v0.6.7 (2026-09-13) — 도망가는 ↓ · 라이트 테마의 묻힌 글자 · 바깥 링크는 바깥에서: ① 🔴 「최근으로」 단추가 누르려 하면 도망갔다 (Dave 제보). 원인은 자동 스크롤과 단추가 문턱 하나(80px)를 같이 쓴 것 — 글이 흘러드는 동안 그 선을 오가며 단추가 DOM 에서 빠졌다 들어왔고, 들어올 때마다 «4px 떠오르는» 등장 애니메이션이 다시 돌았다. 이제 눈금을 나눈다: 자동 스크롤은 그대로 80px, 단추는 240px 넘게 멀어져야 뜨고 40px 안에 와야 사라지는 이력(hysteresis)을 쓰며, 사라질 때도 DOM 에서 빼지 않고 투명도만 낮춘다. 스모크가 마우스를 올린 전후 좌표가 같은지 잰다. ② 🔴 라이트 테마에서 「여기서 시작」 이 안 보였다 — .btn.primary 가 color:#111 을 박아 뒀는데 라이트에서는 --w 가 바로 그 #111 이라 배경과 글자가 같은 색이 됐다(hover 의 #fff 도 같은 문제). 「글자 위 색」은 언제나 --onAccent 로 바꿨고, 라이트 테마에서 칠해진 단추를 전수로 훑어 대비 3:1 미만이면 빨개지는 검사를 붙였다(그 검사가 .sendb.off 2.38 도 함께 잡아 고쳤다). ③ 바깥 링크는 바깥에서 연다 — setWindowOpenHandler 는 target=_blank 만 받아서, 답변 속 평범한 <a href> 는 창을 통째로 그 사이트로 끌고 갔다(앱이 그 자리에서 사라지고 돌아올 길도 없다). 셸의 will-navigate 에서 우리 화면이 아닌 http(s) 를 기본 브라우저로 넘기고, 렌더러도 그린 뒤 바깥 링크에 target=_blank 를 단다(marked 는 안 달아 준다).

v0.6.6 (2026-09-13) — 턴 경계는 1px 선 하나: 「A · 문서처럼」의 유일한 약점을 캔버스가 적어 둔 처방 그대로 메웠다 — 배경도 카드도 안 쓰기로 했으니 새 턴이 시작되는 자리(사람이 말한 자리)에 1px 선 하나만 긋는다. ⛔ 굵히거나 배경을 깔면 그 순간 B(카드)가 된다 — 스모크가 두께와 배경을 잰다. ⚠ 선이 세로 리듬을 늘리지 않게 음수 여백으로 26px 한가운데 앉힌다(폰은 14px 리듬이라 값이 다르다) — 안 그러면 턴 사이가 52px 로 벌어져 «조용함» 이 «허전함» 이 된다.

v0.6.5 (2026-09-13) — 봇 답의 첫 줄 · 접힌 줄의 걸린 시간: 승인된 캔버스의 활자 여섯 단계 중 마지막으로 남아 있던 「봇 답의 첫 줄(17/600)」을 넣었다. ⚠ 짧은 한 줄이 문단 하나로 끝날 때만 올린다 — 길면 굵은 덩어리가 돼서 오히려 시끄럽고, 뒤에 빈 줄이 없으면 스트리밍 중에 커졌다 작아졌다 한다(그래서 «뒤에 빈 줄» 이 조건에 들어 있다). ② 접힌 기계 줄에 걸린 시간을 더해 「도구 7회 · 파일 3개 · 12.4초」로 맞췄다. 도구 줄에는 «끝난 시각» 이 없어서 다음 줄이 생긴 시각으로 잰다. ⚠ 0.1초 미만은 안 적는다 — 「0.0초」 는 정보가 아니라 잡음이다. 글귀 규칙은 순수 함수 machSummary 로 빼 유닛이 고정한다(스텁은 즉답이라 화면 검사로는 시간을 잴 수 없다 — 잴 수 없는 것을 화면에서 우기지 않는다). ⛔ 접힌 줄에 도구 이름을 늘어놓지 않는다는 규칙은 그대로고, 스모크가 줄이 길어지는지 본다.

v0.6.4 (2026-09-13) — 모드 전환은 눈·연필 두 아이콘뿐: 승인된 「A · 문서처럼」 캔버스의 문서 규칙을 마저 옮겼다. 읽기↔편집은 툴바의 같은 자리 한 단추(연필 ↔ 눈)로만 오간다 — 종전의 「완료」 버튼과 «편집 중» 이라는 큰 상태 표시를 지웠다. 🔴 «원래 고칠 수 있는 것» 이 기본이라 편집을 사건처럼 알리지 않는다. 상태는 «자동 저장» 한 마디로 조용히 두고, 저장 중·실패일 때만 말을 바꾼다. ⋯ 메뉴에서도 「편집」 항목을 뺐다(같은 일을 하는 문이 둘이면 그게 곧 화려함이다). 더블클릭으로 그 자리에서 편집하는 길은 그대로다.

v0.6.3 (2026-09-13) — 표는 진짜 표로, 칸만 고친다 · 문서를 옮겨도 글이 안 따라온다: ① 표 리치 편집 — 커서가 밖에 있으면 마크다운 표를 진짜 <table> 로 그리고(정렬 :--:·--: 까지), 칸을 누르면 그 자리에서 고친다. 🔴 고친 칸의 글자만 바뀐다. Rondo 는 표를 파싱해 통째로 다시 직렬화하는데(690줄), 그러면 파이프 간격·정렬 표기가 전부 정규화돼 한 칸만 고쳐도 표 전체가 바뀐 diff 가 난다 = churn. 여기서는 그 칸의 문자 범위만 갈아 끼운다 — 스모크가 파일 전체를 바이트로 대조한다. ⛔ +행·+열도 «끼워 넣기» 뿐(순수 insert)이라 기존 문자를 옮겨 쓰지 않는다. Tab 으로 칸을 옮기고, 표 안에 커서가 있으면 접지 않는 규칙 하나로 ⋯(원문으로 고치기)가 공짜로 나온다 — 「모드」 플래그를 따로 두지 않는다. 기계(+행·+열·⋯)는 「A · 문서처럼」 대로 올려야 나온다. ② 🔴 편집 중에 다른 문서로 옮기면 그 글이 새 문서를 덮어썼다 — 실사고다. rel 이 먼저 바뀌고 edit 은 load 가 끝난 뒤에야 꺼져서, 편집기가 떠나며 내는 «못 낸 저장」이 새 문서의 onCommit 에 실렸다(스모크가 todo.md 가 표 문서로 덮인 것으로 잡았다). 문서를 옮기는 즉시 편집을 닫고, <MdEditor key={rel}> 로 문서마다 편집기를 따로 둔다 — 떠날 때의 저장이 옛 문서로 가고, 되돌리기 기록이 문서를 넘나드는 것도 함께 막힌다.

v0.6.2 (2026-09-13) — HTML 은 브라우저처럼 · 외부 앱으로 · 프론트매터 접기: ① HTML 갈래 신설 — .html 은 TEXT_EXT 에도 있어서 먼저 보지 않으면 원문으로 떨어진다(그래서 kindOf 에서 맨 앞에 뒀다). 문서 열 안에 주소줄 크롬을 얹고 페이지를 그대로 그린다. 🔴 샌드박스 안에서 그린다 — 에이전트가 만든 리포트를 앱 안에서 보되 그 안의 스크립트가 볼트를 읽거나 우리 화면을 만지지 못하게 가둔다. ⛔ allow-same-origin 을 주지 않는다 — 주는 순간 샌드박스가 사실상 없는 것과 같다. ② 외부 앱으로 열기 (POST /api/bots/:id/open) — 🔴 여는 주체는 언제나 호스트(메인 맥)다. 폰에서 눌러도 미리보기는 메인에서 뜬다. 그래서 화면이 원격이면 이름을 «메인 맥에서 열기» 로 바꾸고 «이 기기로 내려받기» 를 함께 준다 — 안 그러면 눌러도 아무 일이 없는 것처럼 보인다(우리가 세운 원격/메인 규칙 그대로). 루트 밖은 guard 가 막고 스모크가 그 차단을 잰다. ③ 프론트매터 접기 — 맨 위 --- 블록을 REFERENCE · PARA · 지침 같은 작은 라벨 한 줄로 접는다. 🔴 문서를 열자마자 YAML 이 먼저 보이면 안 된다 — 그건 문서가 아니라 설정이다. ⛔ YAML 을 파싱해서 다시 쓰지 않는다. Rondo 는 속성 패널에서 고치면 블록을 통째로 재직렬화하는데 그 순간 서식이 정규화된다(그쪽도 churn 리스크로 적어 뒀다). 여기서는 보여 주기만 하고, 고치는 것은 커서를 넣어 원문에서 한다 — 그래야 churn 0 이 예외 없이 성립한다. ⚠ 그 결과 처음 커서를 프론트매터 뒤에 둔다 — 기본값(0)이면 1번 줄이 «활성» 이라 접히지 않는다(스모크가 잡았다). 사람이 제일 먼저 고칠 곳도 거기가 아니다.

루프 라운드 (2026-09-15) — 「만든다」 10개를 순서대로, 배포는 한 번: Dave 가 «폴더에 사는 봇에게 시키고, 결과를 그 폴더에서 바로 본다» 라는 컨셉으로 만들 것·안 만들 것을 갈랐다(마스코트·서브에이전트 매니저·백그라운드 셸·Office 보기·스냅샷·레이아웃 프리셋·UI 배율은 안 만든다). 만든 것 — ① 경로 칩 재작업: exists 가 봇 폴더 기준만 보던 것을 봇 폴더 → 볼트 루트 → 절대 경로 세 갈래로, 폴더도 칩(누르면 트리에서 비춘다), 내 메시지의 첨부 꼬리도 칩으로. ② ⌘[ ⌘] 는 방문 히스토리(⌥⌘[ ] 가 레일 순서). ③ 즐겨찾기 고정 — 레일 맨 위 「고정」 3개 상한, 볼트에 남는다. ④ 절전 시간 설정 — 설정 › 에이전트, 0 = 안 재움. ⑤ [[ 자동완성은 이미 있었다 — 점검표의 ❌ 가 틀렸다. 좁히기·고르기·]] 한 짝을 스모크로 고정. ⑥ 파일 전후 diff — 손댄 파일 칩 옆 ⇄. 🔴 «전» 은 봇이 마지막으로 본 내용이다: tool_use 가 도착한 순간 디스크를 읽으면 CLI 가 이미 썼을 수 있다(스텁 실측: 전 = 후). Claude Code 는 있는 파일을 Read 한 뒤에만 고치므로 Read 때 붙잡고, 고친 뒤 다시 읽어 다음 턴의 «전» 으로 삼는다. Read 없이 Write = 새 파일. 메모리에만 살고 호스트를 다시 켜면 «모른다» 고 말한다(빈 파일로 그리지 않는다). 비교(LCS)는 화면이 한다 — 폰에서도 같은 값. ⑦ 지난 대화 찾기 — ⌘P 에 두 글자면 볼트 전체 세션의 이름·말까지(/api/search, 봇 경계를 넘는 유일한 읽기). 200ms 쉬었다 묻고 옛 답은 버린다. ⑧ 슬래시 명령 관리 — 명령은 파일이다(.claude/commands/<이름>.md). 패널에서 목록·만들기·열기만, 고치기는 문서 열, 지우기는 트리 휴지통. 이름은 CLI 가 받는 꼴만([\w-]). 컴포저 / 목록은 filesTick 도 봐서 만든 즉시 나온다. ⑨ PDF 내보내기 — ⛔ 편집기를 인쇄하지 않는다(CodeMirror 는 보이는 줄만 그린다). 글 전체를 marked 로 다시 그린 인쇄용 사본을 body 에 세우고 @media print 가 앱을 숨긴다. 맥 앱은 printToPDF(자리는 사람이 고른다), 브라우저·폰은 인쇄 대화상자. ⑩ mermaid · KaTeX — $…$ 는 marked 보다 먼저 걷어 낸다(_·\ 를 marked 가 먹는다). 코드 안·돈($5)은 그대로. 둘 다 지연 로드 — 번들 검사가 본체 혼입을 잡는다. 스트리밍 중에는 안 그린다. 편집기 안 펜스는 범위 밖. ⚠ 라운드 규칙대로 항목마다 npm run qa 를 통과한 뒤 로컬 커밋했고, 푸시(배포)는 열 개가 끝난 뒤 한 번이다.

파일 칩 B안 · 채팅 열 놓기 (2026-09-15) — Dave 선택: 손댄 파일 칩이 봇 폴더 밖 파일에서 볼트 경로 전체를 한 줄에 다 그려 채팅 폭을 먹었다(실측). 목업 세 안(A 「폴더 / 이름」 · B 「이름 + 폴더 표식」 · C 접힌 목록) 중 Dave 가 B 를 골랐다. 🔴 세 자리(답 아래 · 입력창 위 · 내 말풍선 아래)가 같은 칩이다(FileChip, 조각 나누기는 core/chipName) — 표식은 직전 폴더 한 칸, 봇 폴더 바로 아래 파일은 표식 없음, 최대 340px, 전체 경로는 툴팁, 4개 넘으면 「+n」. 놓을 자리는 채팅 열 전체로 넓혔다 — 종전에는 입력창 상자 안에만 놓을 수 있었고 대화 위에 놓으면 크롬이 창을 그 파일(file://)로 옮겨 앱이 사라졌다. 이게 «파인더 드래그가 안 된다» 의 정체였다: 창 전체에서 기본 동작을 막고(렌더러 + 셸 will-navigate), 열에 들어오면 점선 카드가 뜬다(dragenter/leave 는 세어서 깜빡임을 막는다). 맥 앱은 놓인 파일의 진짜 경로를 안다(preload webUtils.getPathForFile — File.path 는 Electron 32 에서 사라졌다): 볼트 안이면 복사 없이 그대로 첨부, 밖이면 첨부/ 에 복사. 복사 중인 파일도 칩으로 먼저 선다(회전). ⚠ 파인더 놓기 자체는 헤드리스에서 못 잰다 — DataTransfer 에 File 을 담은 DragEvent 로 같은 경로를 잰다.

입력창 안 인라인 칩 (2026-09-15, Dave: «칩이 채팅 창 안으로 들어가야 해» — Rondo 입력창과 같은 꼴): 첨부 칩을 입력창 위 별도 줄에 두던 것을 버리고 글 속에 세운다. 🔴 모델은 그대로 글자열이다 — 첨부는 글 속 @이름 토큰이고 입력창(InlineInput, contenteditable)은 그 토큰을 칩으로 그릴 뿐이다. 보내기·슬래시·@ 고르기·캐럿 계산은 종전 textarea 시절의 text·caret 를 그대로 쓴다. 첨부를 더하는 길은 하나(addAtt = 목록에 넣고 글에 토큰이 없으면 끝에 붙인다)이고, 글에서 칩을 지우면(⌫ 한 번, 원자) 토큰이 사라져 목록에서도 빠진다 — 두 표면이 어긋날 수 없다. ⚠ CodeMirror 를 안 쓴 이유는 번들 계약(입력창은 첫 화면에 있어 지연 로드가 안 된다). ⚠ 한글 조합 중에는 DOM 을 다시 그리지 않는다(IME 가 끊긴다) — 조합이 끝난 뒤 맞춘다. ⚠ 토큰을 끝에 붙였으면 캐럿도 끝으로 옮긴다 — 안 옮기면 옛 캐럿 자리에서 @… 를 읽어 @ 목록이 엉뚱하게 뜬다(스크린샷으로 잡았다). 복사 중인 파일도 칩으로 먼저 선다(회전). 스모크의 inputValue 는 contenteditable 에 없으므로 data-value 로 잰다.

편집기 QA — 클릭 · 화살표 · 선택 (2026-09-16, Dave: «편집 기능에 버그가 많아 … ① 화살표 이동시 제대로 위치하지 못하는 현상 ② 마우스 클릭시 다른곳에 클릭되는 현상 ③ 커맨드 + 키보드에서 선택에 문제»): 원인은 넷이었고 전부 실측으로 잡았다. ① 블록 위젯의 margin — ---·표·이미지 위젯에 margin 을 줬는데 CodeMirror 는 위젯 높이를 rect 로 재므로 margin 만큼 줄 높이 지도가 어긋나 그 아래를 누르면 다음 줄에 캐럿이 섰다(인용 줄을 눌렀는데 아래 빈 줄로). 여백은 전부 padding 으로. ② 줄 단위 드러내기 — 캐럿이 줄에 들어오기만 해도 [글](주소)·[[위키]]·이미지가 원문으로 펴져 줄이 밀렸고, ↑↓ 로 지나가기만 해도 캐럿이 옆으로 튀어 보였다. 이제 그 링크에 닿았을 때만 편다(링크는 노드 범위 안, 위키는 대괄호 안 — 옵시디언과 같다; 위키는 원자를 풀어 캐럿이 들어갈 수 있다). 프론트매터는 캐럿 0 에서 펴지지 않는다(↑ 로 맨 위에 닿을 때마다 YAML 이 펴지던 것). ③ 캐럿 밀어내기 필터가 앵커를 버렸다 — { anchor: hit.to } 하나로 갈아 끼워 ⇧→·⌘⇧→ 가 숨은 ** 에 닿는 순간 선택이 캐럿으로 접혔다. 머리만 밀고 앵커는 둔다(방향도 본다). 같은 필터가 바뀌기 전 좌표의 원자 범위를 그대로 써서 ⏎ 뒤 캐럿이 두 줄 아래 제목으로 튀었다 — tr.changes 로 옮겨서 댄다. ④ 마우스를 누르는 동안은 얼린다(Rondo 라운드 324 dragFreeze) — 클릭 좌표 계산이 밀리기 전 배치 위에서 끝나게. 덤으로 표를 걸친 선택은 표를 통째로 삼키고(Rondo 라운드 325), ⏎ 는 목록·체크박스를 잇는다(markdownKeymap 이 빠져 있었다). 🔴 재는 것은 DOM 선택이 아니라 문서 위치(window.__fbEditor QA 손잡이) — 위젯 사이 자리는 DOM 으로 못 잰다. 필터는 EditorState 만으로 유닛테스트한다.

문서의 링크 — 파비콘 + 바로 클릭, 표 안에서도 (2026-09-17, Dave: «문서의 링크도 favicon 이 포함되어야 하고 바로 클릭이 가능해야 해. 표안에 있는 링크 포함해서.»): 종전엔 본문 링크에 파비콘은 섰지만 여는 손이 없었고(밑줄뿐), 표 칸은 맨 글자였다(스크린샷의 「주요 링크」 표 — 주소가 그대로 드러나 있었다). ① 본문 — [글](주소) 의 글과 맨 주소에 마크(lp-xl) 를 얹는다. 마크라 문서 글자는 한 자도 안 바뀐다(churn 0). 누르면 window.open(_blank) 로 연다 — 데스크톱 셸의 setWindowOpenHandler 가 받아 기본 브라우저로 넘기고 폰은 새 탭, 채팅 답변과 같은 계약이다. 🔴 캐럿이 그 링크 안에 있을 때 누르는 것은 편집이다(원문이 펴져 있는 상태) — 그때는 캐럿만 옮긴다. 보조키가 있으면 기본 동작(선택 넓히기)에 맡긴다. 파비콘 아이콘도 눌리게 했다 — 눌리게 생겼는데 안 눌리는 것이 가장 나쁘다. ② 표 칸 — 칸 안의 링크를 파비콘 + 누르면 열리는 조각(lp-cl, contenteditable=false)으로 그린다. 🔴 누르면 칸에 포커스가 가기 전에 연다(mousedown 에서 preventDefault) — 포커스가 가면 원문이 드러나 링크가 사라진다. 글자 자리를 눌러 칸에 들어가면 원문으로 풀리고, 나오면(blur) 글이 안 바뀌었으면 다시 링크로 그린다. ⚠ 링크 없는 칸은 종전대로 글자 하나만 둔다 — 글자를 다시 쓰면 클릭이 놓은 캐럿이 무효가 된다(Rondo 라운드 246 실측). ⛔ 되읽기는 라벨이 아니라 원문이다 — cellText 가 조각을 data-md([글](주소))로 읽는다. 안 그러면 칸을 들락거리기만 해도 주소가 사라진다. 스모크가 잰다: 본문·표의 표식과 파비콘 자리 · 표 링크를 누르면 열리고 칸은 편집으로 안 들어감 · 들어갔다 Esc 로 나오면 다시 링크 · 그동안 파일은 그대로(바이트 대조). 고치기 전 판에서 «본문 링크: 누를 수 있는 표식이 없다» 로 빨간 것을 확인했다. ⚠ 목업은 건너뛰었다 — 새 모양이 아니라 이미 있는 파비콘·밑줄 링크를 표까지 같은 꼴로 넓힌 것이고, 빠져 있던 «여는 손» 은 버그 쪽이다.

알림을 누르면 그 대화로 (2026-09-17, Dave: «알람 버튼 클릭하면 해당 세션으로 이동해야 해. 현재는 뭔가 버그가 있어»): 원인은 둘이었다. ① 🔴 창이 없을 때 셸이 목적지만 적고 창을 안 만들었다. 메뉴바 앱이라 창을 닫아 두는 게 보통인데, 그 상태에서 배너를 누르면 navigate() 가 pendingNav 에 적고 돌아갔다 — 아무 일도 안 일어나고, 한참 뒤 트레이로 창을 열면 그제야 묵은 목적지로 튀었다. 이제 창이 없으면 만들고, 목적지를 첫 로드 URL 에 함께 싣는다(desktop/nav.js 의 withHash). URL 에 싣는 이유: 호스트 모드의 첫 로드는 #token=… 을 달고 오고 화면은 토큰을 떼며 한 번 리로드한다 — 창이 뜬 뒤 해시를 따로 놓으면 그 리로드와 경합해 지거나 이긴다. 같은 해시에 실으면 화면이 토큰만 떼고 bot·s 는 남긴다(App.tsx 의 토큰 처리가 그렇게 돼 있다). ② 호스트가 자기 배너를 하나 더 띄웠다. 데스크톱이 호스트를 안에서 돌려도 호스트의 Notifier.mac()(terminal-notifier·osascript)이 그대로 돌아 같은 사건에 배너가 둘 떴고, 그쪽은 눌러도 브라우저 첫 화면이 열리거나(terminal-notifier) 아무 일도 없었다(osascript). 앱 안 호스트에는 FOLDERBOT_NO_MAC_NOTIFY 를 켠다 — 배너는 앱이 띄우고 앱이 연다. 터미널로 띄운 호스트(앱 없음)의 배너는 그대로 두되, 열 곳을 첫 화면이 아니라 그 대화(#bot=…&s=…)로 바꿨다. 정책은 순수 함수(navHash·withHash)로 빼 유닛이 고정한다 — 셸 자체는 스모크가 못 띄운다. 폰 푸시(sw.js)는 이미 #bot=…&s=… 로 열고 있어 손대지 않았다.

설정 다시 짓기 — 1안 «무엇의 설정인가 + 범위 배지» (2026-09-17, Dave: «현재 설정 메뉴가 엉망진창임. 뭐가 뭔지도 모르겠음. 종류별로 정리가 필요함 … claude의 경우 새채팅 기본 권한 설정도 빠져있음. 모두 먼저 table로 정리»): 먼저 아홉 칸 43줄을 표로 전수 조사했다(어느 칸 · 어떤 조작 · 어디에 저장 · 어디서 보이나). 드러난 것 — ① 에이전트 칸 하나에 19줄(기본값·Claude 인증·Codex·커넥터·스킬·토큰이 뒤섞이고 Codex 로그인 줄이 둘) ② Claude 새 채팅 기본 권한이 없다 — 입력창 모드 팝오버가 «새 세션은 설정의 기본값으로» 라고 약속하는데 그 설정이 없었다(Codex 는 샌드박스가 있어 비대칭) ③ «권한» 이 세 뜻(Codex 샌드박스 · macOS 권한 · 세션 권한 모드) ④ 조용한 시간이 고칠 수 없는 글자(호스트 quiet 는 있었다) ⑤ 메인 것과 이 기기 것이 표시 없이 섞임 ⑥ 기기 항목이 세 칸에 흩어짐 ⑦ 하네스·커넥터는 설정이 아니라 보기 ⑧ 검색 인덱스가 수동이라 절전·진단이 안 걸림. Dave 가 세 안 중 1안을 골랐다. 가르는 기준을 «언제 만지나»(V25)에서 «무엇의 설정인가» 로: 일반(이름·테마·크기) · 호스트·볼트(루트·주소·Tailscale·깔린 CLI·macOS 권한) · 기기(이 기기 이름·연결된 기기·페어링·로그아웃) · Claude(인증 → 새 채팅 기본값: 모델·생각·권한 모드 → 연결 → 위험한 것) · Codex(같은 뼈대 · 깔렸을 때만) · 세션·사용량(절전·훅·예산) · 알림(푸시·조용한 시간 편집·테스트) · 할 일(폰에서만 목차에) · 참고(커넥터·스킬·하네스 — 보기만, 맨 아래). 🔴 줄마다 범위 배지 [메인]/[이 기기] — 폰에서 바꿨는데 맥이 그대로인 이유가 이제 줄에 적혀 있다. 🔴 새 채팅 기본 권한 — 호스트 defaultPermissionMode, /api/defaults 의 permissionMode, 값 없이 만든 Claude 세션이 이걸로 뜬다. 명시값·루틴의 승인 설정이 이긴다. default 는 «안 정함» 이라 지운다, 모르는 값은 버린다. «항상 허용» 을 고르면 줄에 경고가 붙는다. 조용한 시간은 /api/quiet(HH:MM 둘, 틀리면 거절). ⚠ 검색 인덱스는 여전히 손으로 적되 유닛이 실제 <Row t=…>·그룹 제목과 대조한다(settingsIndex.test.ts) — 줄을 더하고 인덱스를 빼먹으면 빨개진다. AgentPick 의 «모델 바꾸기» 는 고른 벤더의 칸으로 간다. 스모크: 목차 이름·옛 칸 부재·참고 칸 맨 아래 · 범위 배지 · 호스트 칸의 CLI · 기기 칸의 페어링과 맨 아래 로그아웃 · Claude 칸의 순서(인증이 기본값보다 앞) · 참고 칸 보기 전용 · 검색 4단어(토큰·절전·진단·권한) · API 로 기본 권한(저장·상태·새 세션·명시값 우선·모르는 값)·조용한 시간(저장·틀린 시각 거절). ⚠ 목업은 건너뛰었다 — Dave 가 표에서 안을 고르고 «1안대로» 라고 지정했다.

폰 화면 넘기기 · «최신으로» 단추가 튀던 것 (2026-09-17, Dave: «화면을 눌러서 오른쪽으로 슬라이딩하면 뒤로 가기, 왼쪽으로 슬라이딩하면 폴더로 이동 … '최신 대화로 바로 가기' 화살표를 클릭하면 위치가 갑자기 오른쪽으로 살짝 이동하는 버그»): ① 넘기기 — 대화 화면(.col.chat)에서 오른쪽으로 끌면 뒤로(서브에이전트 드릴인 중이면 드릴에서 나온다), 왼쪽으로 끌면 «이 폴더에서». 판정은 swipe.ts 의 navOf(순수 · 유닛 5건): 72px 또는 폭의 22% 이상, 그리고 가로가 세로의 1.6배 이상이어야 한다 — 비스듬히 읽어 내려가다 화면이 날아가면 안 된다. 첫 10px 이 세로면 그 끌기는 끝까지 스크롤이다. ⛔ 입력칸·칩·발판·메뉴에서 시작한 끌기는 넘기기가 아니다. 폰에서만 붙는다. ② «최신으로» 단추 — 원인은 .rb:active { transform:scale(.96) } 이 .tobot 의 transform:translateX(-50%) 를 덮어쓴 것. 누르는 순간 가운데 맞춤이 사라져 오른쪽으로 반 폭 튀었다(CLAUDE.md §5 «같은 속성을 나눠 쓰지 않는다 — translate·scale 은 독립 속성» 의 실사례이고, 같은 꼴의 .pinq 도 함께 고쳤다). 가운데 맞춤을 translate:-50% 0 으로. 스모크: 폰에서 오른쪽 끌기 → 홈 · 비스듬한 끌기 → 그대로 · 왼쪽 끌기 → 패널, 그리고 .tobot 규칙이 translate 인지(transform 이면 빨강) + 단추가 떠 있으면 눌러서 중심이 1.5px 이상 안 움직이는지.

권한 — 바꾼 모드는 그 자리에서 · «항상 허용» 은 늘 있다 (2026-09-18, Dave: «중간에 권한을 바꿨는데 그 이후에도 계속 실행하기 전에 물어보네. 그리고 처음에 디폴트가 오토였는데도 허용해줘야 되는 상황이 너무 많아»): 진단은 둘로 갈렸다. ① 🔴 --permission-mode 는 스폰 인자다 — 살아 있는 워커에는 넣을 수 없어 configure() 가 restartPending 만 켜고 턴이 끝나길 기다렸고, 그 사이 오는 물음은 옛 모드 그대로였다. 게다가 턴이 끝나도 pending 이 남아 있으면 재시작을 건너뛰면서 restartPending 을 지워 버려 다음 턴도 옛 모드였다. 칩은 새 값을 바로 보여 줘 «바꿨는데 왜 물어봐» 가 됐고, «턴이 끝나면 재시작» 힌트는 데스크톱 푸터에만 있어 폰에서 안 보였다. ② «자동» 은 Claude Code 의 default — 이름과 달리 쓰기·실행은 전부 묻는 게 기본이고, «이 세션에서 항상 허용» 단추는 CLI 의 permission_suggestions 가 있을 때만 떴는데 복합 Bash(cd x && npm test | tee)는 제안이 비어 와서 단추 자체가 없었다. 처방(1안): 호스트가 모드를 대신 집행한다 — core/permPolicy.ts 의 autoAllows(mode, tool) 로 «항상 허용» 이면 질문 빼고 전부, «편집 자동 수락» 이면 편집 도구만 호스트가 «허용» 을 누른다. 모드를 바꾸는 순간 화면에 남은 카드도 같은 기준으로 닫고(configure), 그 턴 동안 새로 오는 물음도 그렇게 답한다(autoAllow). 워커를 턴 중간에 죽이지 않는 이유는 하던 일이 끊겨 «계속해» 를 다시 쳐야 하기 때문이다 — 답만 대신하고 턴이 끝나면 새 모드로 --resume 재시작한다. restartPending 은 실제로 내려갔을 때만 지운다. 기록은 남긴다(«자동 허용 · Bash (항상 허용 모드)»). 칩에는 그 사이 «적용 중» 배지(폰 포함)와 설명 툴팁·모드 팝업 힌트. «항상 허용» 은 CLI 제안이 비면 호스트가 만든다(fallbackRules) — Bash 는 조각마다 첫 낱말 접두어(Bash(cd:*) · Bash(npm:*) · Bash(tee:*)), 다른 도구는 도구 전체, 질문은 없음. 카드 아래에 «항상 허용 = …» 로 무엇이 열리는지 적고 시스템 줄에도 남긴다. 부수 발견: 시스템 줄(.meta > span.tx:first-child)이 flex:none 에 걸려 긴 문구가 폰 폭을 밀었다(실측 409/390) — 첫 자식이어도 .tx 는 줄임표로 접는다. 검증: permPolicy 유닛 9 · 스모크 «모드 전환 → 대신 허용 → 턴 끝 재시작» · «bypass 에서는 사람에게 안 온다» · «빈 제안 → 접두어 규칙이 updatedPermissions 로 간다» · «긴 시스템 줄이 폰 폭을 안 민다». 아직 모르는 것: 인라인 --settings JSON 이 사용자 settings.json 의 allow 규칙과 병합되는지 — Dave Mac 의 실 CLI 로 확인해야 한다.

파일이 트리에 «바로» 오지 않던 것 — 신호는 쓴 뒤에, 폴더는 호스트가 본다 (2026-09-18, Dave: «folderbot 과 대화를 통해 파일이 생성되는 경우 원격환경에서 생성된 파일이 폴더에 바로 반영이 안되는 문제»): 원격 전용 코드가 따로 있는 게 아니라 파일 반영 경로 자체에 구멍이 셋이었다. ① 🔴 «파일 바뀜»(files 프레임)이 쓰기 요청 시점에 나갔다 — 모델이 Write 를 부르는 assistant 줄에서 쏘고, 실제 쓰기(권한 확인 → 실행 → tool_result)는 그 뒤였다. 화면은 신호를 받자마자 ls 를 쳐 아직 없는 파일을 못 보고 끝났고, 쓰기가 끝나는 tool_result 에서는 아무 신호가 없었다. Write 가 권한을 묻는 자동 모드에서는 100% 재현. ② 🔴 호스트에 폴더 감시가 없었다 — Bash(cat >·mv)·MCP·Dropbox 동기화·Finder 가 만든 파일은 신호 자체가 없었다. ③ Codex 는 아예 신호가 없었다 — file_change 를 Edit 로 바꾸며 file_path 를 안 실어 touchedPath 가 null. 처방(1안): 신호는 tool_result 성공 시점으로 옮기고(요청 줄은 채팅 칩만), 턴 끝에 한 번 더(안전망, wroteThisTurn). 호스트가 봇 폴더를 fs.watch(recursive) 로 본다(host/watch.ts · 봇마다 하나 · 300ms 디바운스 · 겹치는 루트/하위 봇은 둘 다 알림 · .folderbot·.git·node_modules·.DS_Store 는 core/fileWatch.ignoredChange 로 제외, .claude/ 는 신호). Codex 는 changes[].path(옛 판은 키)를 file_path·paths 로 싣는다. 검증: 스텁이 실 CLI 처럼 tool_use 뒤 150ms 에 파일을 쓰게 바꿔 「files 프레임 시점에 파일이 디스크에 있다」(수정 전 빨강 실측 [false]) · 「세션을 거치지 않은 쓰기도 files 프레임」 · 감시자 유닛(디바운스 · 무시 폴더 · 봇 제거) · Codex 매핑 유닛.

폰 홈 폴더 행 쓸기 — 고정 · 메뉴 · 은퇴, 자리는 고정 (2026-09-18, Dave: «이 내용은 모바일 화면에서 todo 처럼 슬라이딩으로 기본값 고정해서 만들어줘»): 데스크톱 레일 우클릭의 세 가지(맨 위에 고정 · 지우기(연결 해지) · 은퇴)가 폰 홈에는 아예 없었다(폰 행은 누르면 열릴 뿐). 할 일 행의 «쓸어서 처리»(V16) 장치를 client/SwipeRow.tsx 로 떼어 내 폴더 행에도 붙였다 — 판정은 종전 그대로 swipe.ts(slotOf·actOf)·gesture.ts(decide)의 순수 함수. 자리는 고정(FOLDER_SWIPE, 설정에 노출하지 않는다): →짧게·길게 = 고정/고정 풀기(가장 잦고 되돌리기 쉬운 일) · ←짧게 = 메뉴 시트(세 가지 전부) · ←길게 = 은퇴(확인창 askConfirm 을 거친다). 지우기(연결 해지)는 메뉴에서만 — 쓸기 한 번에 레일에서 사라지는 일은 두지 않는다. 관제(오케스트레이터) 행은 쓸리지 않는다. 🔴 행이 <button> 이라 pointerup 뒤 click 이 따라와 «고정» 하려다 폴더가 열리는 것을 onClickCapture 에서 한 번 삼킨다. 예고 글자는 상황에 맞춘다(고정 ↔ 고정 풀기). 검증: 스모크 「←짧게 → 시트에 고정·지우기·은퇴 · →길게 → 예고 «고정» · 손을 떼도 폴더가 안 열림 · /state 에서 pinned · 관제는 .swwrap 이 아님」(수정 전 빨강 .swwrap · 0).

레일 이름 파생 — 1안 (F · 2026-09-19, Dave: «폴더명은 마감 데이터라 바꿀 수 없으니 표시 이름만 따로 만든다»): 폴더명 YYYY[-MM[-DD]]_[타입-]이름 을 파싱만 한다(core/botName.ts — parseFolderName·dueChip, 규칙 밖은 통째로 제목, 빈 이름은 없다). 타입은 목록(기본 강의·컨설팅·코칭·멘토링·모임·행사·책쓰기·자격증, 규칙 yaml types: 로 대체)에 있을 때만 뗀다(«멋사» 는 타입이 아니다). Registry.toBot 이 displayName·kind·due 를 싣고, 봇 폴더 CLAUDE.md frontmatter display_name: 으로 제목만 덮는다(볼트 안이라 다른 맥에서도 따라온다 · mtime 캐시 · 파일이 바뀌면 refreshNames 가 레일도 갱신). 레일 BotName: 제목 굵게 · 타입 태그 · 오른쪽 날짜 칩(M/D D-n · M월 · YYYY, D-3 이내 강조, 지남 흐림) — 안 들어가면 태그를 통째로 접고(반쯤 보이지 않는다, ResizeObserver) 제목 끝을 자르며 칩은 끝까지 남는다. 툴팁은 원 폴더명. 헤더·폰 홈·호버 카드도 같은 이름. 정렬·검색·rel 은 폴더명 그대로, bots_list 에 displayName 만 더했다. 부수 수정: 깊은 봇(경로 줄이 있는 행)이 한 줄 flex 에서 경로에 밀려 제목이 0 폭이 되던 것 → 두 줄. 검증: 유닛 11 · 스모크(예시 3 · D-2 · 지남 · 잘림 · display_name · bots_list) · 시안과 나란한 스크린샷 test/tmp/rail-compare.png.

원격에서 파일 열기 — 그 기기에서 (E · 2026-09-19, Dave 1안 확정: «원격이어도 로컬처럼 느껴져야 한다»): 파일을 여는 주체가 언제나 호스트라 원격 맥에서 «Finder 에서 보기» 를 누르면 메인 맥에서 열리고 원격 사용자는 아무것도 못 봤다. 원리 하나로 푼다 — Dropbox·iCloud 는 폴더 구조를 그대로 옮기므로 이 기기의 볼트 루트 + 볼트 기준 상대 경로 = 같은 파일. 한 함수 client/localOpen.openOnThisDevice(bot, rel, 'open'|'reveal') 에 트리 «Finder 에서 보기» 와 문서 «열기» 를 모았다(«끌어다 첨부» 는 자리만). 메인(호스트 맥)은 종전대로 호스트가 연다(설정 없음). 원격 Electron 은 기기 설정(settings.json openMode)대로: ① sync — 이 기기의 동기화 볼트(vaultLocal)에서 신선도 확인 뒤 연다. 신선도는 mtime 이 아니라 크기 + 앞 64KB sha256(동기화가 mtime 을 바꾼다 · 호스트 GET /bots/:id/stat ↔ 셸 localfs.stat). 다르면 시트 — 기본 [기다렸다 열기](폴더 감시+폴링으로 도착하면 자동으로 연다, 60초 한도) / [그래도 열기] / [호스트에서 받기]. iCloud «클라우드에만» 은 .<이름>.icloud 자리표시자를 보고 brctl download 뒤 기다렸다 연다. 볼트에 아직 없으면 ② 로. ② download — 호스트 raw 에서 받아 <userData>/remote-cache/<호스트>/<rel>(Dropbox 폴더 밖 · .. 로 못 올라감)에 두고 열며, 문서 탭에 「사본」 배지(고쳐도 되돌아가지 않는다). 브리지가 없는 브라우저 탭은 내려받기로 끝내고 폰은 «이 기기에서는 열 수 없어요». 온보딩: 권한 관문에 원격 전용 한 단계 「이 기기에서 파일 열기」 — 호스트 루트의 꼬리(PARA · Dropbox/PARA · …)를 ~/Library/CloudStorage/Dropbox*(직계 자식 = 팀 Dropbox 포함) · ~/Dropbox · iCloud 에서 찾아 realpath 로 합치고 파일 수 내림차순 으로 보여 준다 — ~/Dropbox 심링크는 정본으로 합쳐지고 Dropbox-Cbsjin/…/PARA 껍데기는 «파일이 거의 없어요» 로 뒤에 선다(Dave 실측 환경 · 유닛으로 재현). 설정 › 기기 에서 모드·위치를 언제든 바꾸고 즉시 반영. 셸: desktop/localfs.js(electron 을 안 불러 유닛이 그대로 돈다) + IPC/preload local.*. QA 시임: 인증 없는 호스트에 x-fb-as 헤더를 붙이면 «원격 기기» 로 본다 — 스모크가 가짜 브리지의 호출 기록으로 계약(온보딩·같음·다름→기다림→열림·iCloud·②캐시·설정 즉시·메인 불변)을 잰다.

오케스트레이터가 레일 순서를 정한다 — bots_reorder (A · 2026-09-19, Dave 추천안 승인: «재정렬이 오면 «직접» 자동 전환 · 안내는 orchestrator.md 만»): 레일 차례는 사람이 끌어 놓는 것뿐이었다. 관제 봇에게 «지금 급한 폴더를 위로» 를 시킬 도구가 없었다. ① bots_reorder {order:[rel…], restore?} — 위에서부터 놓을 rel 목록(id·폴더명도 받음), 안 준 봇은 기존 차례로 뒤에. 🔴 모르는 rel 이 하나라도 섞이면 통째로 실패하고 순서는 그대로다 — 사람의 드래그는 낡은 화면을 봐주지만(모르는 id 무시), 도구는 잘못 부른 것이라 반쯤 적용하면 «왜 이렇게 됐지» 가 된다. ② 사람이 끌어 놓은 봇은 자리를 지킨다 — 드래그가 moved 를 보내 그 봇에 orderedBy:'user' 를 찍고, 도구는 그 칸(index)을 비워 둔 채 나머지만 채운다. 풀려면 레일 우클릭·폰 시트의 「순서 고정 해제」(POST /bots/unfix). ③ 첫 재정렬 전 차례를 bots.yml 의 orderBackup 에 남기고 {restore:true} 가 거기로 돌리며 표식을 전부 지운다. ④ bots_list 에 order(0부터, 오케스트레이터 제외)·orderedBy(user/orchestrator/null). ⑤ 🔴 재정렬 프레임이 오면 그 기기의 레일 정렬을 «직접» 으로 자동 전환한다(bots 프레임의 reorderedBy) — 기본값 «이름» 인 기기에서는 도구가 성공했다는데 화면이 그대로인 일이 생긴다. ⑥ 안내는 ORCHESTRATOR_MD 「하는 일」 7번 한 줄 — 볼트 CLAUDE.md 에 도구표를 새로 만들지 않는다(이미 만들어진 볼트의 orchestrator.md 는 안 덮으므로 도구 스키마 설명이 안내를 겸한다). 검증: test/unit/registry.test.ts(임시 볼트 · 실패=그대로 · 자리 유지 · restore · 다시 읽기) + 스모크 「A bots_reorder」(MCP 호출 → 레일 차례 · «직접» 자동 · 메뉴 「순서 고정 해제」).

문서 창으로 가는 문은 하나 — openInDocPane · rondo_open/rondo_reveal (C · 2026-09-19, Dave 추천안 승인): «채팅에서 문서를 클릭했는데 창이 닫혀 있으면 안 열린다» 의 실측 원인은 창이 아니라 경로였다 — 답변 속 칩의 rel 을 Item.onFile 이 절대경로로만 받아 relOf() 에서 버렸다(창이 열려 있어도 같았다). 처방은 문을 하나로 모으는 것: App.tsx openInDocPane(pathOrRel, {pin, source, turnKey}) 가 칩·트리·채팅·에이전트의 열기를 전부 받아 절대경로·rel 을 모두 풀고, 창이 닫혀 있으면 연다(볼트 밖은 토스트 거부). 에이전트 쪽은 MCP COMMON 도구 rondo_open {path}·rondo_reveal {path} → 호스트가 볼트·참조 폴더 안인지 확인 → SSE doc 프레임(botId·sid·rel·action·turn) → 그 봇을 보고 있는 화면이 같은 함수로 연다. 🔴 에이전트 열기는 같은 세션·같은 턴에 한 번 — ${sid}:${turn} 을 기억해 두 번째부터는 무시한다(도구를 루프에서 부르면 창이 깜빡이며 탭이 갈린다). 열린 창의 미리보기 탭 교체 규칙은 그대로. 검증: 스모크 «닫힌 창 + 칩 → 열림» · «rondo_open → 열림 · 같은 턴 두 번째 무시 · 볼트 밖 거부» · «열린 창 규칙 불변».

폴더 밖 문서 보기 — 읽기만 · 참조 폴더로 추가 (D · 2026-09-19, Dave 추천안 승인): 볼트 안이지만 이 봇 폴더 밖 인 파일(예: 1. Inbox/…)은 문서 창이 열지 못했다(rel 이 봇 폴더 기준이라 표현할 수 없었다). core/paths.botRelOf(botAbs, root, abs) 가 그런 경로를 ../ rel 로 풀고(볼트 밖이면 null), 문서 창은 ../ 로 시작하면 「폴더 외」 배지(.scp.out) + 띠(.outband: 볼트 기준 경로 · «읽기만» · «이 Folderbot 에 참조 폴더로 추가») + 읽기 전용 렌더로 연다 — 봇 폴더 밖을 편집기로 고치게 두면 «누가 이 파일을 건드렸지» 가 된다. 참조 폴더 추가는 POST /bots/:id/repo → Registry.setRepo(볼트 안 폴더만 · 비어 있을 때만 — 참조 폴더는 봇당 하나 · 빈 값이면 풀기) 로 그 파일의 부모 폴더를 붙인다. 볼트 밖 경로는 클라이언트 토스트(«볼트 밖 경로예요»)와 호스트 도구 거부 두 겹. 폴더 안 파일의 동작은 바뀌지 않았다(배지·띠 없음 · 편집기 그대로). 검증: 유닛 botRelOf + 스모크 «폴더 밖 문서 · 볼트 경로 띠 · 폴더 안 불변».

PDF 칩 — 원인은 ⓑ, 파일명만 있어도 찾는다 (G · 2026-09-19, Dave 추천안 승인: C 의 함수 위에 · 원격은 호스트 스트리밍): 실측(test/repro-chip.mjs)으로 세 가설을 갈랐다 — ⓐ 뷰어 아님(트리에서 연 pdf 는 탭+iframe 뷰어가 뜬다) · ⓑ 칩 클릭이 경로 해석에서 버려진다(pdf·md·png·폴더 밖 전부, C 와 같은 뿌리) · ⓒ 창 상태 무관. 처방: 칩 클릭은 전부 C 의 openInDocPane 로. 파일명만 적힌 것(설명서.pdf)도 core/paths.bareFileNames(아는 확장자만) 로 후보가 되고 호스트 /exists 가 봇 폴더 → 참조 폴더 → 볼트 순으로 찾는다(files.findFiles, 깊이 8 · 여럿이면 matches → 고르기 시트 fb:pickfile). 🔴 «확인 없이 칩을 만들지 않는다» 는 기존 규칙 그대로 — 못 찾은 이름은 애초에 칩이 안 된다. 뷰어: PDF 는 기존 iframe(Chromium 내장 · 이동·확대) · 이미지 img · 그 밖(docx…)은 「미리보기 없음 + 외부에서 열기 ↗」(E 의 openOnThisDevice — 그 기기에서 연다). 원격은 뷰어 src 가 호스트 raw(토큰 인증) 스트리밍이라 그대로 뜬다. 검증: 스모크 «pdf→뷰어 · png→img · docx→미리보기 없음 · 파일명만(찾기 · 2곳이면 고르기) · 원격 raw 200».

입력창 줄내림 — IME 가 아니라 자리표 (I · 2026-09-19, Dave: «Shift+Enter 를 두 번 눌러야 줄이 바뀐다»): 실측(test/repro-enter.mjs)으로 IME 가설을 걷어 냈다 — 영문·빈 칸에서도 똑같이 두 번이었다. 원인은 contenteditable 입력창의 끝 줄바꿈: pre-wrap 에서 텍스트 끝의 \n 은 줄로 그려지지 않고, 이어 치는 글자에 흡수돼 사라진다. render() 는 «구조가 같으면 손대지 않는다» 라 자리표 <br> 을 못 붙였다. 처방은 ensureTail() 한 곳 — 끝이 \n 이면 <br> 을 두고 아니면 뗀다(insertText 직후 · render 의 같음 경로). 🔴 글자 노드 한가운데 넣으면 크롬이 노드를 셋으로 쪼개(«abc»·«\n»·«») 빈 꼬리가 끝 판정을 속였다 — normalize() 로 합친 뒤 캐럿을 글자 수로 다시 놓는다. 조합 중의 ⇧⏎ 는 크롬이 IME 에 넘겨 확정만 하므로 키를 기억해 두었다가 compositionend 에서 줄을 넣는다(한 번에 확정+줄 · 기본 삽입은 막는다). Safari 순서(compositionend→⏎)는 이미 온전했다. I-2(폰에서 줄이 늘면 마지막 메시지가 컴포저에 가려짐 · 실측 52px)는 컴포저 높이 ResizeObserver 가 «맨 아래였으면 따라 붙는다» 를 같이 한다 — 스크롤 컨테이너의 옵저버는 상자 크기만 보므로 패딩 성장을 못 봤다. 검증: 재현 5조건 전부 초록 · 스모크 「I 줄내림」(⇧⏎ 한 번 · 빈 줄 유지 · 조합 중 · ⏎ 보내기 불변 · 폰 줄 5번).

채팅 렌더링 — 위키링크는 공유 렌더러, 코드는 토막 내지 않는다 (K · 2026-09-19): 채팅은 ![[그림.png]] 을 대괄호 그대로 보였고(스크린샷 1236), 경로 칩 걷기가 코드 조각 안까지 들어가 «PARA/ · 첨부 · /…png» 로 토막 냈다. ① core/wikilinks.ts — 코드(펜스·인라인) 밖에서만 ![[x]]→<img class="wimg" data-wiki> · [[노트|별명]]→<a class="wlink" data-wiki> 로 바꾸는 순수 함수. 이름만 달고 어디 있는지는 호스트가 답한다(/exists — 파일명만이라 봇 → 참조 → 볼트 순으로 찾고, 확장자가 없으면 .md 도 찾는다). 그림은 호스트 raw 를 src 로(원격도 같은 길) · 눌러서 문서 창 · 노트는 눌러서 openInDocPane · 없으면 「찾을 수 없음」(로드 실패도 같다). ② 🔴 마크다운 → HTML 은 client/render.ts renderMarkdown() 한 곳 — 수식 걷기 → 위키링크 → marked → 수식 끼우기. 채팅 Md 와 문서 창의 인쇄·PDF 사본이 같은 함수를 부른다(유닛: 같은 입력 → 같은 출력). ⚠ 문서 편집 화면은 CodeMirror 라 제 장식을 쓴다 — HTML 을 그리는 길만 하나다. ③ K-2 — 칩 걷기가 code 를 건너뛴다(2026-09-15 의 «통째 경로인 코드 조각은 요소째 칩» 은 그대로), 코드 밖에서는 긴 경로에 포함되는 짧은 후보를 버려 통째 칩 하나만 남긴다. 검증: 유닛 wikilinks·render + 스모크 「K 채팅 렌더」 + 1236 메시지 재전송 test/tmp/k-1236.png.

사용량이 0 이던 것 — 스캔 상한이 최근 기록을 밀어냈다 (L · 2026-09-19, Dave: «메뉴바 usage 패널에 세션 사용량이 반영 안 돼»): 원격 패널이 «아직 쓴 게 없어요 · 쓴 0 · 이번 주 남은 $0.00» 이었다(스크린샷 1238). 원격은 호스트 /api/usage 를 그대로 그리므로 문제는 호스트다 — ~/.claude/projects 를 훑는 walkJsonl 이 파일 400개에서 멈췄고, 기록이 많은 사람은 옛 파일이 그 자리를 다 채워 오늘 기록이 목록에 못 들어왔다(유닛으로 재현: 옛 450개 + 최근 1개 → 도구 줄이 아예 비었다). 처방 ① mtime 이 7일 안인 파일만 모은다(개수 상한은 그 안에서 2000 · stat 만 하므로 싸다). ② 사건에 sid(기록 파일 이름 = CLI 세션 id)를 붙이고 report() 가 byBot 을 낸다 — 게이트웨이가 세션 목록으로 sid→봇을 잇고, 모르는 기록은 «그 밖 (터미널 등)». ③ 턴이 끝나면(state running→idle) 호스트가 30초 캐시를 비우고, 클라이언트는 같은 프레임에서 fb:usage 를 쏴 0.8초 뒤 다시 묻는다 — «60초 안에» 가 아니라 «턴 끝 직후» 다. ④ 예산 0 은 «다 썼다» 가 아니라 «예산 없음» — left:null → 화면 «—»(설정 › 한 주 예산에 안내). ⑤ 카드 밑줄에 예산 출처(설정/기본값). 트레이(메뉴 막대)도 같은 UsageCard 라 숫자가 갈릴 곳이 없다. ⚠ Dave 맥의 파일 수는 직접 못 셌다 — 400 을 넘는지가 이 원인의 확인점이다. 검증: 유닛 usageScan(재현·byBot·null) + 스모크(byBot API · week:0 → null · 턴 끝 뒤 카드에 봇 줄) · test/tmp/phone-usage-bybot.png.

이미지 확대·복사 · 파일 복사 → 붙여넣기 (M · 2026-09-19, Dave: «이미지 확대·축소·복사와 첨부파일 복사 → 다른 곳에 붙여넣기»): 문서 창의 그림은 맞춤 한 장뿐이었고, 파일은 «경로 복사» 밖에 없었다(카톡에 e-ticket 을 보내려면 Finder 까지 가야 했다 · 스크린샷 1245). ① 뷰어 — 수학은 core/zoom.ts 한 곳(커서·손가락 아래 그림 점을 고정한 채 배율만 바꾸는 zoomAt · fitScale · clampView), 화면은 ImageView(트랙패드 핀치=ctrl+휠 · 휠 끌기 · ⌘+/−/0/9 · 두 손가락 핀치 · 더블탭 맞춤↔2배 · 배율 막대). 🔴 확대 상태면 data-consume-x 를 단다 — H 의 서랍 쓸기가 이 표식을 보고 양보한다. ② 이미지 복사 — clip.copyImage 한 함수: 맥 앱은 셸 clipboard.writeImage(호스트는 파일에서, 원격은 raw 를 받아서), Safari 는 ClipboardItem 에 Promise(제스처 안에서 write 를 먼저), 그 밖은 PNG 로 구워서. 못 하면 «이 기기에서 열기» 로 안내. ③ 파일 복사 — 길은 기기가 정한다(fileCopy.copyFiles): 호스트 맥 앱은 경로 그대로 macOS 파일 클립보드(NSFilenamesPboardType plist + public.file-url → Finder ⌘V·카톡 첨부) · 원격 맥 앱은 호스트 manifest(재귀 목록·용량·head)로 50개/200MB 를 넘으면 먼저 묻고, 캐시에 스트리밍으로 받은 사본 경로를 넣는다(같은 사본이면 즉시) · 폰은 Web Share(폴더는 호스트 zip 스트리밍, /usr/bin/zip) · 브라우저 탭은 내려받기. 트리 메뉴 «파일 복사» = ⌘C(⌥⌘C 가 경로), 폰은 «공유…». ④ 진행 — 용량·속도·남은 시간·[취소]; 받기는 .part 로 하다가 제자리로, 취소하면 부분 파일이 없다(유닛). 캐시는 2GB 상한 LRU — 판정은 core/cache.evictPlan 이고 셸 localfs.evictPlan 이 같은 규칙(유닛이 둘을 같이 잰다), 설정 › 기기에 크기·[비우기]. 🔴 덤 — 트리 메뉴가 길어져 아래로 잘렸다: Float 가 그린 뒤 높이를 재서 바닥을 넘으면 위로 올린다(포털 그릇이 body 에 붙는 useEffect 뒤에 재야 한다 — layout effect 시점엔 크기가 0). ⚠ 맥 클립보드·Finder·카톡 붙여넣기는 Linux 에서 못 잰다 — 가짜 셸 브리지로 «어느 길로 무엇을 넘기나» 까지 스모크, 실기기 확인은 Dave 몫(todo.md › 확인 필요).

폰 입력창 — 카톡처럼 (N · 2026-09-19, Dave: «첫 줄이 상자 위로 잘린다 · 좁은 기둥 · 버튼이 사라진다 · 한 장만 · 4단계»): 폰 컴포저는 데스크톱의 «한 줄 행(+ · 글 · ● · 보내기)» 을 그대로 썼다 — 글이 + 옆 좁은 기둥으로 흘렀고(IMG_2002), 상자가 가운데 정렬이라 자라면 첫 줄이 위로 잘렸다. ① 그리드(composer.ph): 칩 행 / 글 전폭 / 아래 줄(📷 · + … ● · 보내기), 한 줄일 때만 버튼과 같은 줄(multi 가 영역을 바꾼다). 글자 16px·1.6 = 본문, 상자 최대 40%(--vvh 기준). ② 🔴 폰은 글에 @토큰 을 안 넣는다 — 칩은 글 위 별도 행(썸네일 · 진행 링 · ✕)이고 목록이 곧 첨부다(보낼 때 그대로 붙는다). 토큰↔목록 동기화 효과는 폰에서 끈다. ③ 여러 장: core/attach.attachRoom(10개 · 합계 50MB, 이유를 돌려준다) · uploadFile 을 XHR 로 바꿔 진행% · 호스트는 같은 이름에 _2. ④ 📷 는 image/* multiple 로 2단계, + 첫 줄은 「카메라로 찍기」(capture), 안내는 기기별(폰에 ⌘V 없음), 붙여넣기는 clipboardData.files 도 받는다. ⑤ N-2 질문 헤더 — 폰은 알약(오버레이) 대신 흐름 안 .qhdr(스크롤 영역이 그 아래 남는 자리를 채운다), 탭하면 3줄까지 펼침. ⑥ H-5 헤더 — ☰ · 표시 이름(F) · 작업 중 ●, 44px+safe-area, ‹·폴더 아이콘 없음(폴더는 쓸기·H 의 독, ☰ 은 H 전까지 봇 목록). 검증: 유닛 attachRoom + 스모크 「N」(첫 줄·전폭·버튼 하단·글자 일치·질문 헤더·3장 칩·✕·11개째·_2·📷·카메라·⌘V 없음·붙여넣기) · test/tmp/n1-sixlines.png n2-qhdr.png n3-chips.png n4-plus.png. ⚠ 안전 영역·iOS 붙여넣기·카메라는 실기기 미측정.

스트리밍 중 화면이 흔들리던 것 (O · 2026-09-19, Dave 녹화 실측: ±3px 왕복 12회 · 한 줄 반 점프 5회 · 원문 고정폭 · 오른쪽 잘림 · 상태 줄 위 빈 공간): 계측부터 했다(test/measure-stream.mjs — 10fps 프레임 시트의 행 밝기 프로파일 이동량 + 페이지 안 rAF 로 잰 프레임당 scrollTop 이동). 고치기 전: 폰 점프 12회·최대 59px, 데스크톱 7회·최대 75px — 실체는 토큰마다 scrollTop = scrollHeight 로 한 줄 반씩 «툭». ±3px 왕복은 헤드리스에서 재현되지 않았다(B 와 같은 후보: 유리 위 재래스터). 처방: ① 따라가기는 rAF 로 프레임당 ≤10px(followBottom), 사용자가 60px 이상 올려 봤으면 안 따라가고 「↓ 새 내용」, 다시 내려오면 재개. 브라우저 스크롤 앵커링은 끈다. 화면 하나 넘게 벌어진 간격(처음 열기)은 스트리밍이 아닐 때만 한 번에. 🔴 우리가 옮긴 스크롤은 «사용자가 올렸다» 로 세지 않는다(autoScrollRef) — 그리고 내용이 자란 직후 의 거리도 아니다(measure(true)), 이 둘을 빼먹으면 폰에서 따라가기가 아예 멈춘다(실측). ② 토큰 프레임은 33ms 에 한 번만 반영(≤30fps · store). ③ 점진 렌더 — core/mdBlocks 가 닫힌 블록/열린 마지막 블록을 가르고, 닫힌 것은 블록 단위 캐시로 렌더, 열린 것만 원문(pre-wrap). 🔴 열린 펜스는 처음부터 코드 블록으로 — 원문으로 두면 긴 줄이 여러 줄로 접혔다가 닫히는 순간 한 줄로 줄어 83px «툭» 했다(실측). ④ 높이가 변하는 것은 자리를 미리 잡는다 — 커서는 고정 높이 인라인 .scaret 하나(목록·코드 끝에 붙던 ::after 제거), 상태 줄 24px 고정 + tabular-nums, 본문과 8px 고정(스페이서를 상태 줄 뒤로), 상태 줄이 사라지면 그 높이만큼 --settle 여백. ⑤ overflow-wrap:anywhere — 코드·표만 가로 스크롤. 🔴 덤 — 자동 따라가기의 scroll 이벤트가 Float 메뉴를 한 프레임 만에 닫았다(스모크 E 가 잡음): data-autoscroll 요소의 scroll 로는 닫지 않는다. 고친 뒤: 프레임당 최대 10px, 10 초과 0회, 클램프 점프 0회, 상태 줄 24/8px 한 값, 넘침 0, 끝 재배치 0px. 녹화 test/tmp/o-phone-after.webm · 시트 o-sheet-phone.png. ⚠ B 와 원인이 같은지는 확인 못 했다 — 둘 다 헤드리스에서 재현 불가.

질문이 온 기기를 에이전트가 안다 (J · 2026-09-19, Dave: «발신 기기 인지 — 원격에서 물었는데 호스트 맥의 Finder 를 연다»): 에이전트에게 «누가 어디서 묻는가» 가 없었다. ① core/clientCtx.ts — 워커에게 가는 글 앞에만 <folderbot-client origin device tier touch canOpenOnDevice openMode/> 한 줄(withClient). 화면이 종류·손가락·열 수 있나·모드를 보내고, 🔴 origin·device 는 호스트가 정한다(clientOf(who)) — 화면이 «나는 호스트» 라고 우겨도 안 된다. 채팅에는 사람 글 그대로, from{device,main,tier} 만 붙는다. ② 규칙(DEVICE_RULES_MD)은 새 볼트 CLAUDE.md 머리말 과 시스템 프롬프트 둘 다 — 옛 볼트의 CLAUDE.md 는 건드리지 않으므로 프롬프트가 없으면 규칙이 없는 볼트가 생긴다. ③ 세션이 lastClient 를 기억하고 rondo_open/reveal 의 doc 프레임에 device 를 싣는다 — 화면은 자기 것(호스트는 'host', 원격은 기기 이름)이 아니면 무시한다. 그래서 원격 턴의 열기는 원격 문서 창에만, 폰은 안내만, 호스트에는 아무것도. ④ 채팅에 「원격 · macbook」/「폰 · iphone」 표식(호스트 메시지는 없음). 스텁은 블록을 떼고 읽되 «기기확인» 이면 되읊어 값을 잰다(Codex 스텁도 argv 기록에서 뗀다 — 옛 검사가 프롬프트를 통째로 비교한다). ⚠ 실 CLI 가 «원격에서 open 만 쓰는가» 는 여기서 못 쟀다(스텁) — Dave 실기기 확인. 검증: 유닛 clientCtx·registry(installRules) + 스모크 「J」(호스트/원격/폰 값 · 표식 · 요청 기기에만 열림 · 폰 안내).

칩이 줄을 깨뜨리던 것 · 키보드 열린 채 당기면 화면이 밀리던 것 (P · 2026-09-19, Dave 스크린샷 IMG_2012·2013·2014): 답변 속 경로 칩이 본문보다 크고(고정폭 · 세로 패딩) 줄 높이를 밀어 문단이 «턱» 지고, → 처리·3/5·v0.2.113 같은 글자가 칩으로 오탐되고, 폰 입력창의 첨부 칩이 글 사이에 끼어 줄을 갈랐다. 키보드를 띄운 채 채팅을 세게 당기면 iOS 가 문서 자체를 굴려 헤더·입력창이 화면 중간에 떠 있었다. P-1 칩은 글자처럼 앉는다 — 글꼴·크기 상속, 높이 1em + 2px, 세로 패딩 0 · margin 0, 테두리 1px · 모서리 4px, 아이콘 1em, 줄 폭 60% 상한에 nowrap, 긴 이름은 폭에 맞춰 가운데 생략(글자 수 어림 28자는 폰에서 넘쳐 CSS 끝 생략이 붙었다 — 붙은 뒤 한 프레임에 재서 두 글자씩 줄인다). 칩 있는 줄과 없는 줄의 높이가 같다(±1px, 스모크). P-2 후보 규칙은 core/paths 에 — 확장자 없는 상대 경로가 전부 숫자 토막(3/5·1/2/3)이면 탈락 · 코드 조각·코드 블록은 칩으로 바꾸지 않는다. 통째로 경로인 코드 조각은 code.code-path 로 모양은 코드 그대로, hover 배경만, 클릭·툴팁·여러 곳 고르기는 칩과 같다(09-15 «백틱도 칩» 규칙을 덮음 — 클릭이라는 목적은 그대로다). 유닛 chipDetect.test.ts 되어야 10 · 되면 안 됨 10. P-3 폰 첨부 칩은 글 위 28px 별도 행(6px 간격), 첨부가 없으면 행 자체가 없다. P-4 문서는 어떤 경우에도 안 구른다 — html,body { overflow:hidden; overscroll-behavior:none }, 구르는 것은 .chat-scroll 하나(overscroll-behavior-y:contain), 루트는 시각 뷰포트에 닻(top=offsetTop · height=height)을 내리고 visualViewport scroll 마다 scrollTo(0,0) 보험, 입력칸 focus({preventScroll}), 키보드가 내려가면 컴포저는 바닥으로. 계산은 core/viewport.planViewport/applyViewport 한 함수(I-2 · O · P-4, 유닛 viewport.test.ts). 덤으로 O 의 «우리가 옮긴 스크롤» 판정을 불리언에서 우리가 놓은 scrollTop 값으로 바꿨다 — 따라가기 프레임과 사용자의 위로 당기기 사이에서 사용자 scroll 이벤트가 «자동» 으로 먹혀 다시 끌려 내려가던 경주(O 스모크 간헐 빨강)를 없앴다. 실 iOS Safari · Android Chrome 은 헤드리스에서 시각 뷰포트를 흉내 내 쟀으므로 실기기 확인은 Dave 몫(## 확인 필요). 근거 test/tmp/p-chips-phone.png · p-composer-phone.png · p-keyboard-phone.png · p-keyboard.webm(test/record-p.mjs).

첨부는 + 하나 · 고정 질문은 «바로 위 질문» 라운드 알약 (Q · 2026-09-19, Dave 스크린샷: «카메라 아이콘이 생겼어 … + 하나만 있는 UX가 더 좋아 · 2스텝 안에 카메라/이미지첨부» · «바로 위쪽 질문 내용이 항상 상단 고정 · 더 올리면 그 이전 질문 · 페이드로 글이 덜 보임 · 이전처럼 라운드 칩»): N-4 의 📷 단추는 Dave 의 뜻을 잘못 읽은 것이었다 — 원한 것은 단추가 아니라 스텝 수다. 입력창 왼쪽은 다시 + 하나뿐이고, + 메뉴의 첫 두 줄이 「카메라로 찍기」(capture) · 「사진에서 고르기」(image/* multiple, 사진 보관함이 바로 열린다)라 둘 다 2스텝(+ → 줄)이다. 상단 고정 질문은 N-2 의 «흐름 안 헤더(.qhdr)» 를 폐기하고 종전의 유리 라운드 알약(.pinq, 폰 14px 모서리)으로 돌아간다 — 폰·데스크톱이 같은 부품 하나다. 고정되는 질문은 «마지막 질문» 이 아니라 화면 위로 지나간 마지막 질문(.umsg[data-id] 중 bottom 이 알약 자리보다 위인 마지막 것)이라 더 올리면 그 앞 질문으로 바뀌고 맨 위에서는 없다. 알약을 탭하면 그 질문이 알약 바로 아래에 오게 스크롤한다(맨 위에 붙이면 알약이 첫 줄을 가린다). 헤더 아래 페이드는 폰에서 10px 로 줄여 글을 가리지 않는다. 스모크 N 블록의 N-2·N-4 검사를 Q 규칙으로 갱신(4 질문 세션에서 알약 글자가 본문 1 → 본문 2 로 바뀌는 것 · + 메뉴 두 줄 · filechooser 가 바로 뜨는 것).

반응형 3단계 + 쓸기 내비게이션 (H · 2026-09-19, Dave 시안 확정 「반응형 3단계 시안」): 창이 좁아지면 봇 레일과 문서 패널이 그대로 줄어들어 읽히지 않았고 폰은 더했다. 이제 같은 컴포넌트가 창 폭에 따라 세 모습이 된다 — 어느 단계인지는 창 폭으로만(core/drawer.stageOf: 넓음 ≥1200 · 중간 768–1199 · 좁음 <768) 정하고 «폰이냐» 는 따지지 않는다(맥북 창을 줄여도 같은 모습). **넓음**은 지금 그대로 세 칸. **중간**은 왼쪽이 아이콘 띠(52px · 폴더 아이콘 + +·🔔 배지), 오른쪽은 채팅 가장자리에 떠 있는 **알약 독**(📄 문서 · ☑ 할 일 · 📁 파일 · ↗ 외부에서 열기 — 시안의 🧩 HARNESS 는 실제 앱에 없어 📁 파일로 대응, 이모지 대신 앱 아이콘). 띠 토글이나 독 아이콘을 탭하면 이름 있는 레일/그 칸이 열린 패널이 **채팅 위로 덮여** 나온다 — 채팅 폭은 안 변하고 어두워질 뿐(밀면 글이 다시 흘러 B 같은 흔들림이 또 난다). **좁음**은 띠도 숨기고 ☰ + 쓸기, 독은 작게. 쓸기는 [봇 레일 | 채팅 | 문서] **세 칸 띠**를 한 칸씩 옮기고 반대로 쓸면 한 칸 돌아온다 — 건너뛰지 않는다(문서 보다가 봇 바꾸려면 👉 두 번). 손가락을 따라 서랍이 같이 오고, 폭의 30% 를 넘게 끌었거나 빠르면(0.5px/ms) 열리고 아니면 되돌아간다. 어두워진 채팅 탭·Esc 로 닫히고, 레일에서 봇을 탭하면 닫히며 그 봇으로 간다. 서랍 상태(어느 칸)는 기기별로 기억한다(fb:last.view). 충돌 회피: 터치 지점이 있는 기기에서만(트랙패드 두 손가락은 wheel 이라 애초에 안 온다) · 첫 10px 에서 |dx| > 2|dy| 일 때만 쓸기 · 코드 블록·표·확대 이미지(data-consume-x)가 그 방향으로 더 스크롤될 수 있으면 그 요소가 먹는다 · 글 고르는 중·입력칸·쓸리는 행(V16) 위에서는 시작하지 않는다 · 쓸면서 생긴 선택은 지운다. 판정은 전부 core/drawer.ts(순수 · 유닛 7), 셸은 App.tsx 가 같은 부품(sidebarEl·stripEl·rpwrapEl·docwrapEl·homeEl)을 넓음은 흐름 안에, 중간·좁음은 .drawer 안에 둔다 — 순서(A)·표시 이름(F)·확인 대기 점은 rows 하나다. 좁음 왼쪽 서랍은 폰 홈(카드·쓸리는 폴더 행)이라 V16·폴더 행 쓸기가 그대로 산다. ⚠ 실측 함정: 열린 서랍에 will-change:transform 을 남기면 안의 position:fixed 시트가 서랍 기준이 돼 키보드 위 편집 시트가 화면 밖으로 나갔다 — 끌리는 동안만 준다. 덤으로 O 의 «사람이 올렸다» 판정을 상태 커밋을 기다리지 않고 즉시 따라가기 rAF 를 끊도록 고쳤다(간헐 빨강의 두 번째 원인). 근거 test/tmp/h-compare.png(시안 + 세 폭 + 서랍 4장). 실 iOS Safari 의 왼쪽 가장자리 쓸기는 «뒤로 가기» 라 ☰ 이 보험이다 — 실기기 확인은 Dave 몫.

질문 카드의 중복 선택 · 새 세션 + 상시 노출 (R · 2026-09-20, Dave: «AskUserQuestion 에서 중복 선택이 안되네» · «우측 상단의 새 세션 + 버튼은 마우스 오버 하지 않아도 상시 노출»): 에이전트의 질문(AskUserQuestion)은 multiSelect: true 인 질문을 보낼 수 있는데, 화면의 답이 질문당 글자 하나라 새로 고르면 앞의 것이 꺼졌다 — 여러 개를 고르라는 질문에도 하나만 갔다. 이제 답은 질문당 목록이다: 여럿 질문은 토글(네모 + 체크, 다시 누르면 꺼짐)이고 하나 질문은 종전 라디오 그대로(둘이 켜지면 답이 두 개로 가 버린다). 「기타」 칸도 갈린다 — 여럿이면 고른 것들 뒤에 덧붙고, 하나면 종전처럼 고른 것을 대신한다(2026-09-15 의 «기타는 질문마다 따로» 계약은 그대로). CLI 로는 «, » 로 이어 한 줄로 간다(tool_result 의 답은 질문→글자 하나짜리 지도라 형태를 바꾸지 않는다). 그리고 오른쪽 패널 「세션」 머리말의 새 세션 + 는 .sech .tools .ib:not(.mdb) { opacity:0 } 에 걸려 마우스를 올려야 보였다 — 가장 자주 누르는 단추가 숨어 있을 이유가 없어 .nsb 를 달아 늘 보이게 했다(다른 섹션 도구는 종전대로 호버). 근거 test/tmp/ask-multi.png.

읽은 답과 안 읽은 답 · 배너 점프 · 접힌 띠의 새 세션 + (S · 2026-09-21, Dave: «답변이 완료된 것 중에 내가 읽은 것과 읽지 않은 것을 구분하는 게 안 되네»): 턴이 끝나면 방금 온 답과 어제 읽고 닫은 답이 한 화면에서 똑같은 초록 배지였다. 알림(🔔)은 «왔다» 만 세고 «봤다» 를 안 세서 갈라 주지 못했다(실제로 50 이 쌓였다). 이제 세션이 두 시각을 갖는다 — lastReplyAt(봇이 낸 마지막 말)과 readAt(내가 맨 아래까지 본 지점). 안 읽음은 그 둘의 크기 비교 하나다(core/unread), 상태(running·done)와 섞지 않는다. 읽음 기준은 «맨 아래까지 봤을 때» (Dave 확정) — 세션을 여는 것만으로는 아니다(긴 답의 앞머리만 보고 나가도 읽음이 되면 표식이 거짓말을 한다). 스트리밍 중에는 적지 않고 턴이 끝나고도 맨 아래면 그때 적는다. 읽음은 기기별이 아니라 볼트에 적는다 — 폰에서 읽은 것이 맥에서 다시 안 읽음이면 표식을 못 믿는다. 화면은 행 강조(굵은 제목 · 밝은 미리보기) + 폴더봇 배지 링(레일 · 아이콘 띠 · 폰 홈). ⛔ 깜빡임은 뺐다 — 맥동은 이미 「일하는 중」(주황)의 언어라, 안 읽음까지 깜빡이면 색만 다른 같은 움직임이 둘이 된다(Dave 의 초록 깜빡임 제안을 이 근거로 링으로 바꿨고 승인받았다). 세션을 다 읽으면 그 세션의 알림도 읽음으로 넘긴다(notifier.markReadBySession) — 두 표면이 어긋나면 배지를 못 믿는다. S-2: 맥 알림 배너를 눌러도 그 세션으로 안 가던 것 — 앱 안의 벨은 go() 라 보이는 칸까지 바꾸지만 셸은 location.hash 만 놓아서(desktop/main.js 의 navigate), 좁음·중간에서는 서랍이 덮인 채 뒤에서 폴더만 바뀌었다. 밖에서 온 해시면 보이는 칸을 채팅으로 되돌린다(navSelf 로 내 걸음과 가른다 — 내 걸음은 이미 스스로 칸을 챙기고, 마지막 자리 복원까지 채팅으로 끌려가면 H 의 «서랍 상태 기억» 이 깨진다). S-3: 접어 둔 오른쪽 띠에는 새 세션 + 가 없었다(펼친 패널의 것은 v124 에 있다) — 띠 맨 위에 더했다. 근거 test/tmp/s-rail-zoom.png · s-home-phone.png.

복사가 거짓말을 못 하게 (T · 2026-09-21, Dave 실기기 보고: «다 안되는거 같아»): M 의 복사 다섯 건이 전부 안 된다는 보고를 받고 경로를 다시 읽었다. 맥 클립보드는 이 개발 환경(리눅스)에서 잴 수 없으므로, 앱이 스스로 확인하고 말하게 바꿨다. ① 셸은 clipboard.writeImage 뒤 readImage() 가 비었는지, 파일은 availableFormats() 에 파일 형식이 올라왔는지 되읽어 판정한다 — 종전에는 무조건 true 라 화면이 «복사했어요» 라고 말하고 붙여넣기는 비어 있었다(조용히 틀리는 쪽이라 무엇이 고장인지도 몰랐다). ② macOS 파일 복사는 clipboard.writeBuffer('NSFilenamesPboardType') 로는 안 된다 — Electron 은 형식마다 앞서 쓴 것을 지우고, 옛 이름은 커스텀 형식으로 들어가 Finder 가 파일로 못 읽는다. 그래서 osascript 로 진짜 파일 참조를 올리고(set the clipboard to {POSIX file "…"}), 되읽어 확인한 뒤 실패하면 옛 길 → 경로 글자 순으로 물러난다. 그림도 writeImage 가 비면 PNG 를 임시 파일로 굽고 «class PNGf» 로 다시 올린다. ③ 결과는 {ok, why} 라 실패하면 어디서 죽었는지가 토스트에 그대로 뜬다. ④ 폰 «공유…» 가 안 되던 진짜 이유는 보안 컨텍스트다 — http://100.x.x.x:7373(Tailscale) 에는 navigator.share·ClipboardItem 이 아예 없다(2026-09-13 의 navigator.clipboard 사고와 같은 뿌리). 이제 이유와 다음 걸음을 말하고 파일은 내려받는다. ⑤ 설정 › 기기 › 복사 진단 — 파일 하나를 골라 두 길을 밟아 보고 형식 목록까지 글로 준다([진단 글 복사]). ⑥ test/unit/desktopBridge.test.ts 가 preload 의 invoke 채널과 main 의 handle 이름을 대조한다 — 두 쪽이 갈라지면 화면에서는 «그냥 안 되는 기능» 이 되기 때문이다.

모바일 디자인 5건 · 유리가 실은 안 흐려지고 있었다 (V · 2026-09-22, Dave 스크린샷): 다섯 가지 요청을 한 라운드로 묶었다. V-1 버튼 글씨가 세로로 선다 — 원인은 폰트도 레이아웃도 아니라 줄바꿈이다. 칸이 좁아지면 크롬은 한글을 글자 단위로 감아 「명령」을 「명/령」으로 세워 버튼이 세로 기둥이 된다. 글씨를 담는 버튼 계열(.ib .btn .nb .rb .cbtn .tab .sech .seg button .dock .db .dfoot .talk …)에 white-space:nowrap 을 못 박고, 절 머리 글만 넘치면 … 로 자른다(.sech > span:not(.tools):not(.sp)). ⛔ 폭을 줄여 넘기지 마라 — 더 좁아지는 순간 같은 모양이 다시 난다. 검사는 «버튼이 높은가» 가 아니라 글씨 마디 하나의 줄 상자 개수를 센다(요소째로 재면 아이콘·배지 상자까지 세어 오판한다). V-2 오른쪽 독 — color-mix(var(--box) 62%) + backdrop-filter:blur(14px) saturate(1.2) 유리로 바꾸고 맨 위에 손잡이(.grip)를 달아 위아래로 끈다. 자리는 «화면 가운데에서 얼마나» 로 기기에 남고(fb:docky) 그릴 때마다 화면 안으로 가둔다(core/dock, 유닛 4). 🔴 실측 함정 둘: 손잡이는 독의 맨 위라 위로 조금만 끌면 손가락이 독 밖으로 나가 요소에 건 onPointerMove 가 끊긴다 — 창에서 듣는다. 그렇다고 setPointerCapture 를 누르자마자 걸면 이번엔 독 단추의 클릭이 사라진다(H 중간 판의 «독 📁» 가 안 열렸다). 6px 을 안 넘긴 끌기는 «누른 것» 이라 단추가 제 일을 한다(isDockDrag). V-3 손가락 크기 — 폰에서 .ib 40px · 절 머리 버튼 36px · 목록 줄(.hz·.kv) 44px · 독 단추 44px · 문서 앞뒤 46px(HIG 44pt ~ 머티리얼 48dp 사이). V-4 명령과 스킬을 한 칸으로 — 「지침 · 하네스」와 「슬래시 명령」 두 칸을 「명령 · 스킬」 하나로 합쳤다. 지침이 위, 아래로 명령 → 스킬 → 커넥터가 한 목록이고 줄마다 «명령/스킬/커넥터 · 이 폴더/볼트/사용자», 처음 6줄 뒤 «+N개 더 보기», 머리의 + 는 새 슬래시 명령. 범위 표기는 명령 쪽 말(이 폴더)로 통일했다 — 한 목록에 두 어휘가 섞이면 같은 것이 달라 보인다. V-5 문서 화면 — 폰 경로 줄에서 앞/뒤 넘기기·목차·외부에서 열기를 걷어 ⋯ 하나만 남겼다(목차는 ⋯ 안으로). 앞뒤 넘기기는 아래 줄에 이미 있었고 거기에 «3 / 7» 위치를 붙였다. 같은 조작을 위아래에 두 벌 두지 않는다. V-덤 (진짜 사고): 소스에 손으로 적어 둔 -webkit-backdrop-filter 때문에 번들러가 접두사 없는 쪽을 지웠고, 크롬 141 은 -webkit- 별칭을 없앴다(실측: getComputedStyle().backdropFilter === 'none'). 즉 유리 10곳 전부 blur 가 안 걸린 채 나가고 있었다 — 계약 검사도 스모크도 «배경색» 만 봤기에 조용했다. 소스에서 손 접두사를 빼고 vite.client.config.ts 에 cssTarget 을 주어 번들러가 표준·접두사 둘 다 찍게 했다. 교훈: 접두사는 손으로 적지 않는다 — 적는 순간 번들러의 판단과 겹쳐 조용히 한쪽이 사라진다. 근거 test/tmp/v-phone-panel.png · v-phone-doc.png.

맥 QA 층 + 복사가 «다 안 되던» 진짜 이유 (X · 2026-09-22, 맥미니 실기기): 앞 세션(리눅스 컨테이너)이 맥이 아니면 못 하는 일을 남겨 두었고, 그 첫 일은 새 기능이 아니라 맥에서 실제로 재는 층을 세우는 것이었다. X-1 npm run qa:mac (test/mac-qa.mjs) — npm run qa 위에 얹는 맥 전용 층. CI 와 같은 순서로 호스트 번들을 desktop/host/ 에 만들고 → 클립보드 검사(test/mac-copy-check.mjs) → 실제 Electron 앱을 Playwright _electron 으로 띄워(픽스처 볼트 · 스텁 CLI · 임시 HOME) 실번들에서 ① 유리 blur 가 실제로 걸리는가(.dock computed backdropFilter, V-덤 회귀 방어) ② 버튼 white-space:nowrap(V-1) ③ 독 손잡이 끌기 + fb:docky(V-2) ④ preload → main → 클립보드 → 되읽기의 IPC 복사(M) ⑤ 창 자리가 settings.json 에 남는가(winBounds) ⑥ 딥링크 second-instance → location.hash(알림 클릭과 같은 길)를 잰다. 안 재는 것 — 알림 배너 «클릭» 자체와 Finder·메모·카톡에의 실제 ⌘V(사람이 한다). 🔴 실측 함정 셋: ⓐ macOS 의 userData 는 $HOME 을 무시한다(NSSearchPath 가 passwd 의 홈을 쓴다) — 임시 HOME 만으로는 실 설정을 읽으므로 셸에 FOLDERBOT_USER_DATA 훅을 두어 통째로 옮긴다. ⓑ 개발 Electron 은 folderbot:// 기본 앱을 가로채고 로그인 항목에 스스로를 넣고 15초 뒤 릴리스를 내려받아 «업데이트» 를 권한다 — 전부 Dave 가 쓰는 실 앱을 건드리는 일이라 FOLDERBOT_QA=1 이 셋을 끈다. ⓒ 이 맥(외장 Dave HDD · 경로에 공백)에서 electron . 은 아무 말 없이 exit 0 이고 electron main.js 는 뜬다 — 원인은 안 좇았고 검사는 main.js 를 직접 준다. X-2 M 의 뿌리 — Electron 44 는 clipboard 를 통째로 갈았다. desktop/package.json 이 처음부터 44.3.0 을 박아 두었는데, 이 판의 clipboard 는 W3C 식 비동기 API(read()·write([ClipboardItem])·has(mime)·readText·writeText·clear) 여섯 개뿐이고 writeImage·readImage·writeBuffer·readBuffer·availableFormats 가 없다(Object.keys(clipboard) 실측). v126 까지의 세 핸들러(그림·파일·진단)는 없어진 함수를 불러 한 줄씩 TypeError 로 죽었다 — 그래서 «다 안 된다» 였고, T 라운드의 «되읽어 판정» 도 없어진 readImage 를 불러 실은 한 번도 안 돌았다. 종전 mac-copy-check.mjs 역시 availableFormats 에서 던져 잡는 곳이 없어 영원히 안 끝났다(90초 넘게 매달림). 새 API 로 실측한 길(desktop/clip-core.js 머리말): 그림은 ClipboardItem({'image/png': Blob}) → image/png + Apple PNG + TIFF 가 올라간다. 파일은 ClipboardItem({'text/uri-list': 'file://…\r\n…'}) → public.file-url(0x6675726C) + NSFilenamesPboardType 이 올라가고 AppleScript the clipboard as «class furl» 이 경로를 돌려준다 — 한글 이름·폴더·여러 개 모두 plist 에 들어간다(NFD). ⛔ 파일 URL 과 text/plain 을 한 항목에 같이 싣지 않는다 — 같이 실으면 파일 URL 이 폴더까지만 남는다(실측). ⛔ v126 이 «먼저 쓰던 길» osascript … {POSIX file …}(목록) 은 실은 'list' 형식만 올려 Finder 가 못 읽는다 — 파일 하나를 괄호 없이 주면 furl 이 올라가므로 폴백은 한 개일 때만. 되읽기는 바깥 프로세스가 쓴 직후 빈 목록이 올 때가 있어 100ms 간격 3회, 검사 스크립트는 clear() 가 약속을 먼저 풀고 실제 비우기가 뒤에 오는 경쟁(4회 중 1회)을 «비어 있는 게 보일 때까지» 기다려 피한다. 판정은 desktop/clip-core.js(순수 · 유닛 5)로 뺐고, 진단(fb:copy-diag)은 어느 줄에서 죽어도 그 줄까지 글로 남긴다. X-3 맥에서만 나던 거짓 빨강 — localfs.test.ts 가 맥에서 빨갰다. tmpdir() 이 /var/…(→ /private/var 심링크)라 어느 후보도 path === realpath 가 아니어서 «짧은 경로» 규칙이 ~/Dropbox/PARA(별칭)를 정본으로 올렸다. 홈만 실체로 바꾼 경로와 실체를 비교해(canon) 홈 아래 심링크만 «별칭» 으로 본다. X-4 스모크가 맥에서 처음 돌았다 — test/smoke.mjs 는 지금까지 리눅스 컨테이너에서만 돌았고 CI 에도 없어, 맥에서는 일곱 군데가 조용히 어긋났다(전부 검사 쪽 · 제품 코드는 그대로): ① 크로미움을 컨테이너 경로(/opt/pw-browsers)로 못 박아 두었다 → 있으면 그것, 없으면 Playwright 캐시(node node_modules/playwright-core/cli.js install chromium-headless-shell) ② test/tmp/rail-name-mock.html 은 gitignore 된 산출물이라 새 기기엔 없다 → 있을 때만 시안 비교 ③ 호스트는 루트를 realpath 로 돌려주는데 맥 tmpdir 은 /var → /private/var 심링크라 글자가 다르다 → 픽스처를 realpath 로 ④ 스텁이 답에 앞 60자만 되읊는데 맥 tmpdir 은 그 자체로 55자라 절대 경로 칩이 잘린다 → 맥에서는 /tmp 아래 ⑤ 맥 Chromium 에서 Control+A 는 전체 선택이 아니고 Home/End 는 캐럿을 안 옮긴다 → ControlOrMeta · ⌘→/⌘↓/⇧⌘← ⑥ 이 맥엔 실제 codex 가 깔려 있어 제공자가 둘 → 세션 + 가 고르기를 띄운다 → 메인 호스트에 FOLDERBOT_CODEX_BIN=/nonexistent/codex 로 «Claude 만 깔린 기기» 를 고정(둘인 경우는 V24 블록이 따로 잰다) ⑦ 스킬이 82개라 «+N개 더 보기»(.hz.more · V-3 가 일부러 40px) 줄이 처음 생겨 44px 검사에 걸렸다 → .more 제외. 그 밖에 포화된 미니에서 고정 대기(900ms·1000ms)가 모자라 두 곳을 «될 때까지» 로, 크로미움 153 이 오프라인 흉내 구간의 «못 받음» 을 콘솔 error 로 찍어 맨 끝 «오류 0건» 에 걸리던 것을 그 구간만 걷어냈다. 유닛 fileWatch 는 47 파일 병렬일 때 이 워커에만 FSEvents 가 몇 초씩 안 와(혼자 12ms · 순차 항상 초록 · 바깥 프로세스는 63ms) retry:3 을 줬다 — 풀(forks/threads)을 바꿔도 간헐이었다. 근거 test/tmp/mac-qa-app.png. 교훈: 의존성 판을 올릴 때 사라진 API 는 컴파일러가 안 잡는다 — JS 셸이라 부를 때까지 조용하고, 리눅스에서는 그 줄에 영영 안 닿는다. 맥 층이 없었으면 M 은 «실기기 확인 대기» 로 계속 남았을 것이다.

md 한 개만 ⌘C 가 안 되던 것 — 초점을 «가져간» 편집기 (Y · 2026-09-22, Dave 실기기): Dave 보고는 «여러 개는 원격에서나 메인에서나 잘 되는데 md 한 개만 안 된다» 였다. 클립보드가 아니라 초점이 문제였다. 트리에서 md 를 누르면 문서 창이 열리며 CodeMirror 가 view.focus() 로 초점을 가져간다(편집기 출생 때부터 있던 줄이다 — 커서 없이 뜨면 바로 못 쓰니 맞는 동작이다). 그 상태의 ⌘C 는 트리 핸들러의 「글 쓰는 중이면 양보」 규칙(isContentEditable · .trow 안인가)에 걸려 파일 복사를 안 하고, 편집기 쪽은 고른 글이 없어 아무것도 안 올린다 — 둘 다 손을 떼서 조용히 아무 일도 안 일어났다. 왜 그것만이었나: png·pdf 는 뷰어라 초점을 안 가져가고, 여러 개는 ⌘·⇧ 로 고르는 동안 문서가 아예 안 열려 초점이 트리에 남는다. 처방 — 판정을 core/copyIntent.ts(순수 · 유닛 6)로 빼고 «사람이 마지막으로 만진 곳» 으로 가른다: 트리 행을 누르면 armed, 문서 창을 포인터로 누르거나 수식키 없는 키를 치면 해제. armed 이고 고른 글이 없으면 편집기에 초점이 있어도 파일 복사다 — 초점은 편집기가 «가져간» 것이지 사람이 «준» 것이 아니기 때문이다. ⚠ 실측 함정: 해제 감시자를 keydown 캡처로 달면 ⌘C 자체가 먼저 도착해 늘 무장을 푼다 — 수식키 조합과 수식키 단독은 «만진 것» 으로 치지 않는다. 회귀 방어는 스모크 「Y」 — md 를 누른 직후 ⌘C 가 그 md 를 copyFiles 로 올리고, 편집기를 클릭한 뒤의 ⌘C 는 올리지 않으며, 트리 행을 다시 누르면 다시 올린다. 이 검사는 고치기 전 판에서 실제로 빨갛다(«md 를 누른 직후 ⌘C 가 그 md 를 파일로 복사해야 한다» — copyFiles 호출 0건). 교훈: «안 되는 기능» 을 볼 때 무엇이 다르냐가 아니라 무엇을 마지막에 만졌냐를 본다 — 파일 형식이 아니라 초점이 변수였다.

쓸기가 «고정 기능» 이 아니라 «온 길» 로 (Z-2 · 2026-09-22, Dave 확정 「A Safari 모델」): Dave 보고는 «폴더에서 👉 하면 봇 목록이 튀어나온다 — 쓸기가 현재 위치가 아니라 고정 기능으로 동작한다» 였다. 원인은 제스처가 아니라 모델이었다. 화면이 [봇 레일 | 채팅 | 오른쪽] 세 칸 띠(core/drawer.bandNext)인데 폴더와 문서가 「오른쪽」 한 칸을 같이 썼다 — 그래서 ① 문서에서 👉 하면 폴더를 건너뛰고 ② 채팅에서 👈 는 «마지막에 본 쪽»(lastRight)으로 가고 ③ 결정적으로 문서의 [뒤로] 버튼은 폴더로, 같은 화면의 쓸기는 채팅으로 가서 한 앱에 두 모델이 공존했다. 이제 칸을 버리고 사람이 온 길(스택) 하나를 기준으로 삼는다(core/navstack.ts · 순수 · 유닛 7). 👉 = 뒤로(한 걸음 되돌아감) · 👈 = 앞으로(방금 한 뒤로를 무르기) — 브라우저와 같다. 같은 문서라도 폴더를 거쳐 왔으면 👉 는 폴더로, 채팅의 칩에서 열었으면 채팅으로 간다. 🔴 [뒤로] 버튼과 👉 쓸기가 같은 navBack 하나를 부르고, 버튼의 이름표도 목적지를 약속하던 「폴더로」·「대화로」에서 **「뒤로」**로 바꿨다 — 온 길 모델에서 고정 목적지는 거짓말이다. 어두워진 채팅 탭·Esc·[접기]는 「뒤로」가 아니라 «채팅으로 돌아가기»(dismiss)다: 봇 목록은 뒤로 가서 열었으니 닫는 것이 앞이고 폴더·문서는 앞으로 가서 열었으니 닫는 것이 뒤라, 하나로 묶으면 반드시 한쪽이 틀린다. 방금 한 뒤로를 무르는 참이면 fwd 를 써서 온 길을 잃지 않는다(닫았다가 👈 하면 폴더 → 문서 순으로 되돌아온다). 한 문장 요약은 「쓸기는 뒤로, 독은 앞으로」 — 👈 로 폴더를 «여는» 길은 없어졌고 들어가는 문은 독이다(📄 문서 · ☑ 할 일 · 📁 파일). 🔴 실측으로 드러난 진짜 버그: 갈 데가 없는 👈 를 «쓸기 아님» 으로 흘려보냈더니 손가락이 글 위를 지나며 선택을 남기고, 그 선택 때문에 canStartSwipe 의 «글 고르는 중» 규칙에 걸려 다음 쓸기가 통째로 막혔다. 이제 갈 데가 없어도 가로 쓸기로 잡아(lock='h' · next=null) 선택만 치우고 끝낸다 — 종전 band 에서는 👈 가 늘 어딘가로 갔기에 드러나지 않던 자리다. 서랍 그림은 셋이다 — 서랍이 나오면 open, 들어가면 close, 폴더↔문서처럼 같은 서랍 안에서 내용만 바뀌면 swap(손가락을 따라 움직일 그림이 없어 놓을 때 바뀐다). 마지막 화면 복원(fb:last.view)도 그 쪽 하나가 아니라 거기까지의 온 길을 세운다 — 문서로 되돌아와도 👉 가 폴더·채팅을 제대로 거친다. core/drawer.ts 에는 이제 손가락을 읽는 일(방향 잠금 · 넘길지 말지 · 그림 위치)만 남았고 목적지 판정은 navstack 하나다. 검사는 스모크 「Z」 — 폴더에서 연 문서의 👉 는 폴더로, 채팅에서 연 문서의 👉 는 채팅으로, 👈 두 번이면 폴더 → 문서로 되돌아오고, [뒤로] 버튼과 쓸기가 같은 곳으로 간다. 이 검사는 고치기 전 판(빌드까지 되돌려)에서 실제로 빨갛다(«앞으로 갈 데가 없는 👈 가 폴더를 열었다»). 온 길 자체를 볼 수 있게 .app 에 data-nav(list>chat>panel |doc)를 내보낸다. 근거 test/tmp/z-narrow-stack.png. ⏸ 같은 라운드의 Z-1(독 투명도)·Z-3(늘 띄우기 · 가속도 · 상중하 스냅)은 독 2안 중 어느 쪽인지 Dave 결정 대기라 손대지 않았다.

폰의 떠 있는 독을 하단 탭으로 (Z-1·Z-3 · 2026-09-22, Dave 확정 「B · 하단 탭(내리면 숨음)」): Z 라운드의 두 요청은 원래 독을 손보는 것이었다 — «지금보다 더 투명하게, 스크롤 업/다운 중엔 훨씬 더»(Z-1)와 «봇 목록 빼고 늘 띄우기 + 끌기에 가속도 + 상·중·하 자석 스냅»(Z-3). 시안으로 두 안(A 독 유지 + FAB 답게 / B 하단 탭)을 만들어 물었고 Dave 가 B 를 골랐다. 그래서 폰(좁음 <768)에서는 떠 있는 독을 없애고 하단 탭 네 칸(💬 채팅 · 📄 문서 · ☑ 할 일 · 📁 폴더)으로 갔다. 두 요청의 뜻은 B 의 언어로 그대로 지킨다 — 봇 목록만 빼고 늘 있고(Z-3), 읽어 내려가면 비킨다(Z-1). 독 고유의 «끌어서 옮기기 · 가속도 · 상중하 스냅»은 탭에서 뜻을 잃어 함께 폐기했다(탭은 제자리가 약속이다). 중간(768–1199)은 알약 독 그대로다 — 맥북 창을 줄인 상태에서 화면 아래를 가로지르는 탭 줄은 데스크톱 창의 말이 아니고, Dave 의 요청도 모바일에서 온 것이다. 🔴 실측으로 드러난 진짜 버그 둘. ① 탭이 숨을 때 채팅 높이까지 바뀌면 스크롤이 자기 자신을 흔든다(스크롤 → 탭 숨음 → 높이 변함 → 스크롤 튐). 그래서 자리(--tablift)는 늘 비워 두고 탭만 미끄러지게 했다 — 자리를 거두는 것은 탭 자체가 없을 때(봇 목록·키보드)뿐이다. ② 폰의 보내기 단추가 «눌리지도 않고 아무 일도 안 일어나는» 정체가 여기서 드러났다: 단추를 누르는 순간 입력칸에서 초점이 빠져 키보드가 닫히고, 입력칸이 제자리를 다시 잡느라 mousedown 과 mouseup 사이에 단추가 움직여 click 이 아예 안 났다. onMouseDown 에서 기본 동작을 막아 초점을 지킨다 — 보낸 뒤에도 키보드가 남는 것이 사람이 바라는 동작이기도 하다. 판정은 core/tabbar.ts(순수 · 유닛 4) — 6px 문턱(떨림에 깜빡이지 않게) · 꼭대기 40px 안에서는 늘 보임 · 할 일과 파일은 같은 「폴더」 화면이라 마지막에 연 칸까지 봐야 어느 탭이 켜졌는지 갈린다. 스크롤은 창에서 캡처로 듣는다(채팅·폴더·문서가 저마다 다른 스크롤러라 하나씩 붙이면 한 군데는 꼭 빠진다). 검사는 스모크 「Z」 — 폰 채팅에 탭 4칸 · 내려가면 .tabbar.hide · 올라오면 복귀 · 그동안 --tablift 는 불변 · 봇 목록에는 탭 없음. 이 검사는 고치기 전 판(빌드까지 되돌려)에서 실제로 빨갛다(«좁음은 하단 탭이어야 한다 · dock:true, tabs:false»). V-2 의 «끌리는 독» 계약은 폰에서 중간(900px)으로 재는 자리를 옮겨 회귀 방어를 살려 두었다. 근거 test/tmp/z-tabbar.png.

루틴이 조용히 안 돌던 것 — 새는 곳 셋을 막고 주기를 사람 말로 (AA · 2026-09-22, Dave 실제 사고): Dave 가 UI 주기 칸에 「20」(저녁 8시라는 뜻)을 넣었다. croner 는 5·6·7 필드만 받으므로 CronPattern: invalid configuration format ('20') 를 던졌고, routines.reschedule() 이 그 예외를 try/catch 로 삼키고 로그 한 줄만 남겼다. 화면에는 루틴이 멀쩡히 살아 있었다 — 한 번도 안 도는데 아무도 알 수 없었고 이틀이 갔다. 그걸 발견해 .bot.yml 을 손으로 "0 20 * * *" 로 고쳤더니 이번엔 화면도 스케줄도 안 바뀌었다(registry.botConfig() 는 매번 새로 읽지만 emit("bots") 와 reschedule() 은 UI·REST 저장 때만 일어났고, .bot.yml 을 보는 코드가 없었다). 게다가 목록은 원시 cron 문자열을 그대로 띄워서 「20」이 저녁 8시인지 매시 20분인지 화면만 봐선 알 수 없었다. 새는 곳이 셋이었다. AA-1 · 조용히 삼키지 않는다 — reschedule() 이 실패하면 그 루틴에 lastError 를 붙여서 화면까지 보내고(경고 배지), 목록에는 croner 의 nextRun() 으로 **「다음 실행」**을 싣는다(이 한 칸이 있었으면 그 자리에서 알아챘다). 저장 시점(PUT /api/bots/:id/routines)에도 같은 판정을 써서 못 서는 값은 400 으로 막고 무엇이 잘못됐는지와 고친 예를 함께 돌려준다 — 종전에는 검증 없이 통째로 덮어썼다. 🔴 스케줄을 다시 짜고 나서 보낸다(afterBotsChanged) — 먼저 보내면 화면은 늘 «오류도 없고 다음 실행도 모르는» 낡은 판을 받는다. 화면용 값(lastError·nextRun)은 stripRuntime() 이 저장 길목에서 털어 .bot.yml 로 새지 않는다. AA-2 · 밖에서 고쳐도 따라온다 — 봇 폴더 감시자(FolderWatch)에 .bot.yml 전용 신호를 더해 UI 로 저장한 것과 같은 길(afterBotsChanged)을 태운다. 볼트가 Dropbox 로 동기화돼 쓰기가 연달아 오므로 파일 신호(300ms)와 따로 400ms 디바운스를 둔다. 🔴 그리고 파싱 실패한 YAML 로 기존 루틴을 날리지 않는다 — botConfig() 가 실패하면 {} 를 돌려주던 탓에 반쯤 쓰인 파일을 한 번 읽는 것만으로 루틴이 통째로 사라진 것처럼 되고 그 상태로 스케줄이 다시 걸렸다. 이제 직전에 성공한 값을 그대로 쓰고, 파일이 정말 지워졌을 때만 빈 설정이다. AA-3 · 주기는 사람 말로, cron 은 사람에게 안 보여 준다 — core/when.ts(순수 · 유닛 10)가 「매일 저녁 8시」·「평일 아침 9시 반」·「일요일 오전 10시」·「두 시간마다」·「매달 1일 아침」·「20시」를 5필드 cron 으로 바꾼다. 규칙으로 먼저 풀고 모델은 안 부른다 — 안 풀리는 것만 사람/봇에게 «사람 말로 다시 달라»고 돌려보내므로, 모델은 이미 대화에 있는 그 자리에서만 개입한다. 🔴 숫자 하나(「20」)는 추측하지 않고 되묻는다 — «20시에 돌릴까요, 매시 20분에 돌릴까요?» 이번 사고가 정확히 이 지점이다. 🔴 :00 과 :30 을 피해 흩뿌리지 않는다 — 사용자가 말한 시각이 곧 약속이다. 저장 전에는 사람 말로 되읽어 확인시킨다(«매일 저녁 8시에 돌립니다. 다음 실행은 9/23(화) 20:00 입니다»). 되읽기는 왕복이 된다 — 「저녁 8시」↔20시, 「밤 9시」↔21시(21시를 «저녁 9시» 로 되읽던 것을 고쳤다). 🔴 화면 미리보기와 스케줄러가 같은 파서를 쓴다(croner 는 브라우저에서도 돈다) — 둘이 갈리면 «미리보기는 맞는데 안 도는» 새 사고가 난다. AA-4 · 사람이 쓸 수 있는 UI — 목록 줄은 원시 cron 대신 **「매일 저녁 8시 · 다음 9/23(화) 20:00」**이고 cron 식은 상세에만 작게 뜬다. 폼은 이름 · 언제 · 무엇을 세 칸이고, 「언제」에는 프리셋 넷(매일 아침 9시 · 매일 저녁 8시 · 평일 오전 · 매주 월요일)과 **입력하는 동안 실시간 「다음 실행」**이 붙는다. 못 읽는 값이면 빨간 글씨로 이유를 말하고 저장 버튼을 막는다. 뜻이 갈리면 그 자리에 고를 단추가 뜬다. 프롬프트 칸에는 «무엇을 확인하고, 변화가 없으면 어떻게 하라»는 도움말과 힌트 한 줄. 각 줄에 켜기·끄기(지우지 않고 멈추는 길 · enabled)와 「지금 한 번 돌려보기」(저장하고 바로 돌린다 — 이번 사고는 그게 없어서 이틀을 몰랐다). 승인 수준은 기본이 「계획만 세운다」(=plan)이고, 「무엇이든 한다」에는 ⚠ 모든 확인을 건너뜁니다(bypassPermissions) 라고 적어 둔다 — 모르고 고르면 안 된다. AA-5 · 봇도 루틴을 다룬다 — MCP 도구 넷(routine_list·routine_add·routine_update·routine_remove). 저장 경로는 기존 그대로 .bot.yml 이고 저장 뒤 afterBotsChanged() 로 다시 건다(새 저장 포맷을 만들지 않는다). 🔴 approve 는 도구에 아예 없다 — approve: always 는 approveToMode() 를 거쳐 bypassPermissions 가 되므로 봇이 스스로 권한을 올리는 길을 열지 않는다(승인 수준은 사람이 화면에서만). routine_remove 와 prompt 교체는 되돌릴 수 없으니 직전 값을 결과에 담아 준다. 도구 설명에 «주기는 사람 말로 적는다 · cron 식을 지어내지 마라»를 못 박았고, 봇이 「20」 같은 값을 넣어도 저장되지 않고 되묻는 답이 간다. 검사는 스모크 「AA」 — ① 「20」은 400+되묻기로 끝나고 파일에 안 남는다 ② 「매일 저녁 8시」는 .bot.yml 에 0 20 * * * 로 저장되고 목록엔 사람 말+다음 실행 ③ 에디터로 고치면 몇 초 안에 따라온다 ④ 반쯤 쓰인 YAML 로 루틴이 안 날아간다 ⑤ 「지금 한 번」이 세션을 만든다 ⑥ 봇이 고칠 수 있고 approve 는 못 올린다. 이 검사는 고치기 전 판(빌드까지 되돌려)에서 실제로 빨갛다 — «「20」이 400 으로 안 막혔다 · status 200, {ok:true}». 교훈: 예외를 삼키는 catch 는 기능을 조용히 죽인다 — 죽일 거면 화면까지 가지고 가야 한다.

기다림을 보이게 — 「끝난 건지 기다리는 건지」 (AB · 2026-09-23, Dave 스크린샷 2319·2320 · 시안 「기다림을 보이게」 B안 확정): Dave 보고는 «다른 에이전트에게 일을 시키고 기다리는 화면인데, 기다리는건지 어떤건지 내가 알수가 없거든» 이었다. 두 화면의 공통점은 마지막 말이 끝난 말처럼 생긴 것이다 — 복사·되돌리기 단추와 「방금」을 달고 있고, 살아 있다는 표시는 입력칸 구석의 14px 동그라미 하나인데 눈은 글 끝에 있다. 🔴 뿌리는 상태가 하나 모자란 것이었다. SessionState 의 running 하나가 「내가 생각 중」(몇 초)과 「남이 끝내 주길 기다리는 중」(몇 분)을 같이 덮었다 — 사람이 자리를 떠도 되는지가 정반대인데 화면은 같은 말을 했다. 🔴 그리고 더 나쁜 것: 백그라운드 에이전트를 띄우면 턴이 끝나 state 가 running 을 벗어나고, 그 순간 상태 줄(Live)이 통째로 사라졌다. 2319 가 정확히 이것이다 — 일은 계속되는데 화면은 완전히 조용해진다. 처방 — 판정을 core/waiting.ts(순수 · 유닛 10)로 뺐다. 「누가 공을 들고 있나」 넷으로만 읽는다: me(내가) · other(남이) · you(네가) · none(아무도). 색이 곧 그 답이다 — 주황=내가 · 청록=남이(새로) · 노랑=네가 · 초록=아무도. 🔴 맥동 속도도 뜻이다 — 주황은 빠르게(1.2초 · 바쁘다), 청록은 느리게(2.6초 · 기다린다). 「남이 들고 있다」를 정하는 법은 두 갈래다: ① 맡기는 것이 분명한 도구(Task·bot_send·브라우저·이미지 생성)는 곧바로 ② 그 밖의 도구도 20초 넘게 붙들려 있으면 그 자체로 「기다리는 중」이다 — 🔴 이름 목록만으로 정하지 않는다(새 MCP·새 스킬이 계속 생기므로 목록은 반드시 낡고, 모르는 도구가 조용히 사라진다). 그리고 bg > 0 이면 턴이 끝나도 여전히 「남이 들고 있다」 이다. 호스트는 판정하지 않고 사실만 적어 보낸다(SessionInfo.inflight = { name, summary, since }) — 도구가 뜰 때 적고, 답·생각이 오거나 턴이 끝나면 지운다. 화면 셋이 같은 값에서 나온다(AB-5) — 레일·독 아이콘(멀리서 · 얼굴과 색), 채팅 헤더(한 줄 · holdHeader), 채팅 끝 대기 줄(자세히 · holdLine). ⛔ 셋이 다른 말을 하면 어느 쪽도 못 믿는다 — 종전에는 레일만 「일하는 중 · 생각 중」이라 말하고 채팅은 아무 말이 없었다. 대기 줄은 마지막 말 뒤에 붙는 한 줄이고 말풍선이 아니다(점선 · 옅은 청록 · 복사/되돌리기 없음) — 「아직 안 끝났다」가 생김새로 읽혀야 마지막 말과 안 헷갈린다. 문구는 셋으로 갈린다: 030초 「…의 답을 기다리는 중」(초를 안 센다 — 재촉처럼 보인다) · 30초2분 「아직 … · 1분 5초」 · 🔴 2분+ 「오래 걸리네요 — 다른 일 보셔도 됩니다. 끝나면 알릴게요」 + [알림 켜기]. 마지막이 핵심이다 — 기다림에서 가장 필요한 한 마디는 진행률이 아니라 자리를 떠도 된다는 말이다(ADHD 사용자에게 특히). 기다리는 대상에 따라 [그 봇 보기]·[브라우저 보기]가 붙는다. 얼굴은 눈만 바꾼다(Dave 「B안」) — 남을 기다릴 때는 반쯤 감은 눈(내 차례가 아니다)에 일자 입. ⛔ 입은 안 건드린다 — 28px 레일에서 뭉개진다. 종전 노랑(확인 대기)의 튀는 배지는 그대로 뒀다 — 시안에서는 「안 깜빡인다」로 제안했지만 Dave 가 고른 것은 얼굴 안(B)이라 기존 동작을 임의로 바꾸지 않았다. 검사는 스모크 「AB」 — 대기 줄이 뜨고 · 헤더 얼굴이 fb-hold 이고 · 헤더 한 줄이 있고 · 레일 얼굴이 같은 말을 하고 · 시각을 2분 밀면 「가도 된다」로 바뀌고 [알림 켜기]가 뜬다. ⚠ 스텁의 백그라운드 에이전트는 1초 남짓에 끝나 «턴 끝 + bg 남음» 순간을 화면으로 잡기 어렵다 — 그 계약은 값으로(API 폴링) 재고 판정 자체는 유닛이 잰다. 이 검사는 고치기 전 판(빌드까지 되돌려)에서 실제로 빨갛다 — «대기 줄이 없다 · face: fb-done»(끝난 얼굴을 하고 있었다는 뜻이라 Dave 의 보고와 글자 그대로 같다). 교훈: 화면에서 사라지는 것은 «끝났다» 는 뜻이다 — 일이 남았으면 줄도 남아야 한다.

칸(섹션) 자체가 봇이 되던 것 (AC · 2026-09-23, Dave 스크린샷 056): «리소스와 아카이브도 프로젝트와 에어리어 처럼 그 하단에 폴더명으로 생겨야해. 이렇게 폴더 전체가 생기면 안돼» — 4. Resources 통째로 봇이 되어 레일 맨 위에 서 있었고, 오른쪽 트리에는 그 안의 프로젝트 수십 개가 한꺼번에 딸려 들어와 있었다. 원인은 글롭의 비대칭이다. 봇 후보 규칙이 active: ['2. Projects/*', '3. Area/*'] 라 그 둘은 «부모(칸)» 가 자연히 후보에서 빠지는데, reference: ['4. Resources']·archive: ['5. Archive']·inbox: ['1. Inbox'] 는 /* 가 없어 칸 자체가 그대로 통과했다. start() 는 「어디서든 시작」을 위해 아무 폴더나 받으므로 아무도 안 막았다. 게다가 칸에 붙은 봇은 section 이 빈 문자열이 되어(rel.split('/')[0] === rel) 레일에서 「관제」 바로 아래 제 칸 없이 떠 버린다 — 056 의 그 모습이 이것이다. 처방 — 판정을 core/rules.isSectionRoot()(순수 · 유닛 5) 한곳에 두고 registry.start() 에서 막는다. REST(/api/bots/start)·MCP(bot_start)·「새 폴더 만들고 시작」이 전부 start() 를 지나므로 문이 하나다. 칸 목록은 규칙에서 뽑는다 — active 글롭의 부모들 + inbox·reference·archive 의 경로들(끝의 /* 는 떼고, */* 처럼 부모가 와일드카드인 것은 «칸» 이 아니라 «아무 폴더나» 라는 뜻이라 뺀다). 🔴 거절할 때 그 안의 폴더 예시를 같이 준다 — 「4. Resources 는 칸이에요 — 그 안의 폴더에 봇을 만들어요 (예: 4. Resources/2026_SelfMBA · …)」. 막기만 하면 사람은 다음에 뭘 할지 모른다. 폴더 고르기 화면에서는 칸을 고르면 시작이 아니라 펼쳐지고(더블클릭도 마찬가지) 시작 단추가 잠기며 「칸이에요 — 그 안의 폴더를 고르세요」가 뜬다. 🔴 역할 판정(roleOf)은 안 건드렸다 — 칸 막기와 역할은 다른 일이다. reference 글롭을 4. Resources/* 로 바꿨다면 roleOf('4. Resources') 가 null 이 되어 「참조」 표식과 은퇴 판정이 함께 무너졌을 것이다. 🔴 이미 만들어진 칸 봇은 지우지 않는다 — 남의 데이터를 말없이 지우지 않는다. 레일에서 우클릭 → 지우기로 사람이 거둔다(폴더는 그대로 남는다). 검사는 스모크 「AC」 — 다섯 칸이 전부 400 + 「칸이에요」로 막히고, 목록에도 안 들어가고, 4. Resources/2026_소울-영어오디오 는 폴더명이 봇 이름이고 섹션이 그 칸이며, 봇(MCP)도 같은 문에 걸린다. 이 검사는 고치기 전 판(빌드까지 되돌려)에서 실제로 빨갛다 — 만들어진 봇의 section 이 "" 로 찍혀 나온다(레일에서 떠 버린 그 값이다). 교훈: 「어디서든 시작」 같은 너그러운 문에는 «어디는 아니다» 를 같이 적어야 한다 — 규칙이 두 모양(…/* 와 …)으로 적혀 있으면 한쪽만 조용히 새 나간다.

폰의 길을 하나로 — 탭 고정 · 👉 하나 · 탭 넷이 다른 화면 (AD · 2026-09-23, Dave 실사용 뒤): 하단 탭(Z)을 써 본 Dave 의 판단은 «막상 모바일 버전을 보니까 하단에 메뉴가 있는 게 그렇게 불편하지 않다 · 스크롤을 내리거나 올렸을 때 하단 메뉴가 사라지더라도 실제 채팅창이 고정되어 있으니까 의미가 없어» 였다. 맞는 말이다 — 숨어서 버는 자리가 없으면 숨는 동작은 깜빡임일 뿐이다. 그래서 Z 의 「내리면 숨음」을 폐기하고 탭을 고정했다(core/tabbar.tabVisible 삭제 · 되살리려면 「숨어서 무엇을 버나」를 먼저 답해야 한다). 탭이 없는 때는 둘뿐이다 — 봇 목록(갈 곳이 목록 자체다)과 키보드(입력칸을 가린다). 🔴 길을 하나로 — «왼쪽으로 쓸기는 아예 삭제(탭으로 다 해결됨) · 오른쪽으로 쓸기만 남겨서 어디서든 바로 폴더 리스트 · 모든 탭의 왼쪽 상단 버튼도 무조건 폴더 리스트». 이로써 Z-2 에서 세운 온 길(스택) 되짚기는 폰에서 걷어냈다(navstack.back·swipeMove·sideOf 삭제). 모순처럼 보이지만 아니다 — Z-2 가 고친 것은 «쓸기와 [뒤로] 버튼이 서로 다른 데로 가던 것» 이고, 지금은 둘 다 같은 한 곳(봇 목록) 으로 간다. 되짚기를 남겨 두면 탭과 함께 길이 둘이 되어 같은 고장이 되살아난다. 스택은 «온 길의 기록» 으로만 남아 goTo(거기까지 되돌아가기)와 dismiss(어두워진 채팅 탭·Esc → 채팅)가 쓴다. 탭 넷이 진짜 다른 화면이 된다 — «투두랑 폴더를 클릭했을 때 사실상 똑같은 메뉴가 나오는데 그러면 의미가 없잖아». Panel 에 only 를 주어 할 일 탭은 todo.md 의 할 일만, 폴더 탭은 파일 트리만 보인다(그 절은 늘 펼친 채로 — 접혀 있으면 탭이 빈 화면이 된다). 폰에서 빠지는 것: 세션 · 명령·스킬 · 루틴(세션은 채팅 헤더의 봇 이름에서 고른다 · 나머지는 «모바일에서 보고 쓸 일이 없다»). 지우기 · 은퇴는 폴더 탭 아래에만 작게 남겼다(Dave 선택) — 할 일 탭에는 없다. 데스크톱·중간은 종전 그대로 전부 보인다. 문서 탭은 비어 있어도 눌린다 — «문서가 없을 때 누르면 그냥 빈 문서 보여 주면 돼». 종전의 disabled 는 손가락에 아무 반응도 안 줘서 «고장» 으로 읽혔다. 이제 「연 문서가 없어요 · 아래 폴더 탭에서 파일을 누르면 여기에 열려요」와 [폴더 열기]가 뜬다. 🔴 여백 — 안전영역을 두 번 빼고 있었다: «채팅창과 하단 탭 사이의 여백이 너무 많다». --tablift 가 이미 safe-area-inset-bottom 을 품고 있는데 폰 규칙이 .chat-foot { padding-bottom: calc(var(--sab) + 4px) } 로 또 더해, 아이폰에서 30여 px 이 그냥 비어 있었다. 탭이 있을 때는 숨 자리 6px 만 둔다(탭이 없을 때는 종전대로 안전영역을 뺀다). 🔴 실측으로 드러난 진짜 버그 — 시트의 [추가]가 안 눌렸다: 시트는 서랍 안에 있어 z-index:90 이 서랍의 쌓임 맥락(31) 안에서만 셈이 된다. 탭(32)이 늘 떠 있게 되자 시트 아래쪽 단추가 탭에 깔렸다. 시트를 탭 높이만큼 올리고(bottom: calc(var(--kbh) + var(--tablift))), 더해서 시트 단추가 초점을 뺏지 않게 했다 — 누르는 순간 초점이 빠지면 키보드가 내려가고 탭이 올라오며 시트가 움직여 mousedown 과 mouseup 사이에 단추가 자리를 옮긴다(AB 의 보내기 단추와 똑같은 고장이다). 검사는 스모크 「AD」 — 👉 는 어디서든 봇 목록 · 👈 는 아무 일 없음 · 좌상단 셋 다 봇 목록 · 할 일 탭과 폴더 탭이 다른 화면(제목·트리 유무·지우기 줄) · 문서 탭이 비어도 빈 문서 화면 · 내려 읽어도 탭이 안 움직임 · 입력칸과 탭 사이 여백 ≤ 14px. 이 검사는 고치기 전 판(빌드까지 되돌려)에서 실제로 빨갛다(«👈 가 아직 살아 있다»). ⚠ 옮겨 잴 수 없어 접은 계약 하나 — V-3 의 «명령·스킬 줄 44px» 는 폰 전용 규칙이었는데 그 절이 폰에서 빠졌다(중간 폭에서는 29px 가 맞는 크기라 옮길 수 없다). V-4 의 «명령·스킬 한 칸» 계약은 중간 폭으로 재는 자리를 옮겨 살렸다. 교훈: 숨기는 기능에는 「숨어서 무엇을 버나」라는 답이 있어야 한다 — 답이 없으면 그건 기능이 아니라 깜빡임이다.

문서 화면에서 탭이 사라지던 것 — 편집기가 «가져간» 초점 (AE · 2026-09-23, Dave 실기기): «현재 문서 화면에서 하단 탭이 안 보여요. 하단 탭은 채팅, 문서, 폴더 모두에서 없어지면 안 됩니다». 탭을 띄울지는 tabsHere = phone && view !== 'list' && !kb 였는데, kb(키보드가 올라왔나)는 «입력 중이냐» 하나로만 정해진다 — 그 판정은 2026-09-13 의 사고 네 건(문턱으로 재다가 두 번, 최대 높이를 기억하다가 한 번, 화장까지 문턱으로 접다가 한 번)에서 얻은 결론이라 손대면 안 되는 자리다. 문제는 문서를 열면 CodeMirror 가 스스로 초점을 가져간다는 것이다(Y 라운드에서 md 한 개 ⌘C 가 안 되던 것과 같은 버릇). 사람은 아무것도 안 쳤고 키보드도 안 올라왔는데(--kbh: 0px 실측) 앱은 «입력 중» 으로 읽어 문서 화면의 탭이 통째로 사라졌다. ⛔ --kbh > 0 로 «진짜 키보드» 를 가르는 길은 못 쓴다 — 레이아웃 뷰포트까지 함께 줄어드는 판(iOS 26 · 안드로이드 · 타사 키보드)에서는 키보드가 열려도 그 값이 0 이다. 그게 애초에 문턱을 버린 이유다. 처방은 조건을 좁히는 쪽이다 — 탭이 숨을 이유는 하나뿐이다: 키보드가 입력칸을 가리는 채팅 화면. 문서·폴더에는 입력칸이 없으므로 숨을 이유가 없다. !(kb && view === 'chat') 로 바꿔 kb 의 뜻은 그대로 두고 탭만 제 사정으로 판단하게 했다. 덤으로 «초점이 바뀔 때마다 탭이 생겼다 사라지며 그 위의 시트가 움직이던 것»(AD 에서 [추가] 단추가 안 눌리던 고장의 뿌리)도 문서·폴더 화면에서는 아예 일어나지 않는다. 검사는 스모크 — 폰에서 문서를 연 직후 .tabbar 가 있고 문서 아래 줄(앞뒤·「봇에게 이 문서로 말하기」)이 탭에 안 깔린다. 이 검사는 고치기 전 판에서 실제로 빨갛다 — «문서 화면에 하단 탭이 없다 · cls: app phone kb notabs · activeElement: cm-content · --kbh: 0px»(편집기가 초점을 가져갔다는 증거가 실패 메시지에 그대로 찍힌다). 교훈: «초점이 있다» 와 «사람이 입력 중이다» 는 다른 말이다 — 편집기는 초점을 스스로 가져가고, 그 차이가 Y 와 AE 에서 두 번 고장을 냈다.

모델 목록이 조용히 낡던 것 — CLI 가 정본이다 (AF · 2026-09-23, Dave: «오퍼스 5.5랑 페이블 5.2가 나왔는데 적용이 안 돼 보여요»): 모델 목록은 core/agents.ts 에 박혀 있다(첫 목록 = 종류별 최신 하나씩 · 옛 판은 「더 많은 모델」). 박아 두는 선택 자체는 그대로 둔다 — 고를 것을 짧게 유지하려고 2026-09-14 에 Dave 가 정한 규칙이다. 문제는 새 모델이 나와도 아무것도 알려 주지 않는다는 것이었다. 확인은 짐작이 아니라 실측으로 했다 — 봇이 실제로 부르는 claude CLI 의 번들에 모델 이름·별칭·표시 이름이 그대로 들어 있다(strings -a $(readlink -f $(which claude)) | grep -oE 'claude-(opus|sonnet|haiku|fable)-[0-9-]+' | sort -u). 2026-09-23 실측(CLI 2.1.280, npm 최신과 같은 판): claude-opus-5-5 있음(별칭 opus55 · 표시 이름 Opus 5.5) · Fable 5.2 없음 — Fable 은 claude-fable-5 와 claude-fable-5-1 뿐이다. CLI 가 이미 최신이므로 «CLI 를 올리면 나온다» 도 아니다. 그래서 Opus 5.5 만 넣었다: 첫 목록의 Opus 자리를 claude-opus-5-5 로 올리고 claude-opus-5 는 「더 많은 모델」 맨 위로 내렸다(종류별 최신 하나씩 규칙 그대로 · 기본 모델은 claude-fable-5-1 로 그대로). 🔴 다시 낡지 않게 검사를 남겼다 — test/unit/agentModels.test.ts 가 ① 같은 모델이 두 목록에 없고 ② 첫 목록이 종류별 하나씩이고 ③ 기본 모델이 첫 목록 안에 있고 ④ 첫 목록의 모든 이름을 깔려 있는 CLI 가 아는지 대조한다(없는 이름은 CLI 가 그 자리에서 거절하므로 조용한 고장이 된다). ⚠ CLI 가 없는 기기에서는 ④ 를 건너뛴다 — 못 잰 것을 「통과」로 적지 않는다(CI·컨테이너에는 CLI 가 없다). 되돌려 확인: 가짜 이름(claude-opus-5-9)을 넣으면 «CLI 가 모르는 모델» 로 빨갛고, 옛 목록(Opus 5)으로 되돌리면 «expected 'Opus 5' to be 'Opus 5.5'» 로 빨갛다. 교훈: 박아 둔 목록에는 «정본이 어디 있고 어떻게 다시 재나» 를 주석으로 같이 박아 둔다 — 목록만 있으면 다음 사람은(반년 뒤의 나까지) 그게 언제 적 것인지 알 길이 없다.

폰 마무리 다섯 — 미끄러지는 자리 · 좌상단 · 키보드 여백 · 머리말 크기 · 사라진 세션 (AG · 2026-09-24, Dave 실기기): AD·AE 로 폰의 길을 정리한 뒤 실제로 써 보고 나온 다섯 가지다. ① 봇 목록이 «엉뚱한 화면에서» 나왔다 — «왼쪽 슬라이딩 메뉴는 그 위치에서 나와야 하는데 지금은 채팅창에서 슬라이딩이 나온다». view 가 값 하나라 목록으로 가는 순간 문서·폴더 서랍이 먼저 사라지고 그 밑의 채팅이 드러난 뒤 목록이 덮였다 — 눈에는 「엉뚱한 화면으로 한 번 갔다 오는」 것으로 보인다. 밑에 깔린 화면(under = 마지막으로 본 목록 아닌 화면)을 따로 들고 있다가 목록이 열려도 그대로 둔다. 쌓임 순서는 목록(33) > 스크림(32, 목록이 열렸을 때만) > 있던 화면(31) — ⛔ 스크림을 평소에도 올리면 오른쪽 서랍 자체가 가려지므로 .scrim.over 로 그때만 올린다. ② 좌상단 버튼이 화면마다 달랐다 — 채팅·폴더·할 일은 목록 아이콘인데 문서만 ‹(뒤로) 였다(AD 에서 Panel 만 바꾸고 Doc 을 빠뜨렸다). 네 화면 모두 같은 아이콘·같은 이름(「봇 목록」)으로 맞췄다. ③ 키보드와 입력칸 사이가 떴다 — AD 에서 넣은 .app.phone.notabs .chat-foot { padding-bottom: calc(var(--sab) + 4px) } 가 .app.kb .chat-foot { padding-bottom:6px } 보다 세서, 키보드가 이미 덮은 홈 인디케이터 자리(34pt)를 한 번 더 비우고 있었다. :not(.kb) 를 붙여 키보드가 올라왔을 때는 안 먹게 했다 — 2026-09-13 의 «그 34pt 는 이미 키보드가 덮은 자리다» 와 같은 함정을 내가 다시 판 것이다. ④ 머리말 크기가 화면마다 달랐다 — 채팅은 16px/44px 인데 문서·폴더·할 일은 22px/자동 높이였다. 채팅 머리말에 맞춰 통일했다(가장 자주 보이고 글이 가장 긴 자리라 그쪽이 기준이다). ⑤ 세션 목록이 사라졌다 — AD 에서 폰 패널을 「할 일」과 「폴더」로 쪼개며 세션 절을 통째로 뺐는데, 세션은 여러 개를 한눈에 봐야 하는 것이라 헤더의 봇 이름 메뉴만으로는 모자랐다(특히 루틴이 실행될 때마다 새 세션이 생긴다). 「할 일」 탭에 세션을 할 일 위에 되돌렸다(「폴더」 탭에는 여전히 없다). 덤으로 접는 손잡이가 거짓말하지 않게 secOn() 하나로 묶었다 — AD 에서 only 일 때 본문만 강제로 펼치고 ∨ 표시는 접힌 채였다. 검사는 스모크 「AG」 — 세션이 할 일 위에 있고 줄이 실제로 보이며 · 좌상단을 눌러도 있던 화면이 그대로 남고(쌓임 순서까지) · 네 화면의 머리말 글씨와 좌상단 아이콘이 같고 · 키보드가 올라오면 아래 여백이 10px 이하다. 이 검사는 고치기 전 판에서 실제로 빨갛다(«「할 일」 탭에 세션 목록이 없다 · secs: [할 일, todo.md]»). 교훈: 한 화면을 쪼갤 때는 «무엇이 사라지는가» 를 목록으로 적어 두고 하나씩 되묻는다 — AD 에서 세션·명령·루틴을 한꺼번에 뺐는데, 셋 중 하나는 뺄 것이 아니라 옮길 것이었다.

폰 손질 셋 — 가장자리 쓸기 · 덮개를 걷으면 제자리 · 모델 목록의 정본 (AH · 2026-09-24, Dave 실기기): ① 쓸기가 쓸리는 행과 다퉜다 — «할 일 같은 경우에는 슬라이드가 조금 겹치는 동작이 있어 · 정말 맨 왼쪽 끝에서 잡아서 당겼을 때만». 화면 어디서나 👉 가 잡히니 할 일·폴더의 쓸리는 행(V16)과 손가락을 두고 다퉜다 — 둘 다 가로로 끌기 때문이다. 이제 왼쪽 24px 안에서 잡았을 때만 시작한다(core/drawer.fromLeftEdge · 순수 · 유닛). 🔴 실측으로 드러난 덤: 가장자리 밖에서 글 위를 끌면 브라우저가 선택을 남기는데, 그 선택 때문에 canStartSwipe 의 「글 고르는 중」 규칙에 걸려 바로 다음 가장자리 쓸기가 통째로 막혔다(Z-2 에서 본 것과 같은 부류). 가장자리를 잡는 것은 «이제 목록을 보겠다» 는 분명한 뜻이라 남은 선택을 치우고 시작한다 — 24px 에는 고를 글도 거의 없다. ② 목록을 도로 넣으면 채팅으로 튕겼다 — «할 일 화면에서 집어넣으면 할 일 상태가 그대로 유지되어야 하는데 항상 채팅 화면으로 가더라». AG 에서 봇 목록을 «덮개» 로 만들어 밑의 화면(under)을 살려 뒀는데, 닫는 길(dismiss)은 여전히 채팅으로 가고 있었다 — 덮개를 걷었더니 엉뚱한 방이 나온 셈이다. 목록을 닫을 때는 under 로 돌아간다(그 밖의 닫기는 종전대로 채팅). ③ 새 모델이 영영 안 떴다 — «모델 리프레시 기능 같은 게 있어서 전체 모델 반영이 가능하도록». 새로고침 장치(refreshModels → /api/agents/models)는 이미 있었는데, Claude 쪽 채우는 곳이 claude --help 를 긁고 있었다 — 도움말에는 모델 목록이 없다(예시 한두 개뿐). 그래서 아무리 새로고침해도 「더 많은 모델」은 비어 있었다. 정본은 CLI 실행파일 안에 있다(이름·별칭·표시 이름이 그대로 들어 있다 — AF 에서 손으로 확인한 그 자리). 211MB 라 통째로 읽지 않고 4MB 씩 흘려 읽으며 줍고(토막 경계에서 이름이 잘리지 않게 64바이트 겹쳐 읽는다), 결과는 판(경로·크기·수정시각)으로 캐시해 CLI 를 올리기 전에는 다시 안 읽는다. 못 주우면 빈손이고 화면은 골라 둔 목록 그대로다. 🔴 덤으로 이름표 버그: modelLabel 이 === 로만 찾아, CLI 가 claude-opus-5-5-20260… 처럼 판을 박아 보내면 못 찾고 소문자 대체 이름(「opus 5」)이 칩에 떴다 — Dave 스크린샷의 그 글자다. 호스트가 쓰는 sameModel 하나로 맞췄다(5 와 5.5 는 여전히 다른 모델이다 — 앞자리가 겹친다고 같아지지 않는다). ⚠ Opus 5.5 자체는 CLI 에서 정상이다 — claude --model claude-opus-5-5 -p … 가 답을 주고 스트림도 "model":"claude-opus-5-5" 로 찍힌다(실측). 그래서 「제대로 안 된다」가 무엇이었는지는 아직 재현하지 못했다 — 칩 글자였다면 이번 이름표 수정으로 끝이고, 다른 증상이면 무엇이 보였는지가 더 필요하다. 검사는 스모크 「AH」 — 안쪽(120px)에서 잡은 끌기는 목록을 안 열고 가장자리(8px)에서는 열며 · 할 일·문서에서 목록을 닫으면 그 화면으로 돌아온다. 유닛은 가장자리 규칙 · 날짜 붙은 이름표 · 실행파일에서 claude-opus-5-5 를 실제로 줍는지(두 번째 호출은 캐시)를 잰다. 고치기 전 판에서 실제로 빨갛다(«안쪽에서 잡은 끌기가 봇 목록을 열었다»). 교훈: «덮개» 를 만들면 걷는 길도 같이 만든다 — 여는 쪽만 고치면 닫을 때 엉뚱한 데로 간다.

모델은 호스트가 아는 것만 · 쓸기는 한 뜻만 · 루틴은 묻지 않고 둘까지 (AI · 2026-09-24, Dave 결정 + 실기기): ① 「Opus 5.5 가 제대로 안 된다」의 정체 — Dave 스크린샷에 답이 있었다: «Prompt is too long · automatic compaction failed: API Error: 400 Claude Code 2.1.278 does not support this model; version 2.1.280 or newer is required». 앱은 호스트(미니)의 CLI 로 도는데 그 CLI 가 2.1.278 이었다(내가 AF 에서 잰 2.1.280 은 이 맥의 것이다 — 같은 이름의 다른 기계를 쟀다). 그런데 첫 목록은 코드에 박혀 있어 그 호스트가 못 돌리는 Opus 5.5 를 태연히 내밀었다. 🔴 그래서 첫 목록을 호스트 CLI 가 아는 것에서 만든다(Dave: «이름당 가장 최신 모델만 뜨고 나머지는 더 많은 모델로») — splitModels()(순수 · 유닛)가 주워 온 이름을 종류별 최신 하나씩(첫 목록)과 나머지(더 많은 모델)로 가른다. 판 비교는 숫자로 한다(글자 순서로 세면 5.5 가 5 보다 앞서고 4.10 이 4.9 보다 뒤진다). 못 주워 왔으면 박아 둔 목록 그대로다. 이제 낡은 호스트에서는 Opus 5.5 가 아예 안 보이므로 고를 수도 없다. 🔴 덤 — 칩에 <synthetic> 이 박혔다: 오류 턴에서 CLI 가 "model":"<synthetic>" 을 보내는데 그대로 받아 세션 모델로 저장했다. 모양이 claude-<종류>-<판> 이 아니면 기계가 쓴 표식이지 사람이 고를 모델이 아니다 — modelParts() 로 거른다. ② 오른쪽 쓸기는 «봇 목록» 하나뿐 (Dave: «그 액션에서 뒤로 가기는 완전히 없애») — 종전에는 드릴인(서브에이전트) 안에서 👉 가 «드릴에서 나오기» 로 먼저 먹혔다(fb:nav). 같은 손짓이 자리에 따라 다른 뜻이 되면 못 배운다 — AD 에서 길을 하나로 모은 것과 같은 이유로 이 예외도 걷었다. 드릴에서 나오는 길은 헤더의 ‹ 하나다. ③ 루틴은 묻지 않고 바로 돈다 (Dave 결정) — 루틴은 사람이 없을 때 도는 것이라 중간에 물으면 그 회차가 그냥 멈춰 선다. 기본 승인 수준을 always(bypassPermissions)로 올렸다. ⚠ 이건 권한을 올리는 결정이라 세 가지를 함께 둔다: 화면의 승인 수준 칸에 무슨 뜻인지 계속 적어 두고 · 루틴마다 낮출 수 있고 · 봇은 이 값을 못 바꾼다(AA-5 의 «MCP 도구에 approve 가 없다» 는 그대로). ④ 루틴 세션은 둘까지 (Dave: «바로 직전 루틴까지만 남기고 두 개 이상 가져가지 않는») — 루틴이 돌 때마다 새 세션이 생겨 목록이 루틴 기록으로 덮이던 것을 막는다. 새로 만든 뒤 오래된 «끝난» 것부터 걷는다(routinesToDrop · 순수 · 유닛). 🔴 도는 중이거나 확인을 기다리는 것은 안 걷는다 — 그걸 지우면 하던 일이 끊기고 안 읽은 답이 사라진다. 그 결과 도는 것이 많으면 한동안 둘을 넘을 수 있는데, 일을 죽이는 것보다 낫다. 「루틴마다 한 세션에 이어 쓰기」는 Dave 가 접었다 — «루틴이 너무 길어지면 제일 걱정되는 건 토큰» 이라, 매번 새 세션으로 시작하는 편이 문맥을 안 끌고 간다. 검사: 첫 목록이 종류별 하나씩인지와 「더 많은 모델」에 옛 판이 있는지를 화면에서 재고(⚠ 팝업이 첫 목록을 그대로 두고 덧붙이는 구조라 «겹치지 않는가» 는 화면에서 못 잰다 — 그 계약은 splitModels 유닛이 데이터에서 잰다), 드릴인 안에서 👉 가 목록을 여는지, 루틴 기본 권한과 걷기 규칙을 유닛이 잰다. 고치기 전 판에서 실제로 빨갛다. 교훈: 버전을 잴 때는 «어느 기계의 것인가» 를 같이 적는다 — 나는 이 맥의 CLI 를 재고 호스트의 것이라고 적었고, 그 한 줄 때문에 Dave 는 못 쓰는 모델을 골랐다.

CLI 업데이트를 화면에서 · 보던 봇은 안 잃는다 · 목록을 끌면 키보드는 내려간다 (AJ · 2026-09-24, Dave): ① CLI 가 낡아서 나는 고장을 화면이 먼저 말한다 — «claude 업데이트가 필요하다면 모바일에서든 데스크톱에서든 claude 업데이트 메뉴가 떠야 돼». 앱은 호스트의 CLI 로 도는데 그게 낡으면 새 모델이 400 으로 죽고, 그 사정은 오류 글 안에만 있었다(«Claude Code 2.1.278 does not support this model; version 2.1.280 or newer is required»). 사람은 그 줄을 읽고 어느 기계에 들어가 무엇을 쳐야 하는지 스스로 알아내야 했다 — 원격에서 폰으로 보고 있으면 할 수 있는 일이 없다. 🔴 판정은 오류 글 하나로 한다(core/cliUpdate.requiredCliVersion · 순수 · 유닛) — 「최신이 무엇인가」를 밖에 물으러 가지 않는다(네트워크·설치 방식마다 달라 못 믿는다). CLI 가 «몇 판이 필요하다» 고 정확히 말해 주므로 그 말이 정본이다. 판 비교는 숫자로 한다(2.1.9 < 2.1.10). 지금 판보다 높은 것이 필요할 때만 입력칸 위에 노란 띠가 뜨고, [지금 업데이트] 가 호스트에서 claude update 를 돌린 뒤 새 판을 다시 읽어 돌려준다 — 「했다」가 아니라 「이렇게 됐다」를 말한다(2.1.280 → 2.1.281 로 실제 동작 확인). ⚠ Codex 는 안 연다 — 올리는 길이 설치 방식마다 달라(npm·brew·직접) 함부로 못 부른다. 🔴 실측으로 배운 것 — 검사가 실 CLI 를 올려 버렸다: 스모크가 이 끝점을 두드리자 진짜 claude update 가 돌았다(2.1.280 → 2.1.281). 리포 규칙(«실 볼트·실 앱·실 CLI 를 QA 에 쓰지 않는다»)을 끝점 하나 만들면서 어겼다 — 이제 FOLDERBOT_QA 가 켜져 있으면 길이 열려 있는지만 답하고 실제로는 안 올린다. 스모크는 그 «건너뜀» 을 확인한다(안 그러면 다음 사람이 같은 자리를 또 밟는다). ② 보던 봇을 잃지 않는다 — «CCAF 폴더를 보다가 폴더봇 리스트를 잠깐 열었다 닫으면 CCAF 가 그대로 나와야 하는데 지금 오케스트레이터로 간다». 헤드리스로 그 길(채팅 → 목록 → 닫기)을 재 보니 봇이 유지됐다 — 즉 다른 길에서 hash.bot 이 비고 있었다(브라우저 뒤로 · 해시 없는 링크 · 되살아난 탭 — 가장자리 쓸기가 Safari 의 뒤로 제스처와 겹치는 것도 그중 하나다). 원인을 하나로 특정하는 대신 떨어지는 자리를 막았다: botId = hash.bot || 마지막으로 보던 봇 || 'orch'. 오케스트레이터는 사람이 고를 때만 나온다. 해시도 도로 채운다 — 주소와 화면이 갈리면 다음 되돌리기가 또 튄다. ③ 목록을 끌어 오면 키보드가 내려간다 — 목록을 보겠다는 것은 «지금 쓰던 글은 잠시 두겠다» 는 뜻이고, 키보드가 반쯤 덮은 채 열리면 볼 수 있는 폴더가 절반으로 준다. 가장자리를 잡는 순간 초점을 놓는다. 검사: 유닛(실제로 난 오류 글에서 판 읽기 · 엉뚱한 오류를 안 집기 · 숫자 비교)과 스모크(끝점이 판을 주고 QA 에서는 건너뛴다 · 목록을 끌면 입력칸이 초점을 놓는다 · 닫아도 봇이 그대로다). 고치기 전 판에서 실제로 빨갛다(«업데이트 길이 이상한 답을 준다 · 404»). 교훈: 새 끝점이 바깥 세상을 건드리면, 그 끝점을 만드는 그 손으로 QA 차단도 같이 단다.

한 겹 걷기 · 공백이 든 파일 이름 · 안 끝난 일이 위 (AK · 2026-09-24, Dave): ① 「+ → 사진에서 고르기」가 2뎁스였다 — «세 개의 메뉴가 뜨는 대신 바로 사진첩이 열렸으면 좋겠어 … 무조건 다 1depth로». 재 보니 그 세 줄짜리 시트는 우리 것이 아니라 iOS 것이었다: 웹앱이 <input type=file> 을 열면 사파리가 「사진 보관함 / 사진 또는 비디오 찍기 / 파일 선택」을 반드시 한 번 띄우고 이를 건너뛰는 속성은 없다(accept="image/*" 를 줘도 같다). 그래서 우리가 걷을 수 있는 겹만 걷었다 — 어차피 같은 시트로 가던 「사진에서 고르기」와 「이 기기에서 파일 올리기」를 한 줄(「사진·파일 고르기」)로 합쳤다. 카메라는 capture 가 있어 진짜 1뎁스라 남겼다. ② 입력창이 길어지면 + 팝업 윗줄을 못 눌렀다 — 팝업은 입력창 위에 붙어 위로 자라는데 높이 상한이 60vh 고정이라, 입력창이 화면 절반을 먹는 순간 머리가 화면 위로 넘어갔다. 안에 스크롤이 있어도 화면 밖으로 나간 부분은 손이 닿지 않는다. 열린 뒤 제 위치를 재서 넘친 만큼만 깎는다(--popmax, 천장은 헤더 아래 8px). ③ 파일 칩이 끊겼다 — «파일명에 띄어쓰기가 되어 있거나 한글이 있을 때». 한글은 애초에 통과했고 막던 것은 공백이었다: 첨부/스크린샷 2026-09-23.png 가 첨부/스크린샷 에서 끊겨 있지도 않은 파일을 가리켰다. 절대 경로는 이미 오른쪽으로 넓히고 있었는데 상대 경로만 빠져 있었다 — 이어 붙일 조각이 확장자로 끝날 때만 잇고(문장을 안 삼킨다) 왼쪽 확장(3. Area/…)과도 조합한다. ④ 할 일에서 끝난 것이 안 끝난 것을 덮었다 — «아직 끝내지 않은 항목이 맨 상단에 떠야 돼». 차례가 파일 순서 그대로여서, 제자리에서 체크한 항목이 안 끝난 일 사이에 섞여 있었다. 이제 절 안에서 안 끝난 것이 위로 오고 끝난 것은 최근 3개만 보이며 나머지는 「끝난 일 N개 더보기」 뒤로 간다. 완료 시각은 파일에 없으므로 지어내지 않고, 체크하면 뒤로 가는 성질을 써서 파일 뒤쪽 = 최근으로 본다. 파일은 안 건드린다 — 보여 주는 차례일 뿐이라 todo.md 를 다른 도구로 열면 쓴 그대로다. ⑤ 대화의 맨 아래가 탭 바 뒤에 숨었다 — «대기 화면이 나오면 채팅의 맨 아래 화면까지 안 보이는». 폰의 .chat-foot 은 탭 바만큼 떠 있는데 .chat-body 아래 여백은 --footh 만 비워, 늘 탭 바 높이만큼 모자랐다(대기 줄이 마지막 줄이라 그때 제일 크게 보였을 뿐이다). 바로 옆 .tobot 은 이미 세고 있었다 — 둘이 같은 셈을 쓰게 했다.

두 벌의 CLI · Claude 앱 결의 모델 시트 · 여백 규칙을 문서로 (AL · 2026-09-24, Dave): ① 「Opus 5.5 가 목록에 없다」의 정체는 CLI 가 두 벌이었던 것이다 — 이 맥에 ~/.local/bin/claude(2.1.278)와 /usr/local/bin/claude(2.1.281)가 함께 깔려 있었고, 호스트는 **판을 안 보고 후보 경로를 순서대로 훑어 「먼저 있는 것」**을 집었다. 그래서 늘 낡은 2.1.278 이 이겼고, 그 번들에는 claude-opus-5-5 가 아예 없다. 목록은 거짓말한 것이 아니라 그 CLI 가 아는 것만 정직하게 보여 준 것이었다(AI 라운드의 계약대로). 재시작해도 같은 경로를 다시 집으니 새로고침으로도 안 고쳐졌다. 이제 후보의 판을 재서 가장 새 것을 고른다. 덤으로 --version 비교가 "2.1.281 (Claude Code)" 의 꼬리 글자에 속아 281 과 278 을 같다고 보던 것도 고쳤다. ② 재시작 대신 새로고침이 생겼다 — POST /api/agents/refresh 가 후보를 다시 훑고 판을 다시 읽는다. 모델 시트 맨 아래가 「이 맥의 Claude CLI 2.1.281 이 아는 목록 · 새로고침」이라, 「왜 Opus 5.5 가 없지」를 사람이 스스로 풀 수 있다 — 이번에 실제로 막혔던 자리다. ③ 모델 고르기를 Claude 앱 결로 바꿨다 — 폰에서 입력칸 위 작은 팝업 대신 아래에서 올라오는 시트다. 가운데 제목, 왼쪽 닫기, 카드 안에 이름과 설명, 고른 줄에 체크, 그 아래 「더 많은 모델」과 「노력」. 색만은 Claude 의 파랑이 아니라 우리 강조색을 썼다(DESIGN.md 의 검은 유리 계약). ④ 문서 화면 아래 여백은 안전영역을 두 번 센 것이었다 — 탭 바가 이미 --tablift 로 안전영역을 덮고 있는데 .dfoot 이 제 --sab 를 또 더해 아무것도 없는 34pt 띠가 생겼다(실측 44px → 10px). 같은 종류의 실수가 이번이 네 번째다. ⑤ 그래서 규칙을 문서로 뺐다 — docs/LAYOUT.md 에 바깥 정본(Vercel Web Interface Guidelines · MDN env()·뷰포트 단위)을 링크로 걸고 이 앱의 불변식 다섯을 적었다. ⑥ 검사가 왜 못 잡았는지도 고쳤다 — 브라우저에는 홈 인디케이터가 없어 env(safe-area-inset-bottom) 이 늘 0 이라, 실기기에서만 생기는 빈 띠가 검사에 한 번도 안 나타났다. --sab 를 토큰 하나로 모으고 검사가 그 값을 0 → 34px 로 갈아 끼워 **「안전영역을 키웠을 때 여백이 같이 커지는가」**로 판정한다(절대 여백은 못 쓴다 — 목록이 짧으면 아래가 비는 것이 정상이다). 눈으로 볼 수 있게 node test/shot.mjs 가 폰 크기 캡처와 여백 수치를 함께 낸다.

넘치지 않게 · 줄에 맞게 (AM · 2026-09-24, Dave 승인 뒤): 디테일 다섯을 고쳤고, 셋이 같은 뿌리였다 — 「폭이 좁아지면 안에 든 것이 상자 밖으로 나가거나 글자가 접힌다」. ① 「알림 켜기」가 점선을 뚫고 나간 것은 가로가 아니라 세로 넘침이었다 — .live 에 height:24px 가 박혀 있는데 대기 줄은 위아래 8px 여백에 26px 단추까지 품는다(실측 줄 24 · 단추 26 · 단추 top −1). 점선 상자만 height:auto 로 풀었다. ② 이미지 뷰어 바는 최대 폭도 줄바꿈 금지도 없어 폰에서 글자가 단추 안에서 「맞/춤」으로 쪼개졌다. 줄바꿈을 막고 최대 폭을 준 뒤, 좁은 화면에서는 아이콘만 남긴다(맞춤·원본 아이콘을 새로 그렸다). 글자는 지우지 않고 감춰서 읽는 프로그램에는 남는다. ③ 폰 입력칸의 안내 글이 위로 떠 있던 것은 그 글이 「입력칸 맨 위에서 2px」에 박혀 있었기 때문이다 — 그 2px 는 데스크톱의 안쪽 여백이고 폰은 8px 이라 6px 만큼 어긋났다. 폰용 값을 또 적는 대신 가운데 기준으로 바꿔 여백이 몇이든 맞게 했다. ④ 글줄 속 칩이 처져 보인 것은 inline-flex 의 기본이 밑선 맞춤이라서다 — 테두리와 여백이 있는 상자는 밑선을 맞추면 내려간다(실측 +7px → 1px). 손으로 맞춰 둔 픽셀 값(-5px·-7px)도 같은 규칙으로 걷었다. ⑤ 채팅 칸을 좁히면 보내기 단추가 옆 칸으로 나가던 것은 아래 줄의 항목이 하나도 줄지 않아서다(실측 채팅 칸 361px 에서 85px 넘침 — 단추가 나간 거리 그대로). 설명인 「첨부 N개…」가 먼저 줄어들게 했고, 최소 폭 360px 은 이제 깨지는 지점(≈318px)보다 위에 있다. ⑥ 그래서 폭을 훑는 검사를 만들었다 — npm run fit 이 폰 320·390·430 과 데스크톱 900·1440, 그리고 양옆을 키워 채팅을 한계까지 좁힌 경우를 돌며 정해 둔 상자의 가로·세로 넘침과 단추 안 글자 접힘을 잰다(npm run qa 에 물렸다). ⚠ 만드는 도중 그 검사가 헛돌고 있었다 — evaluate 에 인자를 함수로 넘기는 바람에 호출이 실패하고 catch 가 그걸 삼켜 늘 초록이었다. 초록이 「괜찮다」가 아니라 「안 봤다」였던 것이다. 그래서 이 검사는 상자 개수를 함께 찍는다.

안 읽음 링은 「네가 볼 차례」일 때만 (AN · 2026-09-24, Dave): 상태 배지는 두 겹이다 — 꽉 찬 원 하나에, 아직 안 본 답이 있으면 얇은 링이 더해진다. 그런데 그 링이 상태와 무관하게 붙어서, 봇이 한창 돌고 있는 주황에도 링이 생겼다(«안읽은게 있지만 끝나지 않은 상태에서는 더블링이 나오면 안돼»). 링은 「가서 봐라」는 뜻인데 그때는 아직 볼 것이 없다 — 뜻과 그림이 어긋나 있었다. 이제 색 목록이 아니라 「사람 차례인가」 하나로 가른다(core/unread.unreadRing): 아직 도는 중인 work(봇이 일하는 중)와 hold(남을 기다리는 중)는 원닷 하나, 사람이 볼 차례인 done(끝남)·wait(네 차례)·error(오류), 그리고 배지가 없는데 안 읽은 답이 있는 경우(쉬는 중 = 이미 끝난 것)는 링이다. Dave 의 범위 확인 답이 이 규칙을 정했다 — «초록 + 노랑 + 빨강 처럼 사람 확인이 필요한 모든 경우에 링이 필요해». 색이 아니라 뜻으로 적었으므로 나중에 색을 바꿔도 규칙은 낡지 않는다. 「안 읽음」 표시 자체가 줄어든 것은 아니다 — 목록 행의 굵은 제목과 밝은 미리보기가 그대로 남아 있고, 링은 그것을 거드는 표식이다.

좌우 쓸기로 앞뒤 페이지에 가지 않는다 (AO · 2026-09-24, Dave): «왼쪽 혹은 오른쪽으로 쓸기에서 이전 혹은 다음 페이지로 이동하는 기능이 여전히 남아 있어. 이거 없애기로 했는데». 앱 안의 가로 쓸기는 이미 봇 목록 하나로 줄여 뒀는데(AD·AH·AI) 데스크톱에서는 그게 아니었다 — **맥 트랙패드의 두 손가락 쓸기는 크로미움이 주는 「뒤로/앞으로」**이고, 그건 우리 손짓 코드를 아예 안 거친다. 그래서 화면 쪽에서 쓸기를 지워도 남아 있었다. 걷어 갈 것이 있었던 이유는 따로 있었다 — 화면을 옮길 때마다 location.hash 에 새 기록이 한 칸씩 쌓이고 있었다(실측: 봇을 네 번 오가자 history.length 3 → 7). ⇒ 손짓을 막는 대신 갈 곳을 없앴다. 같은 자리에 덮어쓰게 바꾸니(replaceState) 걷어 갈 기록이 없어, 트랙패드든 마우스든 ⌘[ 든 어디로 와도 아무 일이 없다. 손짓 하나를 막는 방식이었다면 기기와 입력 장치마다 다시 막아야 했을 것이다. 덤으로 데스크톱 셸에서 그 기능 자체(OverscrollHistoryNavigation)도 꺼서 두 겹으로 막았다 — 알림·트레이가 주소를 직접 바꾸는 길이 남아 한 칸이 생길 수 있기 때문이다. 주소는 그대로 둔다 — 딥링크(알림 → 그 대화)와 「마지막에 보던 자리」 복원은 주소를 읽으므로 그대로 산다.

@ 가 든 파일 이름이 칩이 되게 (AP · 2026-09-24, Dave): «왜 이 파일은 칩으로 안만들어진거야?» — CleanShot 2026-09-24 at 10.25.19 PM@2x.png(맥 캡처 도구가 짓는 이름)가 글자로만 남았다. 두 겹이었다. ① AK 에서 「공백이 든 이름」을 고쳤지만 그건 슬래시가 든 경로뿐이었고, 슬래시 없는 맨 이름을 맡는 쪽에는 왼쪽으로 넓히는 길이 아예 없었다(주석에는 있다고 적혀 있었다). 어디까지가 이름인지는 글만 봐서 못 정하므로 정하지 않는다 — 1낱말·2낱말·… 변형을 함께 내고 호스트가 「있는 것」을 고른다(경로에서 이미 쓰던 방식이다). ② 그런데 그걸 고쳐도 칩이 안 생겼다. 후보도 맞고 호스트도 파일을 찾는데 화면만 그대로여서 렌더된 HTML 을 찍어 보니 <a href="mailto:PM@2x.png"> 였다 — 마크다운(GFM)이 이름@도메인.확장자 를 메일 주소로 자동 링크한 것이다. 링크 안은 칩으로 바꾸지 않으므로 영영 칩이 될 수 없었고, 게다가 누르면 파일이 아니라 메일 앱이 떴다. 끝이 우리가 아는 파일 확장자일 때만 그 링크를 되돌린다 — 진짜 메일은 그대로 둔다. 검사는 픽스처에 그 이름 그대로 파일을 두고 칩의 rel 까지 대조하며, 메일 링크로 감싸지지 않았는지도 함께 본다.

목록은 맨 위로 덮고, 앞뒤 이동은 아예 갈 곳을 없앤다 (AQ · 2026-09-24, Dave): 아이폰 사파리 캡처(IMG_2124)에서 화면 전체가 오른쪽으로 밀리고 왼쪽에서 옛 화면이 따라 들어왔다 — «폴더보드가 레이어 뒤에 있는 것 같은 느낌 … 리스트만 와야 하는데 뒤에 배경이 같이 따라오거든». 그 그림은 우리 서랍이 아니라 iOS 사파리의 뒤로 쓸기였다(옛 페이지 스냅숏이 통째로 따라 들어온다). 우리 서랍은 이미 목록만 맨 위(z=33 > 스크림 32)로 덮고 뒤 화면은 제자리에 선다 — 실측으로 확인하고 그 수치를 검사로 못 박았다. 진짜 할 일은 뒤로 쓸기가 걷어 갈 기록을 없애는 것이었고, AO(v146)에서 화면 이동은 막았지만 구멍이 둘 남아 있었다 — 첫 로드에서 토큰을 떼며 주소를 바꾸는 자리와 셸의 알림 딥링크다. 사파리의 그 손짓에는 한 칸이면 충분하다. 둘 다 같은 자리에 덮어쓰게 바꿨다. ⛔ 한편 「popstate 로 되돌리는 덫」은 넣었다가 폐기했다 — 같은 문서 안의 정상 이동(알림 딥링크·# 링크)도 popstate 를 쏘기 때문에 덫이 그것까지 되돌려 #bot=… 이 지워지고 봇 목록으로 튕겼다(실측). 손짓을 가로채는 방식은 늘 정상 동작을 함께 잡는다 — 갈 곳을 없애는 쪽 하나로 간다.

폰에서 전체 복사 · 폴더 탭의 루틴 (AR · 2026-09-24, Dave): «모바일에서 코드블록 및 텍스트 문서 내용 전체 복사 기능이 없어 … 폴더 섹션에 모바일에서도 루틴 메뉴 추가해줘». ① 코드블록 복사 단추는 원래 있었다 — 다만 opacity:0 으로 숨어 있다가 마우스를 올려야 나타났다. 터치에는 올려놓기가 없으니 폰에서는 보이지도 눌리지도 않는, 있으나 마나 한 단추였다. 「폰이냐」가 아니라 「올려놓을 수 있는 기기냐」(hover: none)로 갈라 늘 보이게 했다 — 터치 노트북과 태블릿도 함께 맞는다. 크기는 보이는 알약 32px 에 누를 넓이만 44px 로 넓혔다(작은 표적은 보이는 크기를 키우지 말고 누를 넓이를 넓히라는 Web Interface Guidelines 방식이다). ② 문서에는 「내용 복사」를 새로 넣었다 — 종전 ⋯ 메뉴에는 「경로 복사」만 있었다. 경로는 맥 앞에서나 쓸모가 있고, 폰에서 남에게 글을 보낼 때 필요한 것은 글 자체다. 고치는 중이면 고치던 글을 주고, 잘린 문서면 「보이는 데까지 복사했어요」라고 밝힌다 — 다 복사한 척하지 않는다. ③ 루틴을 폰의 「폴더」 탭에 되살렸다 — AD 에서 「세션·명령·스킬·루틴은 폰에서 볼 일이 없다」고 정했는데 Dave 가 루틴만 뒤집었다. 만들고 고치는 일은 데스크톱에서 해도, 루틴이 도는지 보고 손보는 일은 오히려 폰에서 더 자주 생긴다. 「할 일」 탭에는 여전히 두지 않는다 — 그 탭은 지금 내가 할 일 하나만 담는 자리다.

낡은 CLI 를 화면이 말해 준다 (AS · 2026-09-25, Dave): 미니에서 Opus 5.5 를 고르자 400 Claude Code 2.1.278 does not support this model; version 2.1.280 or newer is required 가 났다. 모델을 돌리는 것은 호스트의 CLI 이므로, 맥북에 새 판이 있어도 턴을 도는 미니가 낡았으면 소용이 없다 — 여기까지는 설계대로다. 진짜 결함은 고칠 길을 화면이 안 알려 준 것이다. AJ(v141)에서 바로 이 경우를 위해 [지금 업데이트] 띠를 만들어 뒀는데 뜨지 않았다. 띠 조건이 「지금 판」을 /api/agents 에만 물었고, 그 답이 비면(version: null) 「낡았나」 판정이 조용히 false 가 되어 띠가 통째로 사라졌기 때문이다. 정작 오류 글에는 두 판이 다 들어 있다 — CLI 가 자기 판과 필요한 판을 또박또박 말해 준다. 그래서 물어본 값이 있으면 그것을 쓰고, 없으면 CLI 가 제 입으로 말한 값을 쓴다. 덤으로 이 판단을 화면 컴포넌트에서 core 로 내렸다 — 컴포넌트 안에 있어서 검사가 볼 수 없었고, 그래서 이 고장이 배포까지 갔다. 이제 한 곳(cliNeedsUpdate)이 정하고 유닛이 잠근다.

확인하는 claude 와 실행하는 claude 는 하나다 (AT · 2026-09-25, Dave): «무조건 모델이나 실행환경에서의 claude 버전만 확인하면 되는거 아니야?» — 맞다. 판정 기준은 턴을 실제로 도는 claude 의 판 하나다. 그런데 재 보니 확인하는 곳과 실행하는 곳이 서로 다른 claude 를 봤다. 미니에는 claude 가 두 벌(~/.local 2.1.278 · /usr/local 2.1.281) 있었고, AL 에서 목록 쪽(providers)만 「가장 새 판」으로 고쳤다. 턴을 띄우는 claudeBin() 은 따로 「먼저 있는 경로」를 훑고 있어 여전히 ~/.local 의 낡은 판을 집었다. 그래서 목록은 Opus 5.5 를 내주고 실행은 2.1.278 로 돌아 400 이 났다 — AS 에서 「미니가 낡았다」고 설명한 것은 틀렸다(봇이 돌던 곳이 바로 미니였다). 이제 claudeBin() 은 스스로 고르지 않고 providers 의 판정을 그대로 쓴다. 사람이 정해 준 경로(환경변수·설정)만 먼저 이긴다. 검사는 두 판을 가짜로 세워 **「확인한 파일 = 실행한 파일」**을 잰다(옛 코드에서 빨강 실측). 사고 당시는 두 벌 다 2.1.281 로 올라 있어 오류가 우연히 멎어 있었다 — 판이 다시 갈라지면 같은 400 이 났을 것이다.

배지는 「무슨 일이 있을 때」만 뜬다 (AU · 2026-09-25, Dave 승인): «기다리는 중이 아니고 읽지 않은 상태가 아닌 경우에는 닷 자체를 없애고 싶어 … 링이 다 있으니깐 현재 작업 중인 링이 뭔지 너무 헷갈려». 끝났고 이미 읽은 봇에도 초록 닷이 남아 있어, 목록에 닷이 너무 많았다 — 그 사이에서 정작 지금 돌고 있는 주황 닷(숨을 쉬는 것)이 묻혔다. 이제 닷을 그리는 경우는 둘뿐이다: ① 지금 돌고 있거나 누군가를 기다릴 때(일하는 중·남을 기다림·네 차례·오류) ② 읽을 것이 남았을 때. 끝났고 봤으면 아무것도 없다. 노랑(네 차례)과 빨강(오류)은 읽은 뒤에도 남긴다 — 끝난 게 아니라 멈춰서 사람을 기다리는 상태이기 때문이다(Dave 승인안). 바뀐 칸은 「끝남·읽음」 하나지만 효과는 목록 전체에 미친다: 남는 닷은 모두 「지금 무슨 일이 있다」는 뜻이 된다. 규칙은 core/unread.badgeOn 한 곳에 있고, 확인은 실제 컴포넌트를 그려서 했다.

어느 세션이 안 읽음인지 보인다 · 「모두 읽음」 (AV · 2026-09-25, Dave 승인): «다 읽었는데도 왜 더블닷이 안사라지지? 읽었다는 기준이 어떻게 돼?». 읽음은 세션마다 적힌다 — 그 대화를 맨 아래까지 본 순간(답이 아직 쓰이는 중이 아닐 때), 그 세션의 마지막 답 시각이 「여기까지 봤다」로 남는다. 봇 줄의 더블링은 그 봇의 세션 중 하나라도 안 읽었으면 켜진다 — 다른 세션에 온 루틴 결과를 놓치지 않으려는 것이다. 실측해 보니 오케스트레이터 세션 34개 중 딱 하나가 「본 뒤에 새 답이 온」 것이었다(00:39 에 보고 00:43 에 답이 더 옴). 규칙은 제대로 돌고 있었고, 문제는 어느 세션인지 화면이 알려 주지 않은 것이었다 — 세션 목록 어디에도 안 읽음 표시가 없어 지금 보던 세션을 다 읽고 나면 「다 읽었다」고 느낄 수밖에 없었다. 이제 세션 행에 초록 링과 굵은 제목으로 안 읽음을 보이고(배지와 같은 말 — 링은 「볼 게 있다」), 봇 메뉴에 **「모두 읽음」**을 넣었다. 읽은 지점은 각 세션의 마지막 답 시각으로 적으므로, 누르는 사이 새로 온 답까지 삼키지는 않는다. ⚠ 계획 단계에서 「읽음 기록이 없는 옛 세션 56개」를 한꺼번에 정리하자고 했는데, 그 숫자는 제 셈이 틀린 것이었다(앱은 저장된 마지막 답 시각만 보는데 대화 내용에서 추정해 셌다). 앱 기준 안 읽음은 볼트 전체에 2개뿐이라 일괄 정리는 하지 않았다 — 남은 것은 진짜 새 답이다.

내 말풍선의 파일 칩은 한 번, 한 가지 모양으로 (AW · 2026-09-25, Dave): «구지 칩이 두번 보일 필요 있나? 그리고 하단 칩 디자인이 더 마음에 들어». 말풍선에서 같은 파일이 두 번 나오고 있었다. 글 속 @이름 자리에는 단순한 알약이 서고, 그 아래에 같은 파일이 아이콘·이름·폴더 표식을 단 칩 줄로 한 번 더 모였다. 이제 글 속 자리에 아래 칩(FileChip)을 그대로 세우고, 아래 줄에는 글 속에 안 나온 첨부만 남긴다. 아래 줄을 통째로 없애지 않은 이유가 있다 — + 로 붙이고 글에는 @ 로 부르지 않은 첨부도 있는데, 그 줄을 지우면 무엇을 붙였는지가 화면에서 사라진다. 보통은 첨부가 전부 글 속에 있으므로 아래 줄이 아예 생기지 않는다. 글 속 칩은 AM 에서 맞춘 세로 정렬(vertical-align:middle)을 그대로 쓴다.

답한 질문의 도구 줄은 그 자리에서 멈춘다 (AX · 2026-09-25, Dave): «왜 이미 끝난 이전 도구가 계속 돌고 있어?». 미뤄진 질문(tool_deferred 의 AskUserQuestion)은 답을 호스트가 claude 의 stdin 에 tool_result 로 직접 넣는데, claude 는 그 결과를 stdout 으로 되돌려 내보내지 않는다. 도구 줄은 그 되돌아오는 줄을 받아야 닫히므로, 턴이 끝날 때의 일괄 정리까지 계속 돌았다 — 답 뒤로 긴 턴이 이어지면 그동안 내내 돈다. 이제 워커가 직접 넣었을 때 true 를 돌려주고, 관리자가 그 줄(t_<id>)을 답(취소면 오류 표시)으로 바로 닫는다. 이미 결과가 있는 줄은 건드리지 않는다.

**밤새 디자인 검수 + 코드 리뷰 (AY · 2026-09-25, Dave: «나에게 묻지 말고 눈으로 보면서 직접 사용해보면서 확인하고 … 완벽한 앱을 완성해줘. /design-review /review»)**: 픽스처 볼트·가짜 CLI 로 띄운 앱을 맥·중간·폰 폭, 다크·라이트로 찍어 보며 12건을 고쳤다. 공통 원인은 이름을 같이 쓰는 CSS 규칙이 남의 부품으로 새는 것이었다 — 채팅 서브에이전트의 .sub { display:flex } 가 상태 타일 부제목의 말줄임표를 끄고, 컨텍스트 게이지의 .ring svg { rotate(-90deg) } 가 할 일 완료 체크를 «›» 로 눕히고, 폰 목록의 .mrow { min-height:70px } 가 문서 메뉴 줄을 늘렸다. 그 밖에 문서 보기의 코드 블록·인라인 코드 모양(펜스를 숨기는 규칙 탓에 코드가 본문처럼 흩어졌다), 링크 아이콘 자리표시자(SVG 두 번 인코딩으로 빈칸)와 아이콘·첫 글자 묶음, 한국어 어절 줄바꿈(keep-all), 알림 미리보기 평문화(답 알림만 — 승인 대기 명령은 원문 그대로), 질문 타일의 [허용] → [답하기](+ 호스트가 질문에 온 허용을 400 으로 거절). 리뷰(자체 · Claude 적대 · Codex 적대)는 /ask 가 질문이 아닌 권한 요청에도 허용을 보내던 옆길, 코드 상자 margin 이 CodeMirror 높이 지도를 어긋나게 하던 것(투명 border 로), 펜스 안 표의 위젯화, 인라인 코드 속 주소 아이콘, 카드 보내기 실패의 무음을 잡았다. 수정마다 스모크 회귀 검사를 붙이고 고치기 전 코드에서 빨강을 실측했다.

폰에도 정렬 단추 · «사용자» · 대기는 유휴보다 위 (AZ · 2026-09-25, Dave): 폰 홈의 네 칸 아래에 레일과 같은 정렬 부품(SortBar)을 두고, 같은 저장값(fb:railsort)을 쓴다 — 라벨이 두 벌로 갈리지 않게. «직접» 은 «사용자» 로 바꿨고 값(manual)은 그대로라 저장된 선택이 이어진다. 상태 정렬에서 「대기」(다른 에이전트·도구를 기다리는 hold)가 맨 밑으로 간 것은 순위표가 그 갈래를 몰라 ?? 9 로 떨어졌기 때문이다. 표를 core/unread 로 옮기고 Record<BotMood, number> 로 바꿔, 갈래를 늘리면서 표를 빠뜨리면 타입 검사가 잡게 했다. 차례는 확인 대기 → 일하는 중 → 대기 → 문제 → 끝남 → 유휴 → 절전.

**뒤늦게 누른 알림도 그 세션으로 (BA · 2026-09-25, Dave: «원격환경에서 알람을 클릭하면 … 이상한 세션으로 가거나 …»)**: 셸(desktop/main.js 의 notify)이 new Notification() 을 띄우고 아무 데서도 붙잡지 않아, GC 한 번에 알림 객체와 click 처리기가 함께 사라졌다(--expose-gc 실측: GC 전 살아 있음 → 후 없음). 그 뒤 알림 센터의 배너를 누르면 macOS 는 앱만 앞으로 가져오고 화면은 보던 세션 그대로다. 실제 앱을 원격(클라이언트) 모드로 띄워 재현해 보니 처리기만 살아 있으면 창이 열렸든 닫혔든 정확히 그 세션으로 갔다 — 화면 쪽 길은 멀쩡했다. desktop/notes.js 가 알림을 클릭·하루·60개·종료 때까지 붙잡는다. ⚠ close 에서 놓지 않는다 — macOS 는 배너가 알림 센터로 들어갈 때도 close 를 쏘는데 그 배너는 여전히 눌린다. 앱이 꺼질 때는 붙잡은 배너를 알림 센터에서 걷는다(새 프로세스는 옛 배너의 클릭을 못 받는다). qa:mac ⑪ 이 GC 를 강제한 뒤 클릭해 지킨다.

알림을 눌러도 Dock 은 건드리지 않는다 (BB · 2026-09-25, Dave · 스크린샷_2010): BA 뒤 Dave 가 «세션으로는 가는데 Dock 이 확대된 채 굳는다» 를 봤다. showWin() 이 창을 띄울 때마다 app.dock.show() 를 무조건 불렀는데, 이 앱은 Dock 아이콘을 숨기지 않아 늘 «이미 보이는 아이콘 다시 보이기» 였다. macOS 는 그때 앱을 전면 앱으로 다시 바꾸며 Dock 을 새로 배치하고, 확대를 켠 Dock 은 그 틈에 굳는다. 종전엔 GC 로 클릭이 셸까지 못 와서 이 줄이 안 불렸다. 이제 숨겨져 있을 때만 부른다. qa:mac ⑪ 이 «이미 보이는 Dock 에 show() 0회» 를 지킨다.

참조 폴더를 작업 폴더에서 떼고, 파일 영역에서 더하고 본다 (BC · 2026-09-27, Dave · 스크린샷_1109): Dave 가 «참조 폴더 추가 기능을 파일 영역에 추가하고, 파일 영역에서 볼 수도 있게» 를 요청했다. 들여다보니 D 의 «참조 폴더로 추가» 는 .bot.yml repo 를 재사용하고 있었다. 그런데 repo 는 원래 코드 세션의 작업 폴더(cwd)라서, 누르면 안내 문구(«이 봇이 그 폴더를 읽을 수 있어요»)와 달리 다음 세션부터 봇의 작업 자리가 그 폴더로 옮겨갔다. 하나뿐이었고 빼는 단추도 없었다. 설계 보고 뒤 Dave 가 1안(참조를 따로 칸으로)을 골랐다. .bot.yml refs 는 볼트 기준 상대 경로 목록이고 최대 5개다. 세션 작업 폴더는 그대로 두고 --add-dir 로만 붙인다(Claude·Codex 둘 다 · session.extraDirs). repo 는 코드 cwd 로만 남는다. 볼트 밖·봇 폴더 안·봇 폴더를 품은 상위·중복·안쪽은 거부하고, 상위를 더하면 안쪽 것을 흡수한다. POST /bots/:id/refs {path, remove} 를 두고, 경로 가드 roots 에 refs 를 넣었다. 화면은 목업 3안 중 Dave 가 B안(트리 맨 아래에 잇기) 을 골랐다(PARA/3. Area/제품_FolderBot/04_리디자인/2026-09-27_BC-참조폴더-시안.html). 파일 트리 끝 구분선 뒤에 「참조 · 이름 · 읽기만」 줄이 잇고, 펼치면 그 폴더가 보이며, 파일을 누르면 「폴더 외 문서」(읽기만)로 열린다. ⋯ 에는 빼기·Finder 에서 보기·여기서 봇 시작이 있다. + 는 파일 머리 도구와 맨 아래 줄 둘 다 두었고, 폴더 창을 「고르기만」 모드로 띄운다(새 폴더 줄 없음 · 칸은 못 고름). 🔴 참조 줄은 내 파일 rows 와 따로 모은다 — 섞으면 ⇧ 고르기가 참조 파일까지 집어 지우기·옮기기 메뉴에 올린다. 새 색은 쓰지 않고 명도와 「읽기만」 표시로만 가른다(디자인 언어 1조). 검증: 유닛 refs.test.ts 9개 — 그중 맥 /var ↔ /private/var 정규화 차이로 상대 경로가 ../ 로 볼트를 빠져나가던 버그를 잡은 검사는 옛 코드에서 빨강을 확인했다. 스모크 «참조 폴더 · 파일 영역» 과 D 블록을 refs 로 갱신했다(참조 줄을 끈 판에서 빨강 확인). ⚠ 스모크가 하나를 더 잡았다 — 펼친 폴더 기억(fb:tree:<봇>)이 봇을 바꾸는 틈에 옆 봇으로 새는 경합이 원래 있었는데, 참조 폴더 경로(../)가 섞이자 오케스트레이터가 ../../3. Area/… 를 읽으려다 볼트 밖이 되어 호스트가 500 을 냈다. 트리는 이제 ../ 폴더를 지금 봇의 참조 폴더 안일 때만 읽는다(고치기 전 두 번 500 → 뒤 초록). 새는 경합 자체는 그대로다 — 다음에 손볼 거리.

사용량은 추정이 아니라 Claude 가 알려 준 실제 한도로 (BD · 2026-09-27, Dave · 스크린샷_1233): Dave 가 «상단의 claude 사용량이 실제 사용량으로 반영이 안 된다 · 근본적으로 고쳐 달라» 고 했다. 카드는 V23 부터 「내 예산 − 토큰을 정가로 환산한 비용」 추정이었다(요금제 한도는 로컬에 없다고 봤다). 구독에서는 캐시 읽기 토큰이 막대해서 정가로 환산하면 하루 $1438 이 나오고, 5시간 예산 $6.40 이 금세 0 이 됐다. 그래서 실제로는 5시간 25%·주간 68% 사용인데 «0% 다 썼어요» 를 띄웠다. Anthropic 문서(costs)도 구독자에게 달러 값은 청구와 무관한 추정이라고 적는다. 조사해 보니 CLI 의 stream-json 제어 요청 get_usage 가 /usage 와 같은 서버 값을 모델 호출 없이 준다(SDK usage_EXPERIMENTAL… · 실측 0토큰 · 2.1.283 · rate_limits.five_hour/seven_day.{utilization 0–100, resets_at ISO} + limits[]). host/planUsage.ts 가 격리된 빈 폴더에서 CLI 에게 한 번 묻는다(--no-session-persistence --setting-sources project --strict-mcp-config). 묻는 때는 켤 때·3분마다·턴이 끝날 때이고, 60초보다 자주 묻지 않는다. core/usage.parsePlanUsage 가 응답을 읽고, report 는 plan 이 있으면 Claude 의 남은 % 를 더 빠듯한 창(100 − max(5시간, 주간))으로, 다시 채워짐을 그 창의 초기화 시각으로 쓴다. 카드와 메뉴 막대는 같은 자리에 달러 대신 5시간·이번 주 남은 % 를 그리고, 기준 줄은 «Claude 가 알려 준 실제 한도 · N분 전» 이다. 실패하면 마지막 값을 그대로 쓰고 «새로 묻지 못해 마지막 값» 을 붙인다. 추정으로 바꿔 치우지 않는다. 한도가 없는 계정(API 키·3P)만 예산 추정으로 돌아가고 «추정» 이라고 적는다. ⛔ OAuth 토큰을 꺼내 /api/oauth/usage 를 직접 부르지 않는다 — 자격 증명 중개 금지(legal-and-compliance). CLI 에게 묻는 것이 경계 안이다. ⚠ get_usage 는 «EXPERIMENTAL» 이다. CLI 판이 올라 사라지면(unknown subtype) 이 길은 조용히 마지막 값·추정으로 떨어지니, 판을 올릴 때 가짜 CLI 대신 실 CLI 로 한 번 재 볼 것. 검증: 유닛 planUsage.test.ts 9개(실측 응답 모양 · 더 빠듯한 창 · 한도 없는 계정 · 가짜 CLI 로 묻기 · 실패해도 마지막 값 유지 — 옛 계산에서 빨강 확인) · 스모크 사용량 블록과 카드 기준 줄을 실제 한도 계약으로 갈았다(가짜 CLI 가 get_usage 를 거절하면 빨강).

세션 상한 12 → 워커 15 + 오래 안 쓴 것부터 절전 (BE · 2026-09-27, Dave): Dave 가 «호스트 세션이 왜 12개가 상한선이야?» 를 물었다. 12 는 초기 PRD(Q4)의 어림값이었고, 메모리를 재서 정한 수가 아니었다. 게다가 새 세션을 만들 때만 세었기 때문에 절전된 세션을 깨울 때는 막지 않았다. 미니 실측(2026-09-27)으로 살아 있는 워커 13개는 트리 RSS 평균 207MB(합 2.7GB)였고, 메모리 압력은 2(경고)·압축 6.5GB·스왑 1.05/2GB 였다. 메모리 압력에 맞춰 상한이 늘고 줄게 하는 안도 봤지만, Dave 가 «맥스를 15개로 두고, 더 많이 생기면 바로 이전에 안 쓰는 것들을 자동 절전으로 가는 거야. 그리고 다시 부르면 그때 resume 하는 방식» 을 골랐다. 이제 세는 대상은 세션 기록이 아니라 살아 있는 워커(프로세스) 다. 기록은 몇 개든 둔다. SessionManager.ensureWorker 가 새 워커를 띄우기 직전에 makeRoom 을 부르고, 15개가 차 있으면 sleepLru 가 lastActivity 가 가장 오래된 쉬는 워커 하나를 내린다. 이 절전은 유휴 60분 절전과 같다. 워커만 내리고 기록과 cliSessionId 는 남기므로, 다시 부르면 --resume 으로 이어진다. 🔴 일하는 중(running)·확인 대기(awaiting_input)·권한 질문이 걸린 워커(pending)·백그라운드 서브에이전트가 도는 워커는 절대 재우지 않는다. 15개가 모두 그런 상태면 새로 띄우지 않고 «워커 15개가 모두 일하는 중이에요 — 하나가 끝나면 이어서 할게요» 라고 답한다. 봇당 4 도 같은 방식이다. 4개가 차면 그 봇 안에서 가장 오래 안 쓴 쉬는 워커를 재우고, 모두 일하는 중일 때만 거절한다. 유휴 60분 절전(reclaim)도 같은 sleepable 판정을 쓴다. 검증: 유닛 lruSleep.test.ts 5개(가장 오래된 것부터 · 일하는 것은 건너뜀 · 모두 일하면 거절 · 살아 있는 워커에 보낼 땐 안 재움 · 봇 안에서만 고르기 — 옛 코드에서 4개 빨강 확인).

문서 창은 연 파일에만 · 사람이 고친 것만 · 연 판 위에만 쓴다 (BF · 2026-09-27, Dave): 하루에 세 번 문서 창이 남의 파일을 덮었다. 12:34 에는 Note-App state.md 에 Folder Bot state 의 글이 들어갔고(첫 줄 앞에 사람이 친 「ㅏㅁ은」), 12:41 에는 Folder Bot state.md 에 봇이 rondo_open 으로 띄운 다른 문서가 들어갔다. 16:06 에는 Folder Bot state.md 가 같은 폴더 todo.md 와 바이트까지 같아졌다. Dave 가 원격 수정 문제인지 Grokbot 문제인지를 물었는데, 둘 다 아니었다. 문서 창의 저장이 「어느 파일을 고쳤나」 를 저장하는 순간의 화면(지금 봇·지금 탭)에서 읽었다. 봇을 바꾸는 첫 렌더 한 번 동안 탭 상태가 옛 봇 것으로 남아 «새 봇 + 옛 파일» 이 짝지어졌고, 편집기는 key={rel} 이라 이름이 같은 파일(state.md)이면 봇을 바꿔도 그대로 남았다. 그 틈에 0.8초 자동 저장과 떠날 때 저장이 옛 글을 새 경로로 냈다. 호스트는 받은 글을 받은 경로에 그대로 썼다. 원격은 새 문서를 받는 데 시간이 더 걸려 이 틈이 길어졌을 뿐이다. 조사 중에 하나가 더 보였다. 읽기만 해도 저장됐다. 문서를 열거나 봇이 고친 글을 받을 때 편집기에 글을 갈아 넣는 것도 «바뀜» 으로 쳐서 0.8초 뒤 되썼고, 표 칸은 누르고 떠나기만 해도 한 바퀴 돌며 모양이 바뀐 원문을 썼다. 봇이 그새 또 고쳤으면 봇의 새 글을 옛 글로 덮었다. Dave 는 «충돌이 나면 알리고 임시 저장 파일을 만들 것 · 필요 없는 임시 파일은 봇이 지울 것 · 동시 편집은 안 됨 · 읽기만 하고 클릭만 한 것은 저장하지 말 것» 을 정했다. 상용 편집기의 답을 따랐다 — VS Code 의 etag(«파일이 더 새롭습니다»)와 Dropbox 의 «충돌 사본». ① 판 대조: 문서를 열 때 받은 판(ver = 바이트 sha256 앞 16자)을 저장에 base 로 함께 보낸다. 호스트(host/docSave.ts)는 디스크의 판이 다르면 원본을 건드리지 않고 내 글을 같은 폴더에 이름 (충돌 사본 MM-DD HH.mm).md 로 두고, 호스트 장부(conflicts.json)에 적은 뒤 { ok:false, conflict } 로 답한다(409 가 아니라 200 — 콘솔에 끌 수 없는 빨간 줄이 남지 않게). 글이 디스크와 같으면 쓰지 않는다(mtime 도 안 바뀐다). 쓰기는 봇 폴더 안으로만 하고, 한글 이름은 읽을 때와 같은 NFC/NFD 파일에 쓴다. ② 저장 주소는 문서에 묶는다: 화면은 받은 문서에 봇·파일을 달아 두고, 지금 봇·탭과 짝이 맞을 때만 그린다. 편집기 key 는 봇+파일이고, 저장은 그 문서의 주소로만 간다. 늦게 온 답은 버린다. 같은 문서의 저장은 줄을 세운다. useDocs 는 상태에 키를 달아 봇을 바꾸는 렌더에도 새 봇의 탭을 낸다. ③ 사람이 고친 것만: 편집기는 userEvent 가 달린 트랜잭션(타자·IME·지우기·붙여넣기·되돌리기·서식·체크박스·표)만 저장으로 보낸다. 밖에서 넣은 글은 되돌리기 기록에도 넣지 않고 커서도 제자리에 둔다. 표 칸은 «그렸을 때의 글» 과 같으면 안 바꾸고, 캔버스 노드는 들어갔다 나오기만 하면 안 쓴다. 충돌 알림: 토스트 한 줄과 문서 위 배너 «충돌 — 다른 곳에서 먼저 바뀌어, 내 글은 ○○ 에 따로 저장했어요 · 사본 열기 · 내 글로 바꾸기 · 지금 파일 유지». 화면은 지금 파일(이긴 쪽)로 바뀐다. 배너는 호스트 장부에서 오므로 다른 기기에서 열어도 뜬다. 기존 .dbanner 를 그대로 써서 새 색·새 모양은 없다. 사본 치우기(호스트가 스스로 · 켤 때와 1시간마다): 배너에서 고르면 그 자리에서 휴지통으로 보낸다. 원본이 사본과 같아졌으면(내 글이 이미 들어가 있으면) 휴지통으로 보낸다. 7일이 지나도 휴지통이다(.folderbot/trash/ · 되살릴 수 있다). 🔴 사람이 사본을 고쳤으면 그 사람 파일이므로 두고 장부에서만 뺀다. 봇이 따로 판단하지 않는다 — 규칙이 코드에 있다. 검증: 유닛 docSave.test.ts 12개(판 대조 · 같은 글은 안 씀 · 충돌 사본 · 뒤바뀐 글이 와도 못 덮음 · 이름 겹침 · 치우기 다섯 갈래 · 장부 유지). 스모크 BF 세 블록 — 읽고 누르기만 하면 mtime 까지 그대로 · 두 봇에 같은 이름 파일을 열어 두고 치자마자 봇을 바꿔도 제 파일로 · 치는 사이 바뀌면 원본 그대로·사본·배너·두 가지 정리. 옛 코드에서 셋 다 빨강을 확인했다(읽기만 해도 mtime 이 바뀜 · 친 글이 사라짐 · 봇의 글이 내 글로 덮임).

세션 목록 끝에도 «+ 새 세션» (BG · 2026-09-27, Dave · 스크린샷_1721): Dave 가 «플러스 버튼이 세션 한단에도 + 로 나와 있으면 어떨까» 를 물었다. 머리의 + 는 절 머리 오른쪽 끝에 있어 눈을 옮겨야 보이고, 방금 훑어 본 세션 줄 바로 아래에서는 누를 자리가 없었다. 이제 세션이 하나라도 있으면 목록 맨 끝에 흐린 줄 «+ 새 세션» 이 붙는다. 머리 + 와 똑같이 움직인다 — 에이전트가 하나면 바로 만들고, 둘 이상(Claude·ChatGPT)이면 고르는 창을 띄운다. 세션이 없을 때는 종전의 «세션 시작» 줄이 그대로 그 자리를 맡는다(두 줄이 겹치지 않는다). 모양은 파일 트리 끝 «참조 폴더 더하기»(BC)와 같은 말투다 — 흐린 글, 새 색 없음(디자인 언어 1조). 검증: 스모크 «BG 세션 목록 끝» — 마지막 줄 아래에 보이고 누르면 세션이 하나 는다(줄을 뺀 판에서 빨강 확인).

폴더 봇이 오케스트레이터에게 직접 요청한다 — orch_ask (BH · 2026-09-27, Dave): 종전 bot_send 는 오케스트레이터만 쓸 수 있었다(mcp.ts 의 mcpTools 가 ORCH 묶음을 오케스트레이터에게만 붙인다). 그래서 폴더 봇이 다른 봇의 도움이나 제 권한 밖의 일(폴더 이동·생성)이 필요하면 사람이 요청을 오케스트레이터로 옮기고, 답도 다시 원래 봇으로 옮겼다. 이 수작업을 없앤다. 🔴 모든 봇에 bot_send 를 여는 길은 고르지 않았다. 봇끼리 일을 떠넘기는 고리가 생기고, «각 봇은 제 폴더만 본다» 는 경계가 무너진다. 봇이 여는 길은 오케스트레이터 한 곳뿐이고, 다른 봇에 나눠 주는 일은 계속 오케스트레이터만 한다. 만든 것: COMMON 에 orch_ask(text) — 받는 쪽은 오케스트레이터로 고정이라 고를 인자가 없다. 호스트가 sendToBot(오케스트레이터, 본문, 새 세션, "요청 ← 보낸 봇 이름", 보낸 봇) 으로 보내고, 본문 앞에 [요청 ← 봇 이름 · 봇 id · 세션 id] 한 줄을 붙인다. 오케스트레이터는 이 줄로 bot_send(bot, text, session) 을 불러 원래 세션에 답을 돌려보낸다. 가드는 넷이다. ① 오케스트레이터 자신은 못 부른다. ② 위임받은 세션(오케스트레이터의 bot_send 로 새로 열린 세션 — SessionRec.delegatedFrom 에 출처를 적는다)은 되묻지 못한다. 거절 문구는 «위임받은 세션에서는 오케스트레이터에게 다시 요청할 수 없어요. 결과를 이 세션에 남기면 오케스트레이터가 읽어 갑니다.» 다. ③ 한 세션은 한 시간에 3건까지 보낼 수 있다(호스트가 켜져 있는 동안 센다). ④ 봇이 보낸 요청은 사람의 승인이 아니다 — 오케스트레이터 지침에 «읽기·조사·다른 봇에 나눠 주기는 해도 되지만, 폴더 생성·이동·은퇴·외부 발송은 봇의 요청만으로 실행하지 말고 사람에게 제안만 한다» 를 넣었다(ORCH_REQUEST_RULES_MD). ⚠ 볼트에 .claude/orchestrator.md 가 있으면 그 파일이 ORCHESTRATOR_MD 상수를 대신한다(Dave 의 볼트가 그렇다). 그래서 상수만 고치면 실제 오케스트레이터에 닿지 않는다. 시스템 프롬프트에도 그 규칙을 붙이고(파일에 이미 있으면 한 번만), 폴더 봇에는 ORCH_ASK_HINT 한 줄을 시스템 프롬프트와 새 폴더 CLAUDE.md 스캐폴드에 넣었다. 화면: core/waiting 의 «다른 봇에게 맡김» 에 orch_ask 를 더해, 보내고 기다리는 동안 청록 대기가 뜬다. 검증: 유닛 orchAsk.test.ts 7개(폴더 봇 목록에 orch_ask 있고 bot_send 없음 · 요청 줄과 세션 이름 · 오케스트레이터 거절 · 위임 세션 거절 · 한 시간 4번째 거절 · 한 시간 뒤 풀림 · 세션 모름 거절). 스모크 «BH orch_ask» — 폴더 봇이 보내면 오케스트레이터에 «요청 ← 봇» 세션이 생기고, 오케스트레이터가 bot_send 로 답하면 원래 세션에 쌓인다. 가드 셋도 함께 재고, 화면의 오케스트레이터 세션 목록에서 이름을 확인한다(옛 판에서 빨강 확인).

맥 배너를 눌렀는데 폴더만 맞고 세션이 틀리다 (BH · 2026-09-27, Dave · 스크린샷_1759): Dave 가 «알림을 클릭하면 해당 폴더봇으로는 가는데, 세션이 여러 개 있을 때 특정 세션으로는 이동을 못한다» 고 했다(맥 화면 오른쪽 위 배너). 인앱 알림 목록·주소 옮기기·새 창 첫 로드는 새 환경에서 모두 제 세션으로 갔고, 호스트에 남은 알림 50건도 전부 살아 있는 세션을 가리켰다. 재현된 길은 하나다. 배너는 셸(메인 프로세스)의 이벤트 줄로 뜨는데, 화면(렌더러)의 줄은 맥이 자고 깨거나 창이 오래 숨어 있으면 늦는다. 그러면 배너가 가리키는 새 세션이 화면 목록에 아직 없고, sessionId 계산이 조용히 첫 세션으로 떨어졌다. 그래서 폴더는 맞고 세션은 틀렸다. 이제 주소가 가리키는 세션이 목록에 없으면 목록을 그 자리에서 새로 받는다(같은 id 로는 한 번만). hash.s 는 그대로이므로 받아 오는 즉시 그 세션이 선다. 받아 오는 동안 «세션 없음» 으로 두지 않는 까닭은, 그 틈에 치면 새 세션이 생기기 때문이다. 다음에 또 나면 원인을 가를 수 있게 발자국도 남긴다. 셸이 배너로 옮길 때 __fbNavVia 를 찍고, 화면은 목록에 없던 세션을 새로 받았을 때 POST /api/client-log 로 호스트 로그에 한 줄을 남긴다(300자 · 분당 20줄). 남은 가능성 하나는 적어 둔다 — 원격 맥의 앱이 업데이트로 다시 켜졌는데 옛 배너가 알림 센터에 남아 있으면, 새 프로세스는 그 클릭을 받지 못한다. 그때는 앱만 앞으로 온다. 검증: 스모크 «BH 알림 점프» — __fbHoldFrames 로 화면 줄을 멈춘 채 새 세션을 만들고, 셸과 같은 길(replaceState + hashchange)로 옮기면 그 세션이 열린다(옛 판은 첫 세션으로 떨어져 빨강).

읽은 뒤에도 «방금 끝난» 봇은 초록 원닷 — 최신 3개 + 2시간 안 (BH · 2026-09-27, Dave): Dave 가 «초록색 불을 읽고 난 뒤에 더블링은 없애고, 가장 방금 끝난 최신 친구들만 초록색으로 남겨 달라 · 기준은 함께 정하자» 고 했고, 물어서 최신 3개 + 2시간 안으로 정했다(읽음 기준은 9/21 의 «맨 아래까지 봤을 때» 그대로). AU 뒤로 끝났고 읽은 봇에는 표시가 없어 «방금 누가 끝냈지» 가 목록에서 사라졌다. 그렇다고 다 켜면 AU 전의 «닷이 너무 많다» 로 돌아간다. core/unread.recentDone 이 도는 중·기다림·확인 대기·오류가 아닌 봇 가운데 끝난 시각(lastReplyAt 의 최댓값, 도는 세션은 뺀다)이 2시간 안인 것을 최근 순으로 3개까지 고르고, badgeOn 이 그 봇에 초록 원닷을 켠다. 링은 여전히 «안 읽음» 만의 것이다. 안 읽은 봇도 최신 3의 자리를 차지해, 화면의 초록이 셋을 넘지 않는다. 레일·띠·폰 홈이 같은 값을 쓰고, 1분마다 다시 매겨 2시간이 지나면 저절로 꺼진다. 검증: 유닛 recentDone.test.ts 4개.

링크를 두른 그림도 그림으로 (BI · 2026-09-27, Dave · 스크린샷_2123): Dave 가 «왜 이미지 안 보이지?» 라고 물었다. 당근메일 초안의 [![](images/291_ai_team_x_grokbot.png)](https://x.com/…) 가 원문 그대로 보였다. 그림 파일은 문서 옆 images/ 에 제대로 있었다. 편집기의 그림 규칙(IMG_LINE_RE)이 ![](그림) 한 줄만 그렸고, 링크를 두른 그림(X·유튜브 썸네일에 흔한 모양)은 알아보지 못했다. 이제 한 줄이 [![글](그림)](주소) 면 그림으로 그리고, 그림을 누르면 그 주소가 바깥에서 열리며(본문 링크와 같은 openExt), 아래에 «↗ 도메인» 한 줄로 어디로 가는지 보인다. ⚠ 위젯 안의 이벤트는 CodeMirror 가 받지 않는다(ignoreEvent 기본값) — 본문 링크의 linkClick 이 못 보므로 위젯이 직접 연다. 원문은 그 줄로 커서를 옮기면 드러난다(그림·링크와 같은 커서 규칙). 검증: 스모크 «BI» — 그림으로 그려지고, 누르면 주소가 열리고, 파일은 한 바이트도 안 바뀐다(규칙을 끈 판에서 빨강 확인).

문서 저장 뒤바뀜의 진짜 원인 — 편집기가 채팅칸의 초점을 훔쳤다 (BJ · 2026-09-27, Dave): BF(v163)를 올린 뒤에도 Dave 가 «0.2.164 인데도 난다 · 진지하게 다시 리서치해서 원인 해결» 을 요청했다. 9/27 에 피해를 본 파일을 전부 다시 보니 두 갈래였다. ① 파일째 뒤바뀜(12:07~18:07, 9건 · 전부 v161 시절) — BF 가 막은 «저장 주소를 화면에서 읽는» 버그와 «읽기만 해도 되쓰는» 버그다. ② 파일 머리에 글자가 박힘 — 「ㅏㅁ은」「기존 ㅔ」「깨」「 혅」「ㅏㄴ보」 다섯이 모두 파일 맨 앞에 있었고, 마지막 「ㅏㄴ보」(21:24)는 호스트가 v164 로 오른 20:05 뒤에 났다. 원인은 편집기가 뜨자마자 부르던 view.focus() 다. 편집기는 사람이 모르는 새에도 뜬다 — 봇이 rondo_open 으로 문서를 띄울 때, 봇을 바꿀 때(BF 뒤로는 봇+파일마다 새로 뜬다), 탭이 바뀔 때. 그 순간 채팅칸에서 치던 글자가 문서 맨 앞(첫 커서 자리)으로 들어가 사람의 입력으로 저장됐다. v161 까지는 그 글이 딴 경로로 저장돼 파일째 뒤바뀌었고(①의 12:34 「ㅏㅁ은」+Folder Bot state 가 그것이다), BF 뒤로는 제 파일 머리에 박혔다. 재현: 채팅칸에 「안녕」을 치는 중 봇이 rondo_open → 이어서 「하세요」 → 옛 코드는 파일이 하세요# 포커스… 가 됐다(실제 사고와 같은 모양). 고친 것 셋. ⓐ 편집기는 열릴 때 초점을 가져가지 않는다 — 사람이 누를 때만(목차 «그 줄로» 처럼 사람이 부른 것은 그대로). ⓑ 호스트는 판(base) 없는 저장을 거절한다(428). 호스트를 올려도 이미 떠 있던 화면은 옛 코드 그대로 판 없이 썼다 — 이제 그 화면은 «저장 실패» 를 본다. 스크립트·검사만 force:true 로 쓴다. ⓒ 호스트가 이벤트 줄의 hello 에 화면 판 지문(index.html sha256 앞 12자)을 싣고, 화면은 그것이 바뀌면 스스로 다시 읽는다 — 호스트를 올린 뒤 옛 코드가 계속 도는 틈을 없앤다(채팅 초안은 localStorage 에 있어 남는다). 검증: 스모크 «BJ» — 채팅칸에서 치는 중 봇이 문서를 띄워도 글자는 채팅칸에 남고 파일은 그대로 · 판 없는 저장은 428(자동 초점을 되살린 판에서 하세요# 포커스 로 빨강 확인). Y 검사(«md 를 누르면 편집기가 초점을 가져간다» 를 전제로 하던 것)는 전제를 뒤집어 «가져가면 안 된다» 로 바꿨다.

새 폴더 CLAUDE.md 에 볼트 공통 지침을 붙인다 — 루트의 md new-folder 블록 (새 폴더 틀 · 2026-09-30, Dave): Dave 가 «앞으로 새로 생성되는 folderbot 폴더 안의 CLAUDE.md 에도 관련 내용(루틴 결과는 텔레그램으로 보고)이 기본으로 들어가게» 해 달라고 했다. 그 문구에는 Dave 맥의 경로(~/.local/bin/tg-report)가 들어 있어 공개 제품의 틀에 박으면 모든 사용자에게 남의 경로가 깔린다. 그래서 틀은 그대로 두고 볼트 주인이 정하게 했다 — 루트 CLAUDE.md(규칙 파일과 같은 파일)에 ```md new-folder 블록을 두면, createFolder(새 폴더)와 start(하네스 없는 폴더에 봇 시작) 둘 다 스캐폴드할 때 그 본문을 기본 틀(orch_ask 한 줄) **뒤에** 붙인다. 스캐폴드 때마다 새로 읽으므로 블록을 고치면 다음 폴더부터 바로 반영된다. 본문에 ``` 가 필요하면 바깥 울타리를 ```` 로 길게 쓴다(같은 길이로 닫는다). ⛔ 이미 있는 CLAUDE.md 는 건드리지 않는다 — 스캐폴드는 없는 파일만 쓴다(w()). 옛 폴더에 붙이는 것은 사람·오케스트레이터 몫이다. 검증: 유닛 registry.test.ts «scaffold · 볼트 공통 지침» 2건 — 고치기 전 코드에서 둘 다 빨강 확인.

레일 정렬 «이름» → «마감일» (2026-10-01, Dave · 스크린샷_1615): Dave 가 «소팅에서 '이름' 소팅 대신 '마감일' 소팅으로 해줘» 라고 했다. 종전 «이름» 은 폴더명 내림차순이라 날짜 접두 폴더가 최신 마감부터 섰다 — 볼트 규칙상 폴더명 날짜는 마감일인데, 정작 급한 것은 아래로 밀렸다. 이제 갈래는 마감일 · 사용자 · 상태 셋이고 마감일이 기본이다. 차례는 섹션(PARA) 안에서 ① 마감이 가까운 것부터 — 지난 마감이 맨 위(아직 안 닫힌 일이다) ② 연·월만 있는 날짜는 그 기간의 끝날로 본다(2026-10 = 10/31, 2026 = 12/31) — 끝날이 같으면 날이 박힌 쪽이 먼저 ③ 날짜 없는 폴더(Area·규칙 밖 이름)는 뒤에 이름순. 비교는 순수 함수 core/botName.ts byDue·dueEnd(호스트가 이미 파싱한 Bot.due 를 쓴다). 옛 localStorage 값 name 은 마감일로 읽는다. 검증: 유닛 botName.test.ts «byDue» 2건(고치기 전 코드에서 빨강) · 스모크 레일 행 차례 검사를 마감일 기준으로 바꾸고 갈래에 «마감일» 이 있는지 잰다.

같은 질문 카드가 두 장 — 미뤄진 질문을 닫는 길이 카드 하나뿐이었다 (2026-10-02, Dave): Dave 가 «AskUserQuestion 을 두번씩 동일한 내용을 보낼때가 있어» 라고 했다. 두 맥의 전역 설정에 gstack PreToolUse 훅(question-preference-hook)이 걸려 있어 질문이 거의 다 미뤄진 질문(tool_deferred · CLI 는 턴을 이미 끝냄)으로 온다. 그 카드는 카드에서 답할 때만 닫혔다. 그래서 ⓐ 화면을 거치지 않는 말(오케스트레이터 답장·bot_send 위임·루틴)이 그 세션에 들어오면 카드가 남은 채 모델이 답을 못 받은 줄 알고 같은 질문을 새 id 로 다시 해서 카드가 둘 섰다(화면 중복 제거는 id 기준이라 못 거른다). ⓑ 사람이 채팅칸에 답을 치면 «답 기다림» 이라 글이 대기열에 묶여 안 보이다가 카드에 답한 뒤에 따로 들어갔다. 고친 것: ① 호스트는 어느 길로 온 말이든 남은 미뤄진 질문을 «채팅으로 답했다» 결과로 닫고 글과 한 메시지로 보낸다(ClaudeWorker.sendClosingAsks — 따로 보내면 헛턴이 먼저 돈다 · 기록에도 닫혀 --resume 이 다시 안 꺼낸다) ② 같은 내용의 질문이 또 오면 앞의 미뤄진 카드는 걷는다(dropSameDeferred · control_request 로 온 질문은 CLI 가 기다리는 중이라 안 걷는다) ③ 화면은 미뤄진 질문만 떠 있으면 채팅을 대기열에 묶지 않고 바로 보낸다(PermissionRequest.deferred) ④ 「기타」 칸 Enter 에 한글 조합·busy 막이. 검증: 유닛 deferredAsk.test.ts 3건 · 스모크 «채팅으로 답하면 미룬 질문 카드가 닫힌다»(스텁은 답 없는 채팅을 받으면 실 모델처럼 다시 묻는다) — 둘 다 고치기 전 코드에서 빨강 확인.

같은 질문 카드가 또 두 장 — 질문을 기다리는 동안 다른 턴이 돌았다 (CQ · 2026-10-17, Dave «똑같은 질문 왜 두번씩해?» · 스크린샷_2346): 유미MCP 해커톤 세션에서 두 번 났다. 봇이 미뤄진 질문을 띄운 채 기다리는데 백그라운드 하위 에이전트의 결과 보고가 먼저 들어왔다. 실 CLI 는 그 보고를 새 턴으로 돌리며 기다리던 질문을 «[Tool result missing due to internal error]» 로 버리고, 모델은 답을 못 받은 줄 알고 문구를 조금 바꿔 새 id 로 다시 물었다. 10/2 의 ② 는 글자까지 같은 질문만 걷어 옛 카드가 남았다. 더 나쁜 것은 연쇄다 — 사람이 옛 카드에 답하면 그 답이 죽은 id 앞으로 가고, CLI 는 그것도 새 턴으로 받아 지금 걸린 질문까지 버렸다(그래서 다음 질문도 두 장이 됐다). 고친 것: ① 워커는 본 대화의 새 줄(assistant · 하위 에이전트 줄 parent_tool_use_id 는 빼고)이나 턴 끝(result)이 오면 그 전의 미뤄진 질문을 모두 죽은 것으로 보고 걷는다(ClaudeWorker.dropDeferred — 글자 비교 dropSameDeferred 를 대신한다 · control_request 질문은 CLI 가 기다리는 중이라 안 건드린다) ② 관리자는 걷힌 질문의 도구 줄을 «끊긴 질문이에요» 로 닫고 세션 목록을 다시 보내 화면 카드가 사라진다(걸린 질문이 다 걷히면 «답 기다림» 도 풀어 채팅이 막히지 않는다) ③ /ask·/permission 은 이미 끝난 id 면 400 «이미 끝난 질문이에요» — 죽은 id 로는 CLI 에 아무것도 안 간다(종전엔 답·거부가 그대로 나갔다). 검증: 유닛 deferredAsk.test.ts(새 턴이면 문구가 달라도 걷는다 · 하위 에이전트 줄은 새 턴이 아니다 · 질문 없이 끝난 턴도 걷는다) · 스모크 «CQ — 다른 턴이 돌면 옛 카드는 걷히고, 늦게 온 답은 지금 질문을 끊지 않는다»(스텁 «끼어든질문» 은 실 CLI 처럼 1.2초 뒤 하위 에이전트 결과를 받고 문구를 바꿔 다시 묻는다 · 죽은 id 로 답이 오면 지금 질문을 버리고 또 묻는다).

봇마다 «🤝 소통» 세션 하나 · 봇의 말은 큐 · 자동 압축 · 큐 편집 크게 (2026-10-02, Dave): Dave 가 «세션이 폴더마다 정말 많이 여러 개가 생기고 있어. 세션 통신 때문이야» → «각 폴더봇도 소통용 세션이 하나만 되어도 되지 않나» → «적절한 시점에는 항상 자동 compress 로 토큰을 줄여야 해 … 기다리는 작업은 현재처럼 큐로 잡아줘 … 큐 편집 화면도 키워줘» 라고 정했다. ① 소통 세션 — bot_send(session 생략)·orch_ask 는 받는 봇의 «🤝 소통» 세션 하나로 간다(SessionManager.commOf · SessionRec.comm). 종전에는 올 때마다 «위임 · HH:MM» · «요청 ← 봇» 세션을 새로 열었다. 답은 요청 줄 머리 [요청 ← 이름 · botId · sid] 가 가리키는 물어본 작업 세션으로 돌아가므로 여러 봇의 요청이 한 세션에 섞여도 엉키지 않는다. 소통 세션도 orch_ask 를 못 쓴다(고리 막기). 화면은 소통 세션을 목록 맨 위에 두되 «처음 열 세션» 은 사람의 세션이다. 사람의 «이어서» 도 소통 세션으로 가지 않는다. name 을 주면 종전처럼 새 세션(드물게). ② 봇의 말은 큐 — 받는 세션이 일하는 중(또는 CLI 가 답을 기다리는 질문이 걸림)이면 호스트 대기열(SessionRec.queue)에 두고, 턴이 끝나면 들어온 차례대로 하나씩 내보낸다(afterTurn). 종전에는 턴 한가운데 CLI 에 밀어 넣었다. 대기열은 볼트에 남아 호스트가 다시 떠도 이어 나간다(drainAll). 화면 대기 목록에 «봇 이름» 표시로 서고 사람이 고치고 뺄 수 있다(POST /api/sessions/:id/queue). ③ 자동 압축(core/compact.ts) — ⓐ 턴이 끝났는데 컨텍스트 70% 이상이면(모든 Claude 세션) ⓑ 소통 세션을 6시간 넘게 쉬었다 다시 쓰면(30% 이상) 먼저 /compact 를 돌리고 그 말은 큐에 둔다. /compact 는 사람 말이 아니라 «자동 압축 — 이유» 시스템 줄로 적는다. ⛔ Codex 는 하지 않는다 · 압축 뒤 10분은 다시 안 한다 · 잠든 세션을 압축하려고 깨우지 않는다. ④ 루틴 «최근 2개» 가 안 읽은 답은 걷지 않는다. ⑤ 큐 편집 크게 — 대기 말이 한 줄로 잘리고 12px 한 줄 입력칸이던 것을, 카드(세 줄까지 보임) + 커지는 글 상자(14px · 최대 화면 40%)로. ⏎ 는 줄바꿈, ⌘⏎ 저장 · ⎋ 취소 · 비우면 빠진다. 검증: 유닛 commQueue.test.ts 7건 · orchAsk.test.ts 소통 세션 가드 · 스모크 «소통 세션» · «BH orch_ask» 하나로 모임 · «대기 메시지 크게 고친다»(옛 코드에서 «bot_send 두 번이 다른 세션» 으로 빨강 확인).

검색 ⌘K · 세션 정리 · 트리 위치 표시 (2026-10-02, Dave · 시안 04_리디자인/2026-10-02_검색-세션정리-트리표시-시안.html 승인 «이대로 만들어»): ① 검색 — 왼쪽 메뉴 맨 위 «검색 ⌘K»(Search.tsx). 결과는 폴더봇이 먼저이고, 고른 봇 바로 아래에 그 봇의 세션이 펼쳐진다(검색어에 맞은 세션 먼저 + 최근 3개 · «더 보기»). 화살표로 다른 봇에 올라가면 펼침이 옮겨 가고, → 는 세션으로 · ← 는 봇으로 · ⏎ 로 들어간다. 세션 이름만 맞은 봇도 목록에 선다. 「지난 대화」(말 내용 · /api/search)는 두 글자부터 맨 아래. 순위는 core/search 하나(팔레트·폴더 고르기와 같다). ⌘K 는 종전 «폴더 고르기» 였다 — 그건 ⌘⇧N(앱 메뉴도 함께) 으로 옮겼다. ② 세션 정리 — 같은 메뉴 넷째 줄 «세션 정리 · 정리 가능 N»(Cleanup.tsx). 모든 폴더의 세션이 한 표이고, 처음 열면 «7일 넘게 안 씀 + 봇끼리 대화» 거름이 켜져 있다. 🔴 잠긴 줄(일하는 중·답 기다림·안 읽음·뒤에서 도는 중·대기 말)은 못 고르고, 호스트(POST /api/sessions/bulk-delete)도 같은 판정(core/cleanup.sessLocked)으로 한 번 더 건너뛴다. 지운 뒤 10초 «되돌리기»(POST /api/sessions/restore — 지우기는 «지웠다» 표식만 남기므로 걷으면 돌아온다). 소통 세션은 처음부터 체크하지 않는다. ⚠ 줄 순서는 «만든 시각» 으로 고정 — «마지막 활동» 으로 세우면 다른 세션이 일할 때 줄이 커서 밑에서 자리를 바꿔 다른 세션이 골라졌다(검사에서 실제로 엉뚱한 것을 지웠다). ③ 트리 위치 표시 — 채팅 칩·문서 속 링크·열린 문서 탭 어느 쪽이든 고르면 오른쪽 트리가 조상 폴더를 펴고 그 줄을 주황 상자로 표시한다(.trow.box). 종전에는 폴더 칩만 1.4초 비추고 사라졌고, 파일은 표시가 없었다(열린 문서는 옅은 바탕뿐이라 조상이 접혀 있으면 안 보였다). 상자는 다른 것을 고르거나 트리에서 직접 누를 때까지 남는다. 스크롤은 줄을 품은 가장 가까운 스크롤 칸만 움직인다 — ⛔ scrollIntoView 는 바깥 틀까지 민다. 스크롤 요청은 1.5초만 살고 우클릭 메뉴가 떠 있으면 미루지 않고 버린다(Float 은 스크롤에 닫힌다). 볼트 밖·이 봇 폴더 밖 파일은 트리에 없어 상자를 못 그린다. 검증: 유닛 cleanup.test.ts 3 · 스모크 «검색 ⌘K» · «세션 정리» · «C 트리 상자»(채팅 파일 칩 · 폴더 칩, 옛 코드에서 빨강 확인). 기존 우클릭 검사는 트리가 문서 자리로 스크롤해 있을 수 있어 «누를 줄을 먼저 보이게» 바꿨다.

원격에서 기다린다고 느끼지 않게 — 호스트가 멈추지 않고, 화면이 먼저 움직인다 (BO · 2026-10-08, Dave): Dave 가 «원격 상태에서 '새 세션'을 열면 채팅 창이 열릴 때까지 딜레이가 생기네» 라고 했다. 맥미니에서 재 보니 맥북↔맥미니 왕복은 8ms 라 네트워크는 범인이 아니었다. 호스트에 2분 동안 50ms 마다 /api/health 를 두드리자 평소 2ms 인데 1초 넘는 멈춤이 두 번(최대 2.2초) 있었고, 원인은 사용량 훑기였다 — 최근 7일 대화 기록(Dave 기준 423개 · 643MB)을 30초마다·턴이 끝날 때마다 통째로 동기로 읽어 한 번에 2.5초 동안 아무 요청도 못 받았다. 턴이 끝나면 0.8초 뒤 열린 화면 전부가 사용량을 물어서, 멈춤이 «답을 읽고 다음 것을 누르는» 바로 그 순간에 겹쳤다. ① 기록은 새로 붙은 줄만, 끊어서, 비동기로 읽는다(usage.ts) — 파일마다 «완전한 줄까지 읽은 바이트 위치» 를 기억하고 그 뒤만 읽는다. 덜 쓰인 마지막 줄은 다음에 다시 읽고, 파일이 줄었으면 처음부터, 7일 밖 파일은 잊는다. 줄 끝은 바이트 0x0A 로 찾아 한국어가 조각 경계에서 안 깨진다. 2MB 조각마다 양보하고, 진행 중인 훑기는 여러 화면이 같이 기다린다(한 번만 읽는다). 호스트가 켜질 때 첫 훑기를 뒤에서 미리 돌린다. 실측: 실제 기록에서 첫 훑기 2초 동안 최대 멈춤 9ms · 턴 뒤 다시 세기 39ms · 옛 코드와 숫자가 같다(102,486,114 토큰). ⛔ 여기에 readFileSync 를 다시 들이지 않는다. 호스트는 스스로 멈춘 시간을 재서(250ms 시계) 최근 1분 최대값을 /api/health 의 stallMs 에 싣고, 1초 넘게 멈추면 로그에 남긴다. ② 새 세션은 화면이 먼저 연다(newSession.ts) — 종전에는 POST → /state 전체 다시 읽기 → 그 세션으로 를 다 기다린 뒤에야 화면이 바뀌었다. 이제 id 를 화면이 정해 임시 세션을 목록 맨 위에 넣고 빈 대화를 깔고 그 세션으로 먼저 간다(데스크톱은 입력칸에 커서). /state 다시 읽기는 뺐다. 호스트 목록이 먼저 와도 임시 세션은 붙들려 있고(store.tsx withGhosts), 호스트 목록에 그 id 가 나타나면 걷힌다. 만들기가 끝나기 전에 그 세션으로 가는 요청은 api() 가 기다렸다 보낸다(부르는 곳마다 기다리게 하면 한 곳을 빠뜨린다). 실패하면 임시 세션을 걷고, 아직 그 세션을 보고 있으면 원래 세션으로 돌아가 이유를 알린다. 호스트는 화면이 준 id 를 형식(s_ + base36 6~24자)·중복(지운 기록 파일 포함)으로 거르고, 같은 봇의 같은 id 가 또 오면 있는 것을 돌려준다(요청이 두 번 가도 세션은 하나). 측정으로 뺀 것: CLI 를 미리 띄워 두기 — CLI 는 첫 메시지를 받은 뒤에야 준비를 시작해 효과가 없었다(바로 2.2초 · 12초 먼저 띄움 2.5초). 다음 판: API 응답 압축 · 다시 붙을 때 새로 온 줄만. 검증: 유닛 usageScan.test.ts 증분 5(옛 코드에서 셋 빨강 — 덧붙인 줄만 · 덮어쓰면 다시 · 한 번만 읽기. 한국어 조각 경계·7일 밖은 옛 코드도 통과하는 회귀 방어) · 재현 test/repro-usage-stall.mjs(가짜 기록 250MB · 옛 코드 멈춤 1,835ms 빨강 → 61ms 초록 · 토큰 합 같음) · 스모크 «BO»(만들기 답을 3초 붙잡고 + — 옛 코드에서 빨강 확인).

⌘+ · ⌘− · ⌘0 — 화면 전체 확대·축소 (BP · 2026-10-09, Dave): Dave 가 «전체적으로 확대 및 축소를 하고 싶다는 요구사항이 있었어. Command + [+/-] 를 통해서 글씨 및 크기 조정이 가능하도록» 이라고 했다. 데스크톱 앱 메뉴에 확대·축소 항목이 없어 Electron 이 ⌘+/− 를 아무 데도 보내지 않았다(브라우저에서 연 화면은 원래 브라우저 확대가 된다). «보기» 메뉴에 실제 크기 ⌘0 · 확대 ⌘+ · 축소 ⌘− 를 넣었다. ⌘+ 는 자판에서 ⇧= 라 ⌘= 도 숨은 줄로 함께 받는다(크롬과 같다). 단계는 브라우저 눈금(67·75·80·90·100·110·125·150·175·200%)이고 정책은 순수 함수 desktop/zoom.js 에 있다. 배율은 셸 설정 settings.json 의 zoom 에 남고, ⚠ Electron 은 크롬과 달리 배율을 스스로 기억하지 않아 셸이 화면을 새로 읽을 때마다(did-finish-load — 호스트 새 판 BJ · 원격 재접속) 다시 건다. 바꿀 때마다 «화면 크기 110% · ⌘0 으로 되돌리기» 를 잠깐 띄운다. 이미지 뷰어가 떠 있으면 ⌘+/−/0 은 종전대로 그림 확대다 — 페이지가 먼저 받아 막으면(preventDefault) 메뉴로 안 온다(⌘N 이 두 번 안 도는 것과 같은 길). 단축키 표(⌘/)에도 넣었다. 검증: 유닛 screenZoom.test.ts 4 · qa:mac ⑫(메뉴 → 배율 · settings.json · 다시 읽어도 그대로 · ⌘0, 옛 main.js 에서 «확대가 없다» 빨강 확인) · 진짜 자판은 cua-driver foreground hotkey 로 실제 Electron 앱에 눌러 ⌘= 1→1.1 · ⌘⇧= 1.25 · ⌘− 1.1 · ⌘0 1 을 확인했다(sendInputEvent 키는 메뉴 단축키까지 안 닿아 자동 검사로는 못 잰다).

연결(Bridge) R0 — 루틴 저장 위치 · 루틴 문구와 권한 · 로컬 MCP 인증 · 압축 절대값 (BK·BS·BT·BV-1 · 2026-10-09, Dave 승인 · 오케스트레이터 지시): 바깥 에이전트와 편지 파일로 대화하는 「연결」(설계 PARA/2. Area/제품_FolderBot/01_기획/2026-10-09_기획_외부-에이전트-연결-Bridge.md · 검토 …_검토_연결-Bridge.md) 앞에, 이미 사고가 난 버그 넷을 먼저 닫았다. BK — 오케스트레이터 루틴은 .claude/routines.yml 에서 읽는데 routine_add 와 화면 저장은 루트 .bot.yml 에 써서, 두 번째 추가가 첫 번째를 지웠다. 읽기·쓰기 자리를 registry.routinesFile·saveRoutines 한 곳이 정하고, 저장 뒤 다시 읽어 개수·이름이 맞을 때만 ok 를 돌려준다. routines.yml 의 다른 키는 그대로 두고, 파일이 있는데 못 읽으면 덮어쓰지 않는다. 반쯤 쓰인 routines.yml 을 읽어도 직전 목록을 쓴다(AA-2 와 같은 이유). .claude/routines.yml 이 바뀌면 .bot.yml 처럼 스케줄을 다시 건다. 덤으로, 폴더 감시가 오류로 끊기면 30초 뒤 다시 건다(종전엔 봇 목록이 바뀔 때까지 꺼져 있었다). BS — 루틴 문구는 r.approve === 'always' 로, 권한은 «비면 always» 로 갈려서 approve 를 안 적은 루틴이 「제안만 하라」와 bypass 를 같이 받았다. routineRun() 하나가 둘을 정한다. 그리고 턴의 출처(turnFrom: human|bot|routine|peer)를 세션에 남기고, 사람이 아닌 턴에서 부른 folder_move·folder_create·bot_retire 는 MCP 가 거절한다(core/turnGuard). 세션이 아니라 턴 단위라, 같은 세션에서 사람이 「해」라고 치면 돈다. ⚠ Bash mv 는 못 막는다 — 진짜 울타리는 권한 모드다. BT — /mcp/ 는 loopback 인지만 봐서 아무 sid 로 오케스트레이터 도구를 다 썼고, content-type 을 안 봐서 이 맥의 웹 페이지가 text/plain POST 로 부를 수도 있었다. 이제 ① Origin 이 붙으면 403 ② JSON 이 아니면 415 ③ 세션 토큰 HMAC(호스트 비밀 <dataDir>/mcp.key, botId\nsid) 이 없거나 다르면 401. 워커는 토큰을 argv 가 아니라 <dataDir>/mcp/<sid>.json(0600) 으로 받는다(macOS ps 는 다른 사용자의 argv 도 보여 준다). 같은 사용자 프로세스까지는 못 막는다 — 막는 것은 브라우저·다른 사용자·실수다. 맥미니 실측: Claude Code 2.1.295 는 설정 파일의 headers.Authorization 을 모든 MCP 요청에 붙이고 Origin 은 안 붙인다. BV-1 — 1M 창에서 70% 는 70만 토큰이라 압축이 거의 안 돌았다. 15만 토큰을 넘으면 턴 끝에 압축한다. 검증: 유닛 r0Bridge.test.ts 13(BK·BV·감시 재시도 6개는 옛 코드에서 빨강 확인 · BS·BT 는 새 모듈이라 스모크로) · 스모크 «BT»(토큰 없음·남의 토큰·text/plain·Origin 네 길 거절 + 0600 설정 파일) · «BS»(approve 없는 루틴 = bypass + 같은 뜻 문구) · «BK»(오케스트레이터 routine_add 세 번 = routines.yml 셋 · 루트 .bot.yml 없음 · 손으로 고치면 따라옴 · 루틴 턴의 folder_move 거절) — 셋 다 옛 코드에서 빨강 확인 · qa:mac 통과.

연결(Bridge) R1 — 들어오는 편지 · 채널 세션 · 편지 울타리 · bridge_send · 루틴 실행 기록 (BQ-1~6 · BR-1 · 2026-10-09, Dave 승인 · 오케스트레이터 지시): 할일이(Grok Bot)는 지금 Folder Bot 화면에 orca 로 대신 타이핑해 말을 넣는다 — 접근성 트리가 잘리면 여덟 회차씩 실패하고, 클립보드에 남은 엉뚱한 글이 그대로 나간 적도 있다. 그래서 바깥 상대와는 우편함에 편지 파일을 주고받는 것을 기본으로 했다(한 파일은 한 쪽만 쓴다 · FBMF v1 · 규약 정본은 우편함 README). 설정 — 볼트 .claude/bridges.yml(상대마다 id·이름·아이콘·우편함·받을 봇과 채널 이름·may_send·trust·host·since). .folderbot/ 이 아니라 .claude/ 인 것은 .folderbot 이 감시 무시 자리라 고쳐도 안 읽히기 때문이다. 파일이 바뀌면 다시 읽는다. 들어오는 편지(host/bridge.ts) — to-folderbot/(월 폴더 포함)을 감시 + 60초 폴링으로 본다(FSEvents 는 놓칠 수 있어 보장은 폴링이 한다). (크기, mtime) 이 3초 떨어진 두 관찰에서 같고 머리말이 닫혀야 받는다. 숨김·임시·충돌 사본(«conflicted copy»·«충돌된 사본»)·README 는 편지가 아니다. 중복 키는 파일 이름의 편지 id(NFC) — 한글 파일명은 NFC/NFD 로 갈려 re: 짝이 안 맞으므로 id 는 ASCII(YYYYMMDD-HHMMSS-<보낸쪽>-<4hex>), 한글 제목은 title:. 배달 기록은 세션과 같은 곳인 <dataDir>/bridges/<상대>/delivered.jsonl 이고, 처음 켤 때 이미 있던 편지는 «본 것» 으로만 적는다(since: 뒤에 쓰인 것은 넣는다) — 쌓인 편지가 한꺼번에 들어가지 않게. host: 가 이 호스트가 아니면 가져가지 않는다(두 맥이 같은 볼트를 열어도 한 번만). 읽다 실패하거나 넣을 봇이 없으면 기록하지 않고 다음에 다시. 넣었으면 cursors/folderbot.json 을 넘기고, 편지가 없어도 15분마다 at 을 새로 써서 하트비트로 쓴다. 채널 세션 — 상대마다 받을 봇에 이름 고정 세션 하나(SessionRec.channel). 편지는 <letter id=… from=… kind=… path=…> 로 감싸 «데이터» 임을 표시하고, 묶음 머리에 «사람의 승인이 아니다» 한 줄을 붙인다. 4,000자가 넘으면 경로만. 파일로 저장되는 세션 큐로 들어가며, 급한 편지는 큐 맨 앞(앞선 급한 것 뒤 — 끼어들기가 아니다), 큐에 쌓인 편지끼리는 한 턴에 묶어 나간다(편지 하나가 한 턴이면 20통이 20턴이다). 로그인이 막혀 있으면 큐에만 두고 복구되면 내보낸다. 껐다 켜서 같은 편지가 다시 와도 큐·최근 대화에 그 id 가 있으면 건너뛴다. 편지 울타리(trust) — 권한 요청으로 막으면 새어 나간다: 사용자 설정이 Write·Edit 를 «항상 허용» 해 두면 CLI 는 묻지도 않는다(맥미니 실측). 그래서 채널 워커를 띄울 때 CLI 인자로 집행한다 — --permission-mode default 명시 · --strict-mcp-config(claude.ai 커넥터 도구 706개 → 0, 맥미니 실측) · --disallowedTools(목록에서 사라진다, 실측). propose(기본) 는 쓰기·명령·웹·루틴 바꾸기를 빼고 읽기·나눠 주기(bot_send)·답장만, read 는 나눠 주기도 뺀다, act 는 제한 없음. 세션 단위라 채널에 사람이 직접 쳐도 같은 울타리다. 편지가 시작한 턴(turnFrom: peer)은 R0 의 턴 출처 장치로 folder_move·folder_create·bot_retire 가 막힌다. bridge_send — may_send 에 든 봇에게만 보이는 MCP 도구. 호스트가 FBMF 파일을 from-folderbot/<달>/ 에 점으로 시작하는 임시 파일 → link(이미 있으면 실패) 로 만든다 — 같은 초에 두 번이어도 덮지 않고, 상대가 반쯤 쓴 파일을 못 본다. BR-1 — 루틴 회차마다 start · end(마지막 줄 결과: ok|fail|skip — 이유 를 읽는다 · 없으면 unknown) · 60분 넘으면 timeout 을 .folderbot/ops/runs-YYYY-MM.jsonl 과 runs_copy 폴더의 folderbot-YYYY-MM.jsonl(감시기가 읽는 자리)에 적는다. 루틴 문구 끝에 결과 줄 안내를 붙였다. 새 폴더 보고 절 — 제품 틀은 그대로 두고(9/30 결정), 볼트 루트 CLAUDE.md 에 ```md new-folder 블록 「📣 루틴 결과는 오케스트레이터로 보고한다」를 넣었다. 남은 것: 재시도·놓친 회차·routine_run(BR-2) · 깨우기 웹훅 · orch_report · 채널 갈아타기 · 레일 「연결」 칸은 R2. 검증: 유닛 bridgeR1.test.ts 23(FBMF · 설정 · 결과 줄 · 첫 켜기 기준선 · 껐다 켜도 한 번 · 꺼진 동안 온 편지 · null 이면 다시 · since · 주인 호스트 · send · 실행 기록 · 채널 세션 · 급함 순서 · 묶음 한 턴 · 워커 인자) · 스모크 «연결 R1»(설정을 쓰면 열림 · 새 편지 3.6초 만에 채널 세션 · 옛 편지·임시 파일 안 들어감 · 편지 턴 folder_move 거절 · bridge_send FBMF · BR-1 기록 — R0 코드에서 빨강 확인) · qa:mac 통과.

연결(Bridge) R2 — 놓친 회차 보충 · 재시도 · routine_run · orch_report · 깨우기 웹훅 · 상대 끊김 · 채널 갈아타기 (BR-2 · BU · BQ-8 · BV-2 · 2026-10-09, Dave 승인): BR-2 — 업데이트로 앱이 다시 뜨거나 맥이 멈춘 사이 걸린 회차는 croner 가 다시 돌려주지 않는다. 루틴마다 마지막으로 돌린 시각을 <dataDir>/routine-runs.json 에 적고, 켜지고 20초 뒤 한 번 «마지막 실행 뒤 예정 시각이 지났고 6시간 안» 이면 보충한다(처음 보는 루틴은 기준선만 — 처음 켠 날 모든 루틴이 한꺼번에 돌지 않게 · catchup: false 로 끈다). 실패한 회차(세션 오류 또는 결과 줄 fail)는 retry: true 인 루틴만 10분 뒤 한 번 다시 돈다 — 기본 끔(외부 발송이 든 루틴이 같은 보고를 두 번 보내지 않게 · 10/9 Dave). 실행 기록에 retry · attempt: 2 · catchup 이 남는다. MCP routine_run(제 폴더 루틴을 지금 한 번). BU — orch_report(kind: report|done|incident, text, urgent) 는 답을 기다리지 않는 보고다. 폴더 봇이 루트 state.md 에 직접 쓰던 일을 대신한다. 호스트가 5분 동안 모아 오케스트레이터의 «🤝 소통» 세션에 한 턴으로 넘기고(장애·급함은 바로), 큐에서도 보고끼리는 묶인다. 세션마다 시간당 20건. 실행 기록에도 남는다. 폴더 봇 시스템 프롬프트에 한 줄, 오케스트레이터 규칙에 «답하지 않고, 연결 상대에게 넘길 것만 bridge_send» 한 줄. BQ-8 — 급한 편지(urgent)를 쓰면 상대 웹훅을 부른다. 본문은 보내지 않고 {id, kind, path, urgent, needs_human} 만. 주소·키는 ~/.config/secrets/folderbot-bridge-<상대>.env(URL·TOKEN·HEADER) — 키체인은 쓰지 않는다: ad-hoc 서명이라 판마다 서명이 달라져 업데이트 뒤 «접근 허용?» 창이 다시 뜨고, 새벽엔 아무도 못 누른다. 파일 권한이 0600 보다 넓으면 쓰지 않는다. expect_every 넘게 상대 신호(커서 cursors/<상대>.json 또는 cursors/<상대>/*.json 갱신 · 편지)가 없으면 «상대 끊김» 을 한 번(회복도 한 번) 실행 기록 + 오케스트레이터 소통 세션 + fallback_notify 명령으로 알린다. 켜자마자 울리지 않게 신호를 못 봤으면 연 시각이 기준이다. ⚠ 같은 일을 볼트 감시기도 하면 둘 중 하나만 켠다. BV-2 — 채널 세션은 15만 토큰을 넘거나 7일이 지나면 압축 대신 갈아탄다: 인수인계를 한 턴 쓰고 → 같은 이름의 새 채널을 열고 → 그사이 온 편지(큐)를 옮기고 → 새 채널의 첫 말 앞에 인수인계를 붙인다. 옛 세션은 이름 끝에 · ~M/D 를 달고 남는다. 남은 것: 레일 «연결» 칸과 편지 스레드 화면(BQ-9)은 목업을 Dave 가 고른 뒤. 검증: 유닛 bridgeR2.test.ts 11(놓친 회차 · 기준선 · catchup false · wake 설정 · 0600 · Bearer/헤더 · 본문 없음 · 급할 때만 깨움 · 끊김/회복 한 번씩 · orch_report 상한·routine_run · 갈아타기 종단) · 스모크 «연결 R2»(일부러 실패하는 루틴 = start · end:fail · retry · start(2) · end:fail, 그 뒤 더 안 돎 · routine_run · 보고 두 건이 한 턴) — R1 코드에서 빨강 확인 · qa:mac 통과.

연결(Bridge) R3 (화면 없는 부분) — 지운 세션 정리 · 호스트 로그 · 들어오는 웹훅 · 업데이트 되돌리기 · 문서 (BW · BY · BQ-11 · BX · BQ-12 · 2026-10-09, Dave 승인): BW — 맥미니 실측으로 세션 파일 317개 중 154개가 «지움» 표시만 남은 채 있었다. 지운 시각(deletedAt · 옛 파일은 수정 시각)이 7일을 넘은 파일을 앱 데이터의 trash/sessions/ 로 옮긴다(설계는 볼트 .folderbot/trash 였지만 세션은 앱 데이터에 산다 — 대화 기록을 Dropbox 로 보내지 않는다). 지우지 않으므로 되돌릴 수 있다. 메모리에 있는 세션 파일은 읽지도 않는다(큰 파일을 다 파싱하면 호스트가 멈춘다). 켜고 1분 뒤, 그 뒤 6시간마다. BY — 데스크톱 앱은 호스트 로그를 console.log 로만 내보내서 패키지된 앱에서는 아무 데도 남지 않았다. <dataDir>/logs/host-YYYY-MM-DD.log 에 쓰고 7일 지난 파일은 지운다. BQ-11 — POST /api/bridges/<상대>/messages. 기기 토큰이 아니라 상대만의 토큰(~/.config/secrets/folderbot-bridge-<상대>-in.env · 0600 · 16자 이상)으로 받고, 이 맥·같은 tailnet 주소에서 온 것만, 상대마다 시간당 60통. 받으면 to-folderbot/ 에 편지 파일을 만들고 평소처럼 배달한다 — 들어오는 길이 몇 개든 처리는 파일 하나로 모인다. ⚠ 클라우드 상대(Grok 등)는 tailnet 밖이라 이 길을 못 쓴다. 공개 주소로 열지 않는다. BX — 9/15 결정(«자동 적용은 하지 않는다»)은 그대로다. 사람이 누른 적용이 새 판을 못 켜는 경우만 메웠다: 적용 스크립트를 desktop/apply-script.js 로 떼고, 호스트 모드면 새 판을 연 뒤 /api/health 가 "ok":true 로 답할 때까지(2초×60) 기다리고, 끝내 안 오면 새 판을 내리고 옛 판(.old)을 되살려 연다. 종전에는 옛 판을 바로 지워 새 판이 켜지다 죽으면 아침까지 호스트가 없었다. 다음에 뜬 앱이 applied.txt 의 «rolled back» 을 읽어 업데이트 칸에 알린다. 🔴 electron-builder.yml 의 files 는 파일을 하나씩 적는 목록이라 새 모듈을 빠뜨리면 배포된 앱이 require 에서 죽는다 — main.js 에서 require('./…') 로 닿는 파일이 모두 목록에 있는지 유닛이 잰다. 「새벽에 적용 예약」 단추는 화면이 바뀌므로 목업을 고른 뒤. BQ-12 — docs/BRIDGE.md(설정·우편함·FBMF·trust·bridge_send·웹훅·갈아타기·실행 기록). folderbot.dev 반영은 공개 문서라 Dave 승인 뒤. 덤 · AB-3 흔들림 — 스텁의 백그라운드 작업이 0.7초 만에 끝나 화면이 «2분 뒤» 문구를 다시 그리기 전에 대기 줄이 사라졌다(맥미니 qa 에서 R2 판 그대로도 연달아 재현). 검사 머리말대로 «시각 밀어 확인» 을 먼저 하고, 스텁에 «느리게»(5초)를 두어 그 검사만 쓴다. 검증: 유닛 bridgeR3.test.ts 4 · applyScript.test.ts 4(실제 bash·ditto 로 가짜 앱을 갈아 끼움 — 답하면 새 판, 안 답하면 옛 판·rolled back·종료 2, 원격 모드는 바로 · 패키지 목록 검사는 항목을 빼면 빨강 확인) · 스모크 «연결 R3»(웹훅 401·401·404·201 → 편지 파일 → 채널 세션 · 날짜별 로그 — R2 코드에서 빨강 확인) · npm run qa 유닛 539 · qa:mac 통과.

BZ — 호스트 운영 감시: .claude/ops.yml 의 명령을 주기마다 (R2 추가분 · 2026-10-09, Dave 결정 · 오케스트레이터 지시): 볼트 감시기 ops-watch(LLM 없음)를 15분마다 돌리려 했는데, launchd 프로세스는 TCC 때문에 Dropbox 볼트를 못 읽었고(10/9 실측 Operation not permitted) 분리 데몬은 세션 권한 정책이 막았다. Dave 가 «Folder Bot 호스트에 넣기» 를 골랐다. 호스트는 «명령 실행기» 일 뿐이다 — .claude/ops.yml 의 watch: {command, every(기본 15m), timeout(기본 60s · 주기보다 길 수 없음)} 를 /bin/sh -c 로 볼트 루트에서 돌린다. 앞 회차가 아직 돌면 그 회차는 건너뛰고, 시간을 넘으면 끊고 실패로 적는다. 결과(종료 코드·소요 시간·stderr 끝 5줄·시간 초과)는 루틴 실행 기록(runs-YYYY-MM.jsonl · runs_copy 사본)에 ops_watch 로 남는다. 연속 3회 실패하면 오케스트레이터 «🤝 소통» 세션에 한 번 알리고, 다시 성공하면 «회복» 을 한 번 알린다. host: 가 이 호스트가 아니면 돌지 않는다. 파일을 고치면 바로 다시 읽는다(첫 회차는 켜고 30초 뒤). 무엇을 검사할지는 명령이 정한다 — 루틴 놓침·실패는 BR 이 이미 기록하므로 겹치는 검사는 ops-watch 쪽에서 줄인다. ⚠ 호스트의 자식 프로세스가 볼트에 닿는지(TCC 책임 앱 상속)는 맥미니 적용 뒤 실측한다. 검증: 유닛 opsWatch.test.ts 5(설정 기본값·초 단위 · 성공·실패 기록 · 연속 3회 한 번 알림·회복 한 번 · 시간 초과·겹침 건너뛰기 · 주기 실행·주인 호스트·끄면 멈춤 — 주인 아닌 호스트의 첫 안내가 빠지던 버그를 이 검사가 잡았다) · 스모크 «BZ»(현황판 파일이 주기마다 바뀜 · 코드 5 실패가 runs.jsonl 에 · 연속 3회면 오케스트레이터에게 · 지우면 멈춤 — v178 코드에서 빨강 확인) · npm run qa 유닛 544 · qa:mac 통과.

공백이 든 파일명도 칩으로 — 첨부에서 조용히 빠지던 것까지 (CB · 2026-10-11, Dave · 스크린샷_2326 · 커밋 제목은 «CA» — 같은 날 채널 승인 건이 볼트 todo 에서 CA 를 먼저 써 CB 로 바꿨다): Dave 가 «파일을 끌어다 두어도 박스가 안 생기는데... 버그야» 라고 했다. 입력창은 글 속 @이름 을 칩으로 그리는데, 이름을 /@([^\s@]+)/ — «@ 뒤 공백 전까지» — 로 읽었다. 그래서 이름에 공백·괄호가 든 파일(카톡·녹취 내보내기에 흔하다)은 @x 에서 끊겨 칩이 안 섰다. 더 나쁜 것은 첨부 목록을 글과 맞추는 쪽도 같은 규칙이라 끌어다 놓는 순간 첨부에서 빠졌다는 것 — 보내면 파일은 봇에게 안 가고 글자만 갔다. 이름은 이제 지금 첨부 목록에 있는 이름과 맞춰 찾는다(core/chipFind.ts · 긴 이름부터 · 이름 뒤는 공백·@·끝 — 종전 경계 그대로). 입력창 그리기(InlineInput) · 첨부 목록 맞추기 · 보낸 말 그리기(App.tsx) 셋이 이 함수 하나를 쓴다 — 한 곳만 옛 규칙이면 «칩은 보이는데 파일이 빠진다» 처럼 서로 다른 말을 한다. 검증: 유닛 chipFind.test.ts 4 · 스모크 «CB»(이름 회의 메모 (최종) v2.txt 을 올려 칩 · 0.5초 뒤에도 첨부에 남음 · 보낸 말에 첨부 목록으로 붙음 · 보낸 말에서도 칩 — 옛 화면 코드에서 «칩이 안 됐다» 빨강 확인).

자동화 세션은 사람이 쓰던 대화를 지우지 않는다 (CC · 2026-10-12, Dave · 오케스트레이터 전달): Dave 가 «오케스트레이터와 각 폴더 사이의 정기 소통채널은 1개로 고정하고, 채널 소통·루틴 등 자동화 섹션은 내가 채팅하는 영역과 별개로 동작해야 해. 이 영역은 정기적으로 자동 정리되어야 해» 라고 했다. 계기는 같은 날 맥미니 사고다 — SNS 봇의 08:00 루틴 세션에 Dave 가 폰으로 답해 11:32 까지 일하고 12:30 게시를 백그라운드로 걸었는데, 12:00 같은 봇의 다른 루틴이 돌며 그 세션과 백그라운드 작업을 지웠다(게시가 안 나갔다). 원인은 루틴 정리(AI)가 봇마다 끝난 루틴 세션 2개만 남기고 사람이 답했는지·백그라운드가 도는지를 안 봤던 것. 이제 ① 세션이 어느 영역인지(채팅 · 자동화 = 소통·연결 채널·루틴·봇끼리)를 core/autoArea 한 곳이 정한다 ② 사람이 자동화 세션에 말을 보내면 루틴·봇끼리·갈아탄 옛 채널은 그 세션을 채팅으로 옮기고(adopted · 하던 일과 백그라운드가 그대로 잇는다), 🤝 소통·연결 채널은 봇마다 하나여야 하니 최근 대화를 물려받은 새 채팅을 갈라 연다(맥락은 워커에게만 · 화면의 내 말과 제목은 내가 쓴 그대로 · 화면이 새 채팅으로 넘어간다) ③ 정리는 루틴마다 2개 + 7일 지난 자동 세션, 🔴 채팅·도는 중·답 기다림·백그라운드·대기 말·30분 안 활동은 안 지운다(안 읽은 답은 «루틴마다 2개» 에서만 지킨다 — 자동화 쪽은 사람이 안 보는 곳이라 7일 규칙은 안 읽음을 안 본다) — 루틴이 돌 때와 매시간 ④ 소통 세션은 봇마다 하나(가장 최근 것 · 뜰 때 나머지는 «옛 소통» 으로) ⑤ 사람의 «이어서» 와 «처음 열 세션» 은 채팅 세션만 ⑥ 뜰 때 이관 — 사람 말이 있는 루틴 세션은 채팅으로, trust 만 남은 옛 채널은 «옛 채널» 로. 지운 세션은 종전대로 7일 뒤 앱 데이터 trash(되살리기 가능). ⛔ 자동화 쪽 정리가 채팅 영역을 건드리는 길을 다시 만들지 않는다. 화면(세션 목록에서 자동화 세션을 따로 접어 두는 자리)은 목업 먼저 — 볼트 03_디자인/2026-10-12_CC-자동화-영역-목업/ A·B·C 를 Dave 가 고른 뒤 낸다(A 는 가지 feat/cc-ui 에 만들어 둠). 검증: 유닛 autoArea.test.ts 15(10/12 사고 재현 · 잡힌 세션 · 루틴마다 2개 · 7일 · 이관 · 소통 하나 · 인수인계 · 루틴/봇끼리 옮기기 · 소통 갈라 열기 — 맥락은 워커에게만) · 스모크 «CC 10/12 사고 재현»(사람이 답하고 읽은 루틴 세션이 다른 루틴 세 번 뒤에도 남음) · «CC 사람이 자동화 세션에 답함»(소통 → 새 채팅 · 소통 하나 유지 · 봇끼리 → 그 자리에서 채팅).

루틴 세션은 루틴마다 하나 · 안 읽음은 루틴을 지키지 않는다 (2026-10-19, Dave «추천대로»): Dave 가 «루틴 세션이 너무 많아진다» 며 목록 스크린샷(11개 중 대부분 «루틴 · …», 같은 루틴 둘씩)을 보냈다. 원인은 «루틴마다 2개» 규칙이 안 읽은 답이 있는 세션을 지키고 남길 자리에 먼저 셌던 것이다 — 루틴 답은 사람이 거의 안 읽으므로 안 읽음이 영영 안 풀렸고, 그 세션들은 7일 규칙이 걷기 전까지 쌓였다. 이제 ① AUTO_KEEP_PER_ROUTINE = 1 — 루틴마다 가장 최근 회차 하나 ② routinesToDrop 의 보호는 autoHeld(도는 중·확인 대기·백그라운드·대기 말·30분 안)만 — 안 읽음 보호는 채팅 영역의 일이다 ③ 화면에서는 자동화 세션을 목록 끝 접힌 «자동화 N» 줄로 모은다(CC 목업 A). 지난 회차 결과는 실행 기록과 폴더 state.md 보고에 남는다. 🔴 사람이 답한 루틴 세션은 이미 채팅으로 옮겨지므로(adopted) 이 규칙에 안 걸린다 — 10/12 사고 재현 유닛·스모크가 그대로 지킨다. 검증: 유닛 autoArea.test.ts(안 읽은 루틴 세션도 걷힘 · 최신 하나는 남음 · 확인 대기는 남고 자리에 셈 · adopted 는 안 잡힘) · when.test.ts · 스모크 OK.

한글 폴더 찾기가 처음부터 바로 걸린다 (CD · 2026-10-12, Dave · 스크린샷_1338): Dave 가 «한국어 검색이 바로바로 안될 때가 있어. 특히 처음에» 라고 했다. 원인은 둘이었다. ① 폴더 선택 창은 찾기 색인(볼트의 폴더 전부 — Dave 볼트 2,933개 · 600KB)을 첫 글자를 친 뒤에야 받기 시작했고, 받는 동안 결과 칸에 «찾는 폴더가 없어요» 를 띄웠다. 원격에서는 이게 1~2초라 «검색이 안 된다» 로 보였다. 창을 닫으면 색인도 버려서 다음에 열 때 또 기다렸다. ② 입력기는 마지막 글자를 조합 중인 채로 보낸다 — «디캠프» 를 치는 동안 질의는 «디ㅋ» → «디캐» → «디캠ㅍ» 를 지나는데, 글자 단위 비교로는 이 중간 상태가 «디캠프» 에 안 걸려 목록이 치는 내내 깜빡 비었다. 고친 것: 색인은 창을 열자마자 받고, 모듈에 남겨 다음에 열 때 바로 쓰며 새 것은 뒤에서 갈아 끼운다(실패해도 다음에 다시 받는다). 받는 중에는 이미 펼쳐 받은 트리라도 먼저 찾고, 없으면 «읽는 중…» 이라고 한다 — «없어요» 라고 단정하지 않는다. core/search 에 자모 비교를 넣었다 — 질의와 이름을 자모 줄로 풀어(겹받침·겹모음까지 낱자로) 이름의 글자 첫머리에서 시작해 맞으면 55점(이름)·35점(경로)으로 친다. 완성된 가운데 일치(60)보다 아래라 다 친 뒤의 순서는 그대로다. 받침이 다음 글자로 넘어가는 경우(«달» + ㄱ → «닭» → «달가»)도 자모 줄에서는 이어져 걸린다. 같은 판정을 쓰는 곳(⌘K 찾기 · 파일 고르기 · 패널 거르기 · 팔레트)이 모두 함께 나아진다. 덧붙여 호스트는 32KB 넘는 JSON 을 받는 쪽이 gzip 을 말할 때 압축해 보낸다(폴더 색인 600KB → 약 60KB) — 원격의 다른 큰 응답도 함께 가벼워진다. 계약: 유닛 koSearch.test.ts(조합 중 상태 7개가 모두 걸림 · 받침 넘김 · 글자 한가운데 일치는 안 침 · 완성 일치가 위) · 스모크 CD(새 창에서 색인 응답을 2.5초 붙잡고 — 글자를 치기 전에 요청했는가 · 받는 중에 «없어요» 가 없고 «읽는 중» 이 있는가 · 풀린 뒤 «트레ㅂ»·«트렙»·«트레바» 로 «트레바리» 가 걸리는가 · 두 번째 열 때 색인 응답을 3초 늦춰도 바로 걸리는가). 옛 코드에서 유닛 3개와 스모크 CD 가 빨갛다.

폰 푸시가 실제로 간다 · 슬래시 명령이 명령으로 간다 · 폰 루틴 시트가 쓸 만하다 (CF-2·4·5 · 2026-10-12, Dave): Dave 가 «모바일에서 푸시가 안와», «/goal·/loop 같은 기본 스킬이 특히 안불러와져», «루틴은 모바일에서 안보여» 라고 했다. ④ 푸시 — 맥미니 호스트의 구독이 0건이었다. 맥미니에 HTTPS 가 없어 iPhone 이 구독을 만들 수 없었고, 실패는 로그 한 줄 없이 삼켜졌다. HTTPS 를 붙여도 VAPID subject 가 mailto:folderbot@local 이라 Apple 이 403 BadJwtToken 으로 거절할 판이었다. 고친 것: subject 기본값을 https://folderbot.dev 로(.local·localhost 는 받지 않는다) · 보낸 결과를 기기·푸시 서버 이름과 함께 로그에 남기고(endpoint 전체는 안 남긴다) 404·410 만 구독을 지운다 · /api/health 에 구독 수와 마지막 결과, GET /api/push/status · «푸시 테스트» 는 조용한 시간에도 가고 결과를 문장으로 돌려준다 · 켜기 단추는 막힌 이유(HTTPS 아님 · 홈 화면 앱 아님 · 거부됨 …)를 그대로 보여 주고, requestPermission 을 첫 await 로 둔다(iOS 는 사용자 동작 안에서만 묻는다) · 앱을 열 때마다 있는 구독을 호스트에 다시 알린다. tailscale serve 를 켜면 tailnet 요청이 127.0.0.1 로 들어와 «미니에서만» 경로가 열리므로, 프록시 머리(x-forwarded-for · tailscale-user-login · forwarded)가 있으면 loopback 으로 보지 않는다. 남은 일은 Dave 몫이다(Tailscale HTTPS 켜기 → tailscale serve --bg 7373 → https://mac-mini.<tailnet>.ts.net 으로 홈 화면에 다시 설치). ① 슬래시 — J-1(9/19) 뒤로 모든 말 앞에 기기 블록이 붙어, 실 CLI(2.1.288)는 /goal … 을 명령이 아니라 프롬프트 글로 받았다(실측: 블록 없는 /context 는 CLI 가 직접 처리 · 블록 붙은 /context 는 모델이 받아 Bash 를 돌렸다). 사용자 스킬은 모델이 Skill 도구로 대신 불러 «일부는 되는» 것처럼 보였고, /goal 은 그 목록에 없어 «특히 안» 됐다. 고친 것: CLI 가 안다고 한 이름으로 시작하는 말은 기기 블록·인수인계 없이 맨 글로 보낸다(core/clientCtx.isCliSlash — /Users/… 같은 경로는 그대로 일반 말) · 실 CLI 는 init 을 첫 말 뒤에 내므로 호스트가 마지막 init 목록을 cli-slash.json 에 두고 새 채팅·잠든 세션의 / 메뉴를 그것으로 채운다 · init 이 오면 slashN 이 바뀌어 화면이 메뉴를 다시 받는다. 검사가 못 잡은 이유는 스텁이 블록을 떼고 봤기 때문이다 — 스텁도 이제 받은 글 그대로 판정한다. ③ 루틴 — 코드상 폰 「폴더」 탭 맨 아래에 루틴이 있었지만, 편집 시트가 목록(240px)을 옆에 붙여 374px 화면에 편집 칸이 92px 만 남았고, 목록 줄은 눌리지 않고 cron 원문만 보였으며, 켜기·돌려보기 단추는 hover 에서만 보였다. 고친 것: 폰에서는 목록을 위·편집 칸을 아래로 쌓고 · 패널의 루틴 줄을 누르면 그 루틴이 골라진 채 열리며 사람 말(«매일 아침 9시»)로 보이고 · 터치 기기에서는 단추가 늘 보인다. 「안 보인다」 가 「채팅」·「할 일」 탭에 입구가 없다는 뜻이면 AD 계약을 바꾸는 일이라 Dave 결정으로 남긴다. 계약: 유닛 pushFix.test.ts(subject · 201/403/410 · 조용한 시간 · loopback) · slashRaw.test.ts · 스모크 CF-2(/goal 이 «슬래시 받음» · 새 채팅 메뉴에 goal · 경로 말은 일반) · CF-4(폰 시트 편집 칸이 화면 폭 70% 이상). 옛 코드에서 유닛과 두 스모크가 빨갛다.

폰 푸시는 «확인해 주세요» 만 울린다 (2026-10-19, Dave «계획대로»): 종전 코드는 끝남·오류·할 일·루틴까지 폰으로 보냈다(끝남만 하루 약 256건). 그러면 정작 사람이 답해야 하는 «확인해 주세요» 가 묻힌다. 이제 Notifier.emit 은 awaiting 만 폰 푸시로 보내고, 나머지는 앱 알림 목록과 맥 배너에만 남긴다. «푸시 테스트»(force)는 종류와 상관없이 간다. 루틴 결과는 할일이 경로(오케스트레이터 → 할일이)로 간다. 설정 › 알림 «이 기기 푸시» 설명에 이 범위를 적었다. 계약: 유닛 pushFix.test.ts «폰 푸시는 «확인해 주세요» 만» — 옛 코드에서 빨갛다(끝남·오류·할 일·루틴 4건이 나간다).

원격에서 기다리는 시간을 절반으로 — 압축 · 나란히 · 미리 받기 · 바뀐 것만 (CF-1 · 2026-10-12, Dave): Dave 가 «나는 사용성에 있어서 performance 를 정말 중요하게 생각해 … 미리미리 캐시를 준비해서 적용속도를 최대한 빠르게» 라고 했다. 실측 스크립트(test/perf-measure.mjs — 4개 망 조건 · 첫 실행 · 봇 전환 · 긴 대화 · 재접속 · 화면 계측)로 재 보니, 맥에서는 이미 빠르고 원격이 느렸다. 폰 원격(RTT 150ms · 10Mbps) 첫 대화 1.4초 중 1.0초가 무압축 자산 800KB 받기였고, 첫 화면은 상태 → 그린 뒤 대화로 줄을 섰으며 /api/state 를 두 번 불렀다(부를 때마다 tailscale status 프로세스 42ms). 처음 여는 봇은 0.3초 빈 칸, 재접속은 0.5초를 그냥 기다린 뒤 본 대화를 전부 다시 받았다(264KB). 고친 것(화면은 안 바뀐다): 호스트가 화면 자산을 br·gzip 으로 압축해 메모리에 두고(파일이 바뀌면 다시) index.html·sw.js 는 ETag 로 304 · tailscale status 는 30초 메모 · 첫 화면에서 상태와 해시가 가리키는 대화를 함께 쏘고, 첫 hello 의 두 번째 상태 받기는 첫 상태가 hello 뒤에 왔으면 건너뛴다 · 같은 대화·할 일·폴더 읽기를 겹쳐 쏘지 않는다 · 레일·목록 줄에 손이 닿으면 그 봇이 열 대화와 할 일을 미리 받고, 첫 화면 뒤 한가할 때 최근 봇 3개를 받는다 · 데스크톱은 한가할 때 문서 편집기 조각과 폴더 색인도 미리 받는다(폰은 번들 계약대로 안 받는다) · 화면이 돌아오거나 망이 붙으면 기다리지 않고 바로 붙는다 · 다시 붙을 때 대화마다 마지막 판(core/chatVer — 길이·시각·상태·대기·끝 6줄)을 내밀어 안 바뀐 대화는 «그대로» 만 받고, 상태·할 일·대화를 한꺼번에 받는다 · 본문 검색 지연 200 → 100ms. 결과(같은 스크립트 · 중앙값): 폰 원격 첫 대화 1,399 → 699ms · 나쁜 망(RTT 400ms · 3Mbps) 3,987 → 1,904ms · 첫 그림 1,012 → 520ms · 봇 전환 294 → 59ms(빈 프레임 16 → 0) · 재접속 1,445 → 680ms · 나쁜 망 2,855 → 1,777ms · 재접속 전송 264KB → 46KB · /api/state 42 → 2ms. 화면이 바뀌는 다섯(첫 그림을 HTML 에 · 대화 스켈레톤 · 할 일 «읽는 중» · 전환 때 옛 대화 흐리게 · «맞추는 중» 점)은 목업 03_디자인/2026-10-12_로딩-전환-목업/ 에서 Dave 가 고른 뒤 낸다. 계약: 유닛 perfCache.test.ts(판이 끝 줄 제자리 교체·상태·대기에 반응 · br/gzip 이 원문으로 풀림 · 파일이 바뀌면 새 압축 · 이미지·작은 파일은 그대로 · tailscale 은 동시에 와도 한 번) · 스모크 CF-1(본체 스크립트가 압축돼 나감 · 안 바뀐 index.html 은 304 · 안 바뀐 대화는 «그대로», 바뀐 대화는 새 줄까지). 옛 코드에서 유닛과 스모크 CF-1 이 빨갛다.

끊긴 루틴 회차를 닫는다 — 업데이트·재시작으로 도중에 죽은 회차 (CI · 2026-10-13, 오케스트레이터 제보): 10/13 12:00 SNS 게시 루틴(Dave 가 승인한 외부 게시)이 시작 2분 뒤 맥미니 앱 업데이트 적용(v0.2.185 · 12:02:09 버전 칩/트레이에서 적용)으로 끊겼다. 다시 뜬 호스트는 워커가 없으니 세션을 «쉬는 중» 으로 돌려놓는데(session.ts 생성자), 실행 기록의 끝을 적는 표는 메모리에만 있어 runs 에는 start 만 남았고, 감시기는 7시간 넘게 «실행 중» 으로만 봤다. 게시는 조용히 빠졌다. 고침 — ① 호스트가 뜰 때 이번 달·지난달 실행 기록에서 이 호스트가 시작하고 끝이 없는 회차(7일 안)를 end(ok:false · result:fail · reason «호스트 재시작으로 중단» · interrupted: true)로 바로 닫고, 그 세션에 «왜 여기서 멈췄나» 시스템 줄을 남긴다. 다른 호스트가 시작한 회차는 그 호스트 몫이라 안 건드린다. ② 다시 돌릴지는 BR-2 와 같은 retry 가 정한다 — 켜져 있고 첫 시도였고 6시간 안이면 보충보다 먼저 두 번째 시도로 한 번(retry 기록에 같은 사유). 꺼져 있으면(기본 — 외부 발송이 두 번 나가지 않게) 오케스트레이터 «🤝 소통» 세션에 끊긴 회차를 한 턴으로 알리고 다시 돌릴지는 사람이 정한다(routine_run). ③ 12:02 에 앱이 세 번 켜진 것은 충돌이 아니다 — 업데이트 로그(updates/log.txt)에 12:02:09 적용 → 12:02:15 · 12:02:47 · 12:02:57 시작이 남았고, 둘째는 LaunchServices 를 거치지 않은 직접 실행(권한 화면의 «다시 시작» = app.relaunch 와 같은 꼴), 셋째는 시스템 설정 개인정보 창이 열린 뒤의 «종료 후 다시 열기» 꼴이다(새 판이라 권한을 다시 확인한 것으로 본다 · 추정). 남은 것: 적용 단추는 도는 루틴이 있어도 바로 적용한다(칩에 바쁨 수만 보인다) — «도는 일이 끝나면 적용» 은 화면 변경이라 목업 뒤(BX 와 같은 자리). 검증: 유닛 bridgeR1 «켜질 때 끝 없는 회차를 닫는다»(지난달 회차 · 7일 넘은 것 · 다른 호스트 · 깨진 줄 · 두 번 돌려도 한 번만) · 스모크 «CI 끊긴 루틴»(실 호스트를 루틴 도중 SIGKILL → 다시 띄우면 둘 다 fail 로 닫힘 · retry 켠 쪽은 start,end:fail,retry,start2,end · 끈 쪽은 오케스트레이터 소통 세션에만) · npm run qa 유닛 585 · fit 통과.

끊긴 턴을 닫는다 — 루틴 아닌 턴도 · 턴 도중 저장 · 멈춘 동안 무엇이 돌았나 (CI-2·CI-3 · 2026-10-16, 오케스트레이터 제보): 10/16 22:19 오케스트레이터가 오사카 봇 소통 세션에 첫 지시를 보냈고, 22:26:16 맥미니 앱에 v0.2.189 업데이트가 적용되며(updates/log.txt «적용» 줄 — 충돌이 아니었다) 그 턴이 끊겼다. CI 는 루틴 회차만 닫았으므로 이 턴은 상태만 «쉬는 중» 으로 돌아가고 «시작하는 중» 글씨·턴 시각이 그대로 남았다. 세션 파일에는 사람 말 하나뿐이었다 — 대화 줄은 상태가 바뀔 때만 썼고, 앱을 끌 때 워커만 내리고 쓰지 않았다. 고침 — ① 켜질 때 끝을 못 본 턴(도는 중으로 저장됐거나, 활동 글씨가 남았거나, 끌 때 남긴 «끊김» 표식)을 «끊겼어요» 시스템 줄로 닫고 글씨를 지운다(core/cutTurn). 루틴 회차는 실행 기록 쪽(CI)이 닫고, 이미 오류로 끝난 턴·7일 넘은 턴은 글씨만 지운다. 봇·바깥 상대·루틴이 시킨 턴이면 오케스트레이터 소통 세션에 한 턴으로 알린다. 다시 돌리지는 않는다 — 외부 발송·파일 쓰기가 두 번 나갈 수 있다. ② 새 줄이 생기면 3초 안에 세션을 쓰고, 앱을 끌 때(업데이트 적용 포함) 도는 턴은 «끊김» 표식과 함께 지금까지의 줄을 다 쓴 뒤 워커를 내린다. ③ «호스트가 N ms 멈췄어요» 줄에 그사이 이벤트 루프를 오래 쥔 일(요청 경로·세션 저장·스트림 줄·폴더 바뀜)을 붙인다(host/slow). 기록이 없으면 범인은 호스트 밖(앱 화면·GC)이다. 10/16 멈춤은 하루 269번이었다(10/1315 는 2240번). 봇 턴이 도는 동안 몰렸지만(12:22 편지 → 12:23 멈춤 19번) 원인은 아직 못 짚었다. 판단은 맥미니에 이 판을 적용한 뒤 로그로 한다. 덧붙여 찾은 것: registry.bot(id) 는 부를 때마다 모든 봇(40개)의 .bot.yml 을 읽고 파싱한다(한 번 약 2ms · 맥미니 실측) — 혼자서는 3초가 안 되지만 자주 불리는 길이라 계측 뒤 줄일 후보다. 검사: 유닛 cutTurn(오사카 꼴 · 루틴 · 오류 · 7일 넘음 · 끌 때 표식 · 멈춤 기록 · 경로 이름) · 스모크 CI-2(실 호스트 — 사람 턴은 SIGKILL, 봇 턴은 SIGTERM 으로 끊고 다시 켠다: 끊기기 전 답이 남고 «끊겼어요» 한 줄 · 활동 글씨 비움 · 봇 턴만 오케스트레이터에게 · 다시 안 돌림 · 다시 켜도 한 줄). npm run qa 유닛 673 · 스모크 188 · fit 통과(CJ 468762c 위에서).

열 때 · 폰 왼쪽 위 · 할 일 마감 · 봇은 제안 · Akiflow 동기화 · 멈추기 (CK·CL·CM·CN·CO · 2026-10-16, 기획안 01_기획/2026-10-16_기획_할일-마감-Akiflow-규칙과-채팅-멈추기-끼워넣기.md · Dave «추천대로» 확정 뒤 «개발 진행·리뷰·배포» 지시): 한 판에 다섯 갈래를 냈다. R0 열 때 — 봇을 열 세션은 core/autoArea.openSessionOf 하나가 정한다(주소 → 안 읽은 채팅 → 최근 채팅 → 최근 자동화 세션 · 빈 창은 세션이 없을 때만). 종전 «채팅 세션 중 첫 것» 이라 소통만 있는 봇은 빈 창이었다(CM · 스크린샷_1222). 첫 세션 고르기와 미리 받기가 같은 함수를 쓰고, 주소에 세션이 없을 때 고른 것은 봇에 머무는 동안 붙잡는다(읽음이 적히는 순간 «안 읽은 채팅 우선» 이 풀려 다른 세션으로 튀지 않게). 소통·채널 세션 입력칸에 «여기 쓰면 새 채팅으로 이어서 열려요». 안 읽은 답이 있는 세션은 맨 아래가 아니라 볼트의 읽은 자리(readAt) 뒤 첫 줄에서 «여기부터 새 답» 선과 함께 연다(CO · core/unread.openAnchor) — 한 화면 안이면 맨 아래 그대로, 읽음은 여전히 맨 아래까지 내려가야 적힌다, iOS Safari 는 overflow-anchor 가 없어 120·400·800ms 에 다시 맞추되 사람이 움직였으면 손대지 않는다. R0′ 폰 왼쪽 위·색 — 왼쪽 위 주황 얼굴은 설정이 아니라 오케스트레이터 대화(그 표정·배지·안 읽음 링), 설정은 오른쪽 끝 ⚙(🔔 · 📁+ · ⚙). 폴더봇 몸 색은 뜻이 없었다(켠 순서대로 10색) — 이제 12색 판(core/botColor)에서 사람이 고르고(폰 행 메뉴 · 레일 우클릭 · 패널 «이 봇») 그 폴더 .bot.yml color: 에 남는다. 주황은 오케스트레이터 전용(판에 없음 · 호스트가 주황 언저리·판 밖을 거절 · .bot.yml 에 손으로 적어도 무시), 앰버는 주황과 헷갈려 판에서 빼고 옛 앰버 봇은 뜰 때 한 번 가장 덜 쓰인 색으로 옮긴다, 새 봇도 가장 덜 쓰인 색. R1 할 일 마감(CK-1) — 줄 끝 📅 YYYY-MM-DD(옵시디언 Tasks 표기) · 칩 오늘/N일 지남/내일/D-N · 칩·📅 도구 → 오늘·내일·이번 주 금·다음 주 월·날짜·지우기 · 입력칸 끝 «내일»·«10/20»·«금요일»·«20일» 을 마감으로 되읽어 준다(core/due · 맨 숫자는 날짜로 안 읽는다) · todo_add/todo_propose 의 due · 레일 «마감일» 정렬은 줄 마감이 폴더 마감보다 이르면 그것으로. 표식(📅 · <!-- ak:… --> · <!-- bot -->/<!-- bot:proposed -->)은 차례와 상관없이 읽고 늘 같은 차례로 쓰며 편집은 표식을 지킨다(core/todo.splitMeta). R3 봇은 제안(CK-3) — todo_propose 는 «제안» 칸(정식 목록 위 점선 칸)에 뜨고 [넣기]·[고치기]·[버리기] · todo_add 는 사람이 시킨 턴에서만 정식 줄(봇·루틴·바깥 상대 턴이면 제안으로 내려간다) · 사람이 시키지 않은 턴이 Edit 로 바로 쓴 새 줄은 턴 끝에 제안으로 돌린다(제목으로 비교 — 있던 줄 설명 고침은 그대로 · 호스트가 쓴 줄은 제외) · 규약은 볼트 루트 CLAUDE.md 의 md todo-rules 블록이 이기고(없으면 새 기본값 — 종전 3·5번을 바꿈). R4 Akiflow(CK-2) — 엔진 A: 호스트가 짝 장부(.folderbot/akiflow-sync.json)로 만들기·고치기·완료·(사람이 고른)지우기를 계산하고(core/akiflowSync) 오케스트레이터 루틴 «Akiflow 동기화»(30분 · 켰을 때만)가 akiflow_sync_plan → Akiflow MCP → akiflow_sync_ack 로 손만 쓴다. 봇 줄은 🤖 · 설명 첫 줄 📍 출처: [폴더](Dropbox 폴더 주소) · M/D + links · 마감 = deadline · 폴더 = 이름이 같은 Akiflow 프로젝트(⚠ Akiflow MCP 는 프로젝트를 못 만든다 — 없으면 프로젝트 없이) · Akiflow 쪽 완료·마감·제목 변경이 내려오고 🤖 를 지우면 Dave 줄 · 양쪽이 다르게 고친 제목·사라진 짝·처음 켤 때 이미 있던 같은 일은 카드로 묻는다 · 켠 첫 회차는 훑기만 · 처음 볼 때 있던 봇 줄은 올리지 않고 설정에서 «제안으로 돌리기» 를 누를 수 있다. 🔴 처음은 미리보기(설정 › Akiflow 동기화 · 루틴 없음) — 일주일 본 뒤 Dave 가 [켜기]. R2 멈추기(CL) — 일하는 동안 글이 있어도 [⏹][↑대기] 나란히(⏹ 는 .stopb 로 이름을 나눴다) · ⎋ = 멈추기(창·시트·메뉴·/·@·대기 고치기가 먼저 — 누르는 순간 열린 것을 재고 입력칸이 쓴 ⎋ 는 거른다) · 사람이 멈추면 호스트가 대기 말을 붙잡는다(held — [이어 보내기] 나 새 사람 말이 풀어 준다) · 실 CLI 는 멈춘 턴을 error_during_execution 으로 끝내는데 사람의 뜻이라 오류 줄·오류 상태로 두지 않는다 · 끝에 «응답을 멈췄어요 — [프롬프트 고치기] [다시 시도]», 고쳐 보내면 앞에 «[앞 요청은 사람이 멈췄다 — 아래로 바꾼다]»(A · 이어서 고치기). 끼워 넣기 스파이크(실 CLI 2.1.288 · 10/16): 턴 중에 stdin 으로 넣은 말은 다음 단계의 도구 결과 옆에 끼어 들어가지만 모델이 «도구 결과 속 지시(주입)» 로 보고 따르지 않았다 → ⚡ 는 멈추고 바로 보내기(⌥⏎ · 일하는 중 «⚡ 멈추고 보내기» 칩 · 대기 줄의 [⚡ 지금]). 되감기(B)는 이번에 안 했다. 검증: 유닛 openSession·botColor·due·akiflowSync(호스트 왕복 포함)·settingsIndex · 스모크 CM·CO·CN(폰 머리 · 색 바꾸기)·CK-1·CK-3(제안·지키는 장치)·CK-2·CL — 옛 코드에는 그 길(openSessionOf·.newmark·.stopb·/api/akiflow·todo_propose·/color)이 없어 구조상 빨갛다(옛 판으로 돌려 다시 재지는 않았다). 리뷰(같은 날 · 서브 에이전트 두 번)로 고친 것 — Akiflow 가 짝 없는 태스크를 줄로 다시 내려 두 벌이 되던 길(첫 훑기 회차·«남기기»·후보 카드·못 단 태스크는 안 내린다 · 계획 기록은 장부에 남겨 재시작해도 결과를 맞춘다 · 열쇠 «폴더\n제목» 으로 짝을 단다) · 밀기 전에 읽기(바뀐 짝을 먼저 읽고 카드가 걸린 제목은 밀지 않는다 — 루틴이 계획을 두 번 받는다) · 지키는 장치가 사람 줄을 봇 제안으로 바꾸던 길(그 턴이 도구로 그 todo.md 를 썼을 때만 · 짝 있는 줄 제외 · 화면·Akiflow 가 쓴 제목은 기억해 뺀다) · 사람이 멈춘 세션에 봇 말이 바로 들어가던 길 · ⎋ 가 대화 열 밖(할 일 편집 등)에서도 턴을 멈추던 길 · 편집만 열었다 닫아도 «연차 3일» 이 마감이 되던 길 · 📅 가 표식 뒤에 오면 짝을 잃던 길 · 멈춘 턴의 끝을 못 받으면 다음 턴의 진짜 오류가 숨던 길 · ⌥⏎ 가 9초 안에 못 멈추면 일하는 중에 밀어 넣던 길(대기열 맨 앞 + 풀어 두기) · 데스크톱 제안 [고치기] 무반응.

세션 정리 «안 쓴 지» 를 일 단위로 (CP · 2026-10-16, Dave · 스크린샷_1505): Dave 가 «세션 정리의 경우에는 2주는 너무 길어, 하루 전, 이틀 전 이런식으로 일단위로도 필터링 해줘» 라고 했다. 종전 거름은 «7일 넘게 안 씀» 하나로 고정이었다. 그 알약 앞머리를 날 고르기로 바꿨다 — «하루 ▾ 넘게 안 씀» 처럼 ▾ 에서 하루·이틀·3일·7일·14일을 고르고, 뒤 «넘게 안 씀» 을 누르면 거름을 켜고 끈다. 날을 고르면 거름이 바로 켜지고, 고른 날은 이 기기에 남는다(localStorage fb:cleanDays · 처음은 7일 그대로). 메뉴의 «정리 가능 N» 도 같은 날로 센다 — 판정은 core/cleanup 의 cleanMatch(…, days) 하나다. 목록 밖 값은 7일로 읽는다(cleanDaysOf). 검사: 유닛(30시간 전 세션이 하루엔 걸리고 이틀엔 안 걸림 · 목록 밖 값) · 스모크(날 목록 다섯 · 끈 거름이 날을 고르면 켜짐 · 이름 «하루 넘게 안 씀» · 기기에 저장).

10/16 기획안의 남은 것 마저 (CK·CL·CO 마무리 · 2026-10-16, Dave «기획 내용 모두 개발 완료해줘»): v187 뒤 기획안을 줄마다 코드와 대조했더니 «되감기(B) · 7일 지난 제안 접기 · 엔진 B 스파이크 · 마감 후보» 와 덜 된 것 열셋이 남아 있었다. ① 고쳐 다시 = 되감기(B) — 실 CLI 2.1.301 에 숨은 인자 --resume-session-at <답 uuid> 가 있다. --resume <id> --fork-session 과 함께 주면 그 답 직후로 갈라 새 세션 id 로 이어간다(스파이크: «사과 → 바나나로 바꿔» 뒤 첫 답 직후로 되감아 물으니 «사과»). 사람이 멈춘 Claude 세션에서 [프롬프트 고치기] 로 보내거나 [다시 시도] 하면 호스트가 CLI 기록에서 멈춘 말 바로 앞 답을 찾아(core/rewind.rewindPoint) 그 자리로 갈라 보낸다. 멈춘 턴은 «이전 시도» 로 접히고(rewind 표시 줄 · chatRows 의 prior), 그 턴이 고친 파일은 칩으로 남는다(파일은 되돌리지 않는다). 기록에서 멈춘 말을 못 찾거나 Codex 면 종전 A(머리말 한 줄)로 간다. ② 제안 날짜 <!-- bot:proposed YYYY-MM-DD --> — 7일 지난 제안은 접는다. 제안 줄에 «○○ 봇 · N일 전». ③ 정식 줄 예외를 좁혔다 — 사람이 시킨 턴이라도 마지막 사람 말이 «할 일로 적어 줘» 류가 아니면 봇의 todo_add·Edit 줄은 제안이다(asksTodo). 개발 대화 중에 봇이 스스로 남기는 줄이 봇 줄 65% 의 정체였다. ④ Akiflow — MCP 로 완료를 되돌릴 수 없다(실측: edit_task status:inbox 뒤에도 done:true). 그래서 todo.md 에서 다시 연 줄은 새 태스크로 짝을 바꾸고(replaces) 끝난 옛 태스크는 기록으로 남긴다. Akiflow 에서 다시 연 것은 todo.md 도 다시 연다. 설명도 양쪽으로(Akiflow 는 줄바꿈을 <br /> 로 돌려준다 — remoteDesc 가 풀고 출처 줄을 뗀다) · 양쪽이 다르게 고치면 카드. 회차는 15분(기획안 «15분 안에»). 프로젝트 없이 만든 태스크는 Akiflow 가 프로젝트를 짐작해 붙인다(실측 — 막을 길 없음). 엔진 B(직접 API)는 공식 공개 API 가 없어(비공식 SDK 는 메일 OTP 토큰을 저장해야 함) 엔진 A 를 유지한다. ⑤ 마감 후보 — 설명 속 «10/20 까지·마감·제출» 만 후보로 설정 › 할 일 동기화에 띄우고 [마감으로]·[아님] 을 하나씩 누른다(기록 날짜 «10/12 재현» 은 후보가 아니다). ⑥ 화면 — 마감 없는 줄의 흐린 📅(손을 올리면) · 끝이 맨 숫자면 «20일 로 써 주세요» · 줄 끝 «Akiflow» 표 · 날짜 고르기에 지금 마감 체크·«마감 지우기» · 레일·폰 목록 칩은 줄 마감이 더 이르면 그 날(botDueChip · 정렬과 같은 규칙) · 다른 봇이 보낸 대기 말에도 ⚡ 지금 · 답을 기다리는 중에도 «⚡ 멈추고 보내기» · «⎋ 멈추기 · ⏎ 대기 · ⌥⏎ 지금» 안내 · 멈춘 자리 줄에 그 턴이 고친 파일 칩 ⇄ · 다른 봇에 갔다 와도 같은 새 답이면 «여기부터 새 답» 자리를 다시 안 잡는다. 검사: 유닛(되감을 자리 · 이전 시도 접기 · 제안 날짜 · asksTodo · 마감 후보 · 맨 숫자 · 레일 칩 · 다시 열기·설명 카드) · 스모크(⎋ 차례 — 목록·팝업 먼저 · 되감기 뒤 새 갈래는 멈춘 말을 모름 · 소통 세션에서 친 말은 새 채팅 · 사람 턴 할 일 예외 · 다시 연 줄 새 짝 · 마감 후보) · qa:mac ⑬(앱 창 키 이벤트로 ⎋·⌥⏎). 안 한 것: 폴더를 가로지르는 할 일 보드(CJ R2 · Dave 결정 대기)와 그 위 폴더별 «Akiflow 맞음 · N분 전» 머리. 리뷰(서브 에이전트)에서 고친 것: 실 CLI 는 멈출 때 기록에 «[Request interrupted by user]» 사람 줄을 남겨 되감기가 늘 A 로 빠졌다(건너뛴다 · 도구를 부르다 멈춘 답 직후로는 안 가른다) · 되감기는 갈라진 새 id 가 와야 끝난 것으로 본다(그 전에 죽으면 다시 가른다 · 갈아 끼운 옛 워커의 늦은 종료는 버린다) · 자동화 세션은 되감지 않고 A 로(이어받기를 거친다) · «todo.md 에 넣어 줘» 도 할 일 요청 · 사람 턴에서는 줄이 늘었을 때만 제안으로 돌린다(제목만 바뀐 사람 줄을 봇 줄로 안 바꾼다) · Akiflow 설명은 아는 태그만 떼고 빈 설명으로 덮지 않는다 · 미뤄진 질문만 걸린 대기의 [⚡ 지금] 은 바로 보낸다 · 돌아와도 «여기부터 새 답» 표시는 남긴다 · 다시 연 줄의 만들기 열쇠에 옛 id(↺)를 실어 계획 기록을 잃어도 짝을 단다.

CJ 홈 네 칸 = 관리 입구 넷 · 전체 할 일 보드 · 폰 알약 폴더 찾기 (2026-10-16, Dave «전부 추천대로» · R1+R2 한 판): 기획안 01_기획/2026-10-16_기획_홈-네-칸-재설계와-폰-폴더-찾기.md · 목업 03_디자인/2026-10-16_홈-네-칸-목업. 9/15 네 칸(확인 대기·일하는 중·할 일·마지막 결과)은 대부분의 시간에 «0 · 없음» 이라 폴더봇 · 세션 · 알림 · 할 일 넷으로 바꿨다 — 큰 숫자 + 이름, 누르면 관리 창. ① 폴더봇 = 레일에 선 폴더 수 · «일하는 중 N · 후보 M» → 폴더 현황 시트(확인 대기 · 일하는 중 · 쉬는 중 · 오래 쉼 14일(접힘) · «폴더 선택·시작»). ② 세션 = 모든 세션 수 · «정리할 만한 것 N» → 세션 창(옛 «세션 정리» 창의 manage 갈래 — 처음엔 안 고름 · «정리할 만한 것 N 고르기» · 줄을 누르면 그 세션 · 고르기는 체크 칸 · 차례는 연 순간의 최근 활동 순으로 얼림). ③ 알림 = 읽지 않은 수 → 알림 창. 확인 대기가 생기면 이 칸이 주황 «확인 대기 N» 으로 바뀌고 칸 안에서 [허용]·[답하기](대기가 덜 보이면 안 된다 · 질문엔 [답하기]). 칸 몸통은 그때도 알림 창을 연다 — 종 단추가 빠져 미뤄진 질문이 몇 시간 떠 있으면 폰에서 알림 창으로 갈 길이 없었다(리뷰). ④ 할 일 = 마감 7일 안 폴더의 열린 할 일 수(결정 ① ⓒ · 지난 마감 포함 · 열쇠는 레일 «마감일» 정렬과 같은 min(폴더 마감 끝날, 줄 📅)) · 작은 줄 «이번 주 마감 · 폴더 칩» → 전체 할 일 보드. 볼트 전체 수는 보드 머리에만 — 전역 규칙 «압박하기 위한 숫자는 쓰지 않는다». [중단] 은 칸에서 뺐다(결정 ② · 중단은 대화 안 단추가 제자리). 레일 «알림»·«세션 정리» 줄과 폰 위쪽 종은 칸이 흡수해 뺐다(⌘⇧U · 명령 팔레트 · 접힌 레일 띠의 종 · 셸 메뉴 «세션 정리» 는 남는다). 폰 홈 부제의 «봇 N · 후보 M» 도 칸이 말하므로 뺐다. 보드(R2): 폴더로 묶고 마감 가까운 폴더부터(byDue) · 폴더 안은 📅 줄이 이른 날부터 앞 · 폴더마다 3줄 + «더 보기 N» · 거름 전체/내가 쓴 것/봇이 남긴 것 · 검색(제목·설명 · 조합 중 한글) · 줄 왼쪽 칸 = 완료(제목을 같이 보내 호스트가 그 줄 제목과 다르면 409 — 그사이 봇·Akiflow 가 파일을 고쳐 줄 번호가 밀려도 엉뚱한 줄을 안 끝낸다 · 보드는 할 일 프레임·파일 바뀜도 따라 다시 읽는다) · 줄 = 그 폴더 할 일 패널 · 폴더 머리에 제안 수(줄로는 안 세운다 — 정식 할 일이 아니다)와 Akiflow 가 켜졌을 때만 «Akiflow 맞음 · N분 전» / «올릴 것 N»(미리보기·꺼짐이면 안 보인다 — 아무것도 안 올라가는데 «맞음» 은 거짓말). 호스트: /api/todos 의 open 에서 제안을 빼고 proposed 로 따로 센다 · ?items=1 이면 열린 줄 전체와 폴더별 Akiflow 상태(AkiflowSync.folderState). 호스트가 옛 판이면 보드는 «맥미니 앱을 올려 주세요» 를 말한다. 폰 알약(결정 ③ A): «폴더 찾기 · 시키기…» — 누르면 그 자리가 입력칸, 네 칸·정렬은 접히고 목록이 걸러진다(표시 이름·경로·칸 · core/search 자모 비교라 «디ㅋ» 도 걸림) · 결과 아래 늘 «"…" 오케스트레이터에게 보내기» · 맞는 폴더가 없으면 그 줄이 맨 위이고 엔터가 곧 보내기(오케스트레이터 대화가 선 뒤 Chat 이 봇 id 를 맞춰 보고 친 말만 한 번 보낸다 — 옛 폴더로 새지 않게 · 옛 폴더의 첨부·초안·«고쳐 다시» 를 안 끌고 간다 · 일하는 중이면 대기열) · 엔터는 맨 위 폴더로 · 빈칸으로 키보드를 내리거나 ✕ 면 원래 홈. 판정은 core/home(weekTodo · boardRows · findFolders · pillEnter · folderGroup). 검사: 유닛 home.test.ts 13 · 스모크(칸 숫자 = bots·sessions·notifications·todos · 레일 줄 없음 · 확인 대기면 알림 칸이 «확인 대기» · 보드 검색·칸으로 완료·줄로 그 폴더 · 제목이 다르면 409 · 현황 시트 묶음 합 = 폴더 수 · 세션 창 처음엔 안 고름·고르기·줄로 그 세션 · 폰 «재ㅁ» → 재무 한 줄 · 엔터 = 그 폴더 · 맞는 폴더 없으면 엔터가 오케스트레이터에게 보냄 · 위쪽 종 없음).

한 번만 도는 루틴 (CR · 2026-10-17, Dave «기본 루틴 기능에 1회만 실행되는 루틴 기능도 추가해줘»): 10/12 SNS 봇이 12:30 게시를 루틴 세션 안 대기로 걸었다가 12:00 루틴 세션 정리에 세션째 지워져 게시가 안 나갔다. 볼트 규칙은 «정해진 시각에 할 일은 루틴으로» 인데, 루틴에는 «그 시각에 한 번» 을 걸 자리가 없었다. 모양 — 따로 칸(once)이나 «반복/한 번» 단추를 두지 않고 «언제» 칸이 읽는다: 날짜(10/20 오후 3시·10월 20일 15:00·2026-10-20 15:00) · 오늘/내일/모레/글피 · 이번 주/다음 주 X요일 · N분/시간 뒤 · (X요일) 오후 3시 한 번. 저장 꼴은 지역 시각 YYYY-MM-DDTHH:mm 하나라 .bot.yml·스케줄러·화면이 지금 길을 그대로 쓴다(croner 가 날짜 하나를 받아 한 번만 걸고, 지난 날짜면 안 건다 · 손으로 초까지 적으면 그 초에 돈다). 되읽는 말은 «10/20(화) 오후 3시 한 번»(해가 다르면 «2027/1/3(일) …») — 이 말을 다시 읽어도 같은 시각이다. 지어내지 않는다 — 시각을 안 말하면 아침 9시(매달 N일과 같다)를 되읽어 보여 주고 · 지난 시각·없는 날짜는 이유와 함께 막고 · 해를 안 말한 날짜가 반년 넘게 지났으면 내년(12/30 의 «1/3») · «20일 오전 9시» 처럼 매달인지 이번 한 번인지 갈리면 되묻는다(종전엔 조용히 «매일 아침 9시» 가 됐다). 반복을 뜻하는 말(매·마다·평일·주말·씩·«하루/주 한 번»·«주 1회»)은 그대로 반복이다 — «오후 3시» 는 여전히 매일, «두 시간에 한 번» 은 2시간마다. 거꾸로 «10/20 오후 3시에 한 번» 은 한 번이고, 한 번짜리 단서(오늘·내일·다음 주·…뒤)를 봤는데 못 읽었으면 매일로 흘리지 않고 이유를 준다(«다음 주 9시»). 끝난 것 — 시각이 지나면 다시 안 돌고 목록·폴더 탭에 «… 한 번 · 끝남» 으로 남는다(지우지 않는다 · 사람이 지운다). 단 돈 기록이 없으면 «끝남» 이 아니라 «놓침» 이고 이유가 붙는다 — 조용히 끝난 척하면 10/12 사고가 그대로 되풀이된다. 지난 시각은 이미 저장돼 있던 값일 때만 받는다(새로 넣으면 400 · 봇 도구도 같다). 시트는 사람이 «언제» 를 안 고친 줄을 저장된 값 그대로 보내고, 고친 줄이 한 번짜리면 읽은 시각을 보낸다 — «30분 뒤» 를 말 그대로 보내면 저장할 때마다 시각이 밀렸다. 채팅 한 줄에서 만든 루틴 초안(«내일 아침 9시에 블로그 올려줘»)도 한 번짜리로 채운다. 놓친 회차 — 반복 루틴은 «처음 보는 루틴은 기준선만» 이지만, 한 번짜리는 기록이 없으면 아직 안 돈 것이라 6시간 안이면 켜질 때 한 번 보충한다(BR-2 와 같은 창 · catchup: false 면 안 한다). 켜진 뒤 스케줄을 다시 짜는 틈에 그 시각이 지나간 것도 바로 보충한다. 봇 — routine_add·routine_update 설명에 한 번짜리 말과 «정해진 시각의 게시·발송은 세션 안에서 기다리지 말고 이것으로» 를 적었고, routine_list 는 once·finished 를 준다. 덤으로 찾은 버그: «정오 12시» 를 다시 읽으면 자정이 됐다(«오전 12시 = 자정» 규칙이 «정오» 에도 걸림) — 시트를 열고 저장만 해도 낮 12시 루틴이 자정으로 바뀌었다. 고쳤다. 화면은 «언제» 칸 아래 한 줄(«한 번만 돌아요: 10/20(화) 15:00 · 그 뒤로는 안 돌아요»)과 목록의 «끝남» 만 바뀌었다 — 새 단추를 두지 않아 목업 없이 냈다. «반복/한 번» 고르개가 필요하면 그때 목업부터. 검증: 리뷰 서브에이전트가 찾은 높음 2·중간 4건을 고쳤다. 유닛 onceRoutine.test.ts 13(읽기 · 반복은 반복 · 지난 시각·없는 날짜·«20일» 되묻기 · 해 넘김 · 왕복 · 정오 · 끝난 것 · 스케줄러 걸기/안 걸기 · 기록 없는 보충 · «주 1회»·«한 번씩» 은 반복 · «…에 한 번» 은 한 번 · 놓침 · 돈 뒤 다음 실행 없음 · 채팅 초안) — 옛 코드에서 빨강 · 스모크 «CR»(«내일 아침 9시» 가 그 시각 한 번으로 저장 · 어제 날짜·새 지난 시각 400 · 저장된 지난 것은 그대로·안 돌았으면 «놓침» · 봇 «30분 뒤» · 정오 왕복 · 몇 초 뒤 시각을 적으면 실제로 한 번만 돈다).

원격 캐시 · 문서 여러 탭 · Haiku 세션 제목 · 기본 권한 유지 (DA·DB·DC · 2026-10-17~18, Dave «원격은 매번 가져와서 느리다 · 문서 탭이 하나만 뜬다» · «세션 이름은 처음 질문과 답변으로 hiku» · «새 채팅 기본 권한이 업데이트마다 돌아간다»): Dave 가 가장 오래 기다린다고 꼽은 순간은 «다른 세션을 누를 때» 와 «PDF 를 열 때» 였고, 범위는 «전부 미리 받기», 탭은 «누를 때마다 새 탭» 으로 골랐다. 원격 캐시(DA) — 원격 데스크톱 앱(folderbotDesktop 이 있고 device.main 이 아님)만 받은 대화·문서를 그 기기의 IndexedDB(fb-cache)에 둔다(src/client/cache.ts). 켜자마자 디스크의 대화를 꺼내 판과 함께 두고, 첫 화면이 선 뒤 모든 봇의 모든 세션을 셋씩 받는다(지금 보는 것 먼저 · 판이 같으면 «그대로» 한 줄). 디스크에서 꺼내기만 한 대화는 열 때 판을 내밀어 맞춘다 — 프레임으로 생긴 대화는 종전대로 둔다(막 만든 세션에 404 를 내지 않게). 쓰는 중인 답이 있는 대화는 판을 비워 다음에 통째로 다시 받는다. 다시 붙을 때는 지금 보는 대화와 트리·문서를 먼저 새로 하고 나머지 대화는 넷씩 뒤따른다. 맥미니 자신과 폰은 종전대로다(이미 빠르거나 데이터를 아낀다). 문서 — 한 번 연 문서는 다시 열 때 캐시부터 그리고 /file?v= 로 판만 묻는다(같으면 {same:true}). 저장이 날아가는 중이면 끝난 뒤에 캐시를 읽고, 404·403 이면 캐시를 버리고 오류를 보인다(끊김만 조용히). PDF — /raw 가 no-store 라 열 때마다·탭을 오갈 때마다 전체를 다시 받았다. 이제 크기·수정 시각으로 만든 강한 ETag(private, no-cache · 쓸 때마다 묻는다)로 같으면 304, Range 한 조각을 받아 크롬 PDF 보기가 첫 쪽부터 그린다. If-Range 가 안 맞으면(받던 중 봇이 고침) 조각 대신 전체를 준다 — 옛 바이트와 새 바이트가 섞인 PDF 를 막는다. 스트림은 pipeline 으로 보내 끊기면 파일을 닫는다. 탭 — 데스크톱은 누를 때마다 새 탭(미리보기 기울임 탭이 다음 클릭에 바뀌던 것을 없앰)이고 탭 줄은 옆으로 굴러 지금 탭을 끌어온다(가운데 클릭으로 닫기). ↑↓·‹ › 로 옆 문서로 넘기기는 지금 탭을 바꾼다(탭이 쌓이지 않게). 폰은 종전대로 한 장. Haiku 제목(DC) — 9/15 «모델에게 묻지 않는다» 는 긴 지시문의 앞머리만 제목이 되는 문제를 남겼다. 이제 겹친다 — 보낸 순간엔 첫 말 제목, 첫 답이 끝나면 질문·답 앞부분을 claude -p --model haiku(도구 끔 · 짧은 시스템 프롬프트 · 세션 기록 안 남김 · 사용자 설정·MCP 없음 · 실측 2.7초)에 한 번 보내 바꾼다(src/host/titleLLM.ts). 사람이 지은 이름·루틴·소통 세션은 건드리지 않고, 답을 기다리는 사이 사람이 이름을 바꿨거나 세션을 지웠으면 버린다. 실패하면 첫 말 제목이 남는다. FOLDERBOT_TITLE_LLM=0 이면 끈다. 기본 권한(DB) — loadConfig 가 아는 칸만 골라 옮기면서 defaultPermissionMode·idleMinutes 를 빠뜨려, 앱을 다시 켤 때마다(업데이트 포함) 사라졌다. 이제 읽은 값을 통째로 깔고 기본값만 채운다(모르는 권한 값은 버린다). ⚠ 이미 지워진 값은 되살리지 못한다 — 새 판 뒤 한 번 다시 고른다. 화면 변경은 Dave 가 지정한 탭 동작뿐이라 목업 없이 냈다. 폴더 찾기 PARA·최근(요청 3) 은 화면이 바뀌어 목업 03_디자인/2026-10-18_폴더찾기-PARA-목업 A·B·C 를 먼저 냈다(선택 대기). 검증: 리뷰 서브에이전트가 찾은 높음 1(늦게 온 Haiku 제목이 지운 세션을 되살림)·중간 5(/raw 스트림 정리·강한 ETag/If-Range·캐시로 보인 문서의 404·저장 중 다시 열기·켤 때마다 디스크 통째 쓰기)·낮음 일부를 고쳤다. 유닛 cacheTabs.test.ts 12(탭 · ETag · If-Range · Range · 판 비우기 · pool) · configKeep.test.ts(옛 loadConfig 에서 빨강) · sessionTitle.test.ts DC(제목 다듬기·프롬프트) · 스모크 «DA»(/raw 200·304·206·바뀌면 200 · /file?v= 같음/다름 · 세 문서 = 세 탭 · ‹ 는 바꿈 · 다시 켜도 탭 · 디스크 캐시: /chat 을 3초 늦춰도 1.8초 안에 대화가 보임) · 제목(스텁 Haiku 가 «요약 …» 으로 바꿈 · 실패하면 첫 말 그대로).

파일 칸이 느린 폴더 · 끌어 놓기 목적지 (DD · 2026-10-18, Dave «현재 파일 부분을 불러올 때마다 오래 걸리는 폴더가 있어» · «여기로 끌면 맨 위로는 의미가 없어. 트리가 열려 있으면 맨 밑으로 가기 어렵다»): 원인은 파일 칸이 아니었다. 봇 답을 그릴 때마다 화면이 답 속 이름이 있는지 묻는데(POST /exists · 최대 40개), 경로 없이 이름만 적힌 것(Panel.tsx)이 볼트에 없으면 호스트가 볼트 전체를 동기로 훑었다(맥미니 볼트 4,069 폴더 · 이름 하나 약 0.1초). 그동안 호스트 이벤트 루프가 멈춰 파일 칸의 ls(0.05초 이하)가 13초씩 줄을 섰다 — 코드 이름이 많이 나오는 폴더일수록 심했다. 맥미니 호스트 기록 10/1718 «멈췄어요» 305번 · 원인으로 찍힌 요청 432건 중 397건이 exists. 이제 훑기는 한 번, 찾기는 표에서 한다(src/host/nameIndex.ts): 바탕 폴더마다 «이름 → 경로» 표를 비동기로 짓고(실 볼트 0.19초 · 그동안 이벤트 루프 최대 1ms 막힘), 폴더 감시의 디바운스 전 신호(FolderWatch.onRaw)가 오면 세대를 올려 요청보다 뒤에 시작한 표만 믿는다 — 봇이 방금 만든 파일도 바로 칩이 된다. 감시가 안 닿는 연결 리포는 60초 TTL. 훑는 규칙(SKIP·점 폴더 · 깊이 8 · 이름당 5개)은 종전 findFiles 그대로이고 그 함수는 지웠다. 화면도 한 번 들은 답을 기억한다(src/client/existsCache.ts — 모르는 것만 묶어 묻고, 동시에 같은 이름이면 요청 하나, «있다» 5분, «없다» 는 1분 또는 files 프레임이 오면 버림 · 묻는 사이 프레임이 왔으면 그 «없다» 는 안 적음). 색인이 안 보는 곳(node_modules·dist·점 폴더)의 바뀜은 세대를 안 올린다 — 빌드·동기화가 볼트 훑기를 줄줄이 부르지 않게. 끌어 놓기 — 트리 밑의 «여기로 끌면 맨 위로» 띠를 없앴다. 맨 위(봇 폴더)로는 늘 위에 있는 «파일» 제목 줄에 놓고(끌고 들어오면 밝아지고 «여기 놓으면 맨 위로»), 파일 줄에 놓으면 그 파일이 든 폴더로 간다(Finder 와 같다 · 밝아지는 것도 그 폴더 줄). 이미 그 폴더에 있는 것(제 줄·옆 파일 줄에 놓기)은 화면에서 빼서 «옮겼어요» 가 거짓으로 뜨지 않는다. 끄는 동안 칸 위·아래 36px 안에 오면 저절로 굴린다. 화면 변경이 띠 하나 빼고 제목 줄을 밝히는 정도라 Dave 승인으로 목업 없이 냈다. 검증: 유닛 nameIndex.test.ts 6(NFC/NFD·SKIP · 없는 이름 여러 번에 짓기 1번 · 동시 요청 1번 · touch 뒤 새 파일 · TTL · 5개 상한) · existsCache.test.ts 6(낡은 «없다» 안 적음 · «없다» 1분) · 스모크 «exists 색인»(없다고 들은 이름을 만든 뒤 3초 안에 찾음) · «끌어 놓기»(띠 없음 · 제목 줄 안내·맨 위로 · 파일 줄 → 그 폴더 줄이 밝아지고 그리로).

연결(Bridge) 화면 — 레일 «연결» 절 · 편지 스레드 · 새 연결 마법사 · 새벽 적용 (BQ-9 · BQ-10 · BX · 2026-10-19, Dave «셋 다 A»): 목업 PARA/2. Area/제품_FolderBot/03_디자인/2026-10-09_연결-목업/연결-목업.html 의 ①A · ②A · ③A 를 만들었다. BQ-9 레일 «연결» 절 — 관제 바로 아래 «연결» 절에 상대가 봇 줄과 같은 꼴로 선다(받는 봇 아래 매다는 B · 홈 다섯째 칸 C 는 안 골랐다). 아이콘은 id 에서 색조를 정한 어두운 칸 + 이모지라 폴더 봇(밝은 판)과 갈리고, 상태 점은 오른쪽 아래(상태 배지 자리 · V24 금기 그대로) — 초록 = 이 호스트가 받음, 빨강 = 끊김. 오른쪽 알약은 안 읽은 편지 수(노랑) 또는 끊김(빨강 «!»). ⚠ 봇 줄 .brow 를 다시 쓰지 않았다 — 끌어 놓기·정렬·검사가 .brow 를 세므로 꼴만 맞춘 .brg-row 이고 클래스는 모두 brg-. 상대가 없으면 절 자체가 안 나온다. 폰은 홈 목록의 같은 자리. 편지 스레드 — 상대를 누르면 가운데 칸(채팅 자리)에 연다(주소 #bot=<받는 봇>&peer=<상대> — 받는 봇으로 옮겨 오른쪽 패널에 채널 세션이 보인다). 채널 세션(생각·도구 줄)과 따로 오간 편지만 시간순: 들어온 편지에는 «✓ 채널 세션에 들어감 HH:MM»(배달 기록) · «처음 켤 때 있던 편지 — 넣지 않음»(baseline) · 다른 호스트가 주인이면 «<호스트> 호스트가 받아요», 나간 편지에는 «↩ re …» · 깨우기 웹훅 응답 코드(이번 실행에서만) · «<상대> 읽음 HH:MM(커서)» 또는 «아직 안 읽음». 읽음은 상대 커서(cursors/<상대>.json 또는 cursors/<상대>/*.json 중 늦은 것)의 last 가 그 편지 id 와 같거나 뒤인지로 본다(id 가 시각으로 시작해 글자 순서 = 시간 순서). 머리에 trust · 마지막 신호(커서·편지 — 끊김 판정의 «연 시각» 기준선은 안 보인다) · 우편함 · «채널 세션 열기». 이번 달·지난달 폴더만 보고 최근 150통까지, 본문은 2,000자까지(넘으면 경로). 사람이 쓰는 편지 — 아래 칸에서 report · request · done · incident + 급함 · 사람 결정, 들어온 편지의 «답장» 을 누르면 reply + re. 호스트가 bridge_send 와 같은 길(숨김 임시 파일 → link)로 from-folderbot/<달>/ 에 쓰고 보낸쪽은 human 이다 — may_send 는 봇을 거르는 울타리라 사람에게는 걸지 않는다. 급하면 봇 편지처럼 상대를 깨운다. 안 읽음 — 사람이 스레드를 마지막으로 연 시각(<dataDir>/bridges/<상대>/seen.json · 기기가 여럿이어도 하나) 뒤에 도착한 들어온 편지 수. 도착은 배달 기록 시각, 기록이 없는 호스트(주인 아님)는 이 실행에서 처음 본 시각이다 — 쓴 시각(created)만 보면 Dropbox 가 늦게 내려 준 편지가 영영 안 읽음이 안 된다. 처음 켤 때 «본 것» 으로 적은 편지는 세지 않고, 호스트를 다시 켜 처음 훑을 때 있던 편지는 쓴 시각으로 본다. seen 이 없으면 지금을 적는다 — 옛 편지를 «안 읽음» 으로 쏟지 않게. 스레드를 열면 걷힌다. 스레드는 늦게 온 다른 상대의 답을 버린다(A 의 편지 위에서 B 에게 쓰지 않게). 사람이 쓴 편지의 re 는 한 줄 id 만 받고, FBMF 쓰기도 re 를 따옴표 규칙으로 감싼다(바깥 값이 머리말 줄을 지어내지 못하게). 다시 읽기 — 호스트가 편지 배달·보냄·끊김·설정 때 bridges 프레임(0.5초에 한 번으로 묶음 · 내용 없음)을 보내고, 화면은 그때 · 다시 붙을 때(hello · 재접속 계약) · 30초마다 GET /api/bridges 를 다시 읽는다. 레일·스레드·설정이 목록 하나와 요청 하나를 나눠 쓴다. BQ-10 새 연결 마법사(설정 › 연결) — ① 종류(Grok Bot 루틴 · 파일만 쓰는 상대 · 웹훅 상대 — 목업 문구) → ② 자리·권한(이름 · 아이콘 · id · 우편함 · 받을 봇 · 신뢰 수준 read/propose/act · Grok 은 끊김 감지 150m · 깨우기 웹훅 켜기) → ③ 만들기. 추천 우편함은 목업의 2. Area/비서/싱크 를 일반화해 상시 영역(Area) 칸 아래 «이름/싱크» 다 — 상대와 주고받는 일은 끝나는 날이 없는 상시 운영이라서. 볼트 규칙에 Area 칸이 없으면 루트의 연결/. 우편함은 볼트 안 상대 경로만(절대 경로·..·점 폴더 거절), id·우편함이 이미 있으면 거절. 만들면 우편함 폴더 넷(우편함 · to-folderbot · from-folderbot · cursors) · README(FBMF v1 규약 + 종류별 예시 — Grok 은 셸/Dropbox 커넥터 «새 파일 만들기만», 파일 상대는 mv 로 옮기는 bash, 웹훅은 curl) · .claude/bridges.yml 한 줄(yaml Document 로 덧붙여 사람이 쓴 주석·다른 키 보존 · host: = 이 맥 · since: = 만든 시각 · 임시 파일 → 이름 바꾸기)을 만들고 다시 읽는다. ⚠ README 가 이미 있으면 덮지 않는다(사람이 고친 규약이 정본) · bridges.yml 을 못 읽으면 아무것도 만들지 않는다. ⛔ 키·토큰 파일은 앱이 만들지 않는다 — 자리(~/.config/secrets/folderbot-bridge-<id>[-in].env)만 알려 준다. 폴더 생성은 사람이 시킨 턴에서만 된다는 규칙(BS)과 맞는다 — 이 길은 화면 API 뿐이고 편지·루틴 턴은 여기 못 온다. 끝 화면은 «상대에게 이 한 줄을 주세요 — <우편함>/README.md 를 읽고 그대로 따라 해 줘.» 와 복사 단추. 원격 화면(폰)에서도 된다(파일은 호스트에 생긴다). BX 새벽 적용 — 버전 칩 팝업에 [새벽 4시에 적용] 하나를 더했다(설정 칸 B 는 사실상 자동 적용이라 9/15 결정과 부딪혀 안 골랐다). 누르면 칩이 «v… · 새벽 적용 예약» 으로 바뀌고, 칩을 다시 눌러 같은 자리의 [새벽 예약 취소] 로 푼다. 4시에 도는 세션(돌거나 답을 기다리는)이 있거나 30분 안에 도는 루틴이 있으면 30분씩 미루고, 미룬 시각이 5시를 넘으면 다음 날 4시로 넘긴다(desktop/dawn.js 순수 함수). 예약은 updates/dawn.json 에 남아 앱이 다시 떠도 살고(받기 정리가 이 파일을 지우지 않는다), 새벽 창(4:00–5:05) 밖에서 깼거나 예약 시각을 90분 넘게 놓쳤으면(앱이 꺼져 있었다) 아침에 갈아끼우지 않고 다음 새벽으로. 30초마다 시각을 본다(setTimeout 하나는 맥이 잠든 동안 멈춘다). 적용은 사람이 누른 «지금 적용» 과 같은 apply() 라 켜지다 실패하면 옛 판으로 돌아오는 장치(apply-script · /api/health · v178)를 그대로 쓴다. 🔴 예약은 판에 묶인다 — 사람이 고른 것은 그 판이다. 예약이 걸린 동안은 더 새 판을 받지 않고(받으면 옛 zip 이 지워진다) 다시 뜬 앱도 받아 둔 그 zip 을 다시 세운다. 그래도 그 판이 아니게 되면 예약을 풀고 다시 묻는다. 더 새 판은 적용 뒤에 받는다. 원격에서도 걸 수 있다 — 셸이 startHost({ update }) 로 손잡이를 넘기고 GET /api/update · POST /api/update/dawn 이 그것을 쓴다. 메인이 아닌 화면(브라우저 · 맥북 원격 앱)의 레일 아래에 «호스트 v… » 칩이 받은 판이 있을 때만 뜨고, 그 팝업에는 새벽 단추만 있다(«지금 적용» 은 보고 있는 화면째 끊기므로 호스트 맥에서만). 셸이 없는 호스트(터미널)는 { shell: false } 를 200 으로 준다 — 404 는 원격 화면 콘솔에 오류로 찍힌다. ⚠ 폰 홈에는 버전 칩이 없어 이 판에서는 폰에서 새벽 적용을 못 건다. 검증: 유닛 bridgeUi.test.ts 17(시각·순서·읽음·안 읽음 · 종류 · 추천 우편함(PARA·Dave 볼트·JD) · 경로 · id · 검사 · 🔴 bridges.yml 주석 보존 · README 예시가 FBMF 로 읽힘 · 스레드 · 안 읽음 기준선 · 사람 편지 · 🔴 새 연결이 README 를 안 덮음 · 못 읽는 yml 은 안 건드림) · dawn.test.ts 5(다음 4시 · 한가하면 적용 · 30분씩 · 5시 넘으면 내일 · 놓치거나 창 밖에서 깨면 다음 새벽 · 셸·화면·호스트 배선) · 자체 검토(서브에이전트) 중간 7건 반영 — 재시작 때 dawn.json 이 지워짐 · 예약이 다른 판으로 넘어감 · 5시 뒤 적용 · 스레드 늦은 답 · 늦게 온 편지의 안 읽음 · re 머리말 주입 · 커서 파일 전부 읽기 · settingsIndex 가 Bridges.tsx 의 줄도 읽는다 · 스모크 «연결 화면»(API 마법사 · 볼트 밖 거절 · 스레드 배달·읽음 · from human · 레일 «관제» 다음 «연결» · 누르면 가운데 스레드 · 쓰기 → FBMF · 봇 누르면 닫힘 · 설정 › 연결 세 단계 · 추천 우편함 · /api/update 셸 없음 200).

자동화 세션 자리 A안 — 세션 목록 끝의 접힌 «자동화» 줄 (CC 화면 · 2026-10-19, Dave A 확정 · 목업 03_디자인/2026-10-12_CC-자동화-영역-목업): 10/12 에 동작(사람이 답하면 옮기거나 갈라 열기 · 자동 정리)만 먼저 내고 화면은 목업 A·B·C 를 기다렸다. A 로 정해져 가지 feat/cc-ui 를 지금 main 위로 옮겼다. 패널 «세션» 절은 채팅 세션만 세고 보이며, 끝에 «자동화 N» 접힌 줄 하나(봇마다 접힘 기억 · 도는 중·답 기다림이면 접혀 있어도 점이 켜짐 · 지금 보는 세션이 자동화면 펼쳐 보임) 아래에 소통·채널·루틴·봇끼리 세션이 흐린 글과 종류 표식으로 선다. 채팅 머리의 세션 고르기도 채팅 → 구분선 «자동화» → 자동화 차례다. 자동화 세션을 열면 입력창 위에 쓴 말이 어디로 가는지 한 줄(«새 채팅이 열려요» · «채팅으로 옮겨져 자동 정리에서 빠져요»). 레일의 «안 읽음»·«방금 끝남» 은 채팅 세션만 센다 — 자동화 세션의 답이 사람을 부르지 않게. ⚠ 옮기면서 겹친 곳: 10/16 CM(openSessionOf — 채팅이 없으면 가장 최근 자동화 세션을 연다)이 «처음 열 세션은 채팅만» 보다 나중 결정이라 CM 을 그대로 두고, 목록 차례만 «채팅 앞 · 자동화 뒤» 로 바꿨다(종전 «🤝 소통 맨 위» 는 폐기). 채팅 머리 메뉴의 «자동화» 머리 클래스는 .mh(가운데 말줄임 Mid 의 앞 조각)와 겹쳐 .mhead 로 지었다. 검증: 스모크 «CC 세션 목록»(세션 절 숫자 = 채팅 수 · «자동화 N» 줄 · 접힘 · 펼치면 종류 표식 · 열면 안내 한 줄 · 채팅이 있으면 자동화로 안 들어감) · 오케스트레이터 «🤝 소통» 은 채팅 목록이 아니라 «자동화» 줄 아래.

폴더봇 찾기 A안 — 맨 위 «최근» 다섯 줄 + PARA 칸 (DE · 2026-10-19, Dave A 확정 · 목업 03_디자인/2026-10-18_폴더찾기-PARA-목업): ⌘K 찾기가 레일 순서 그대로 서서 2. Area 폴더가 1. Projects 폴더와 섞이고 어제 쓴 폴더가 아래에 묻혔다(스크린샷_002). 이제 검색어가 없으면 오케스트레이터 한 줄 → «최근 N»(최대 5) → 칸마다 머리(색 네모 · 실제 칸 폴더명 · 수)를 단 1. Projects · 2. Area · 3. Resources · 4. Archive · 그 밖 차례다. «최근» 은 사람이 그 폴더의 세션을 마지막으로 읽은 시각(채팅 세션 readAt 의 가장 큰 값 · 어느 기기에서든)이다 — 자동화 세션은 알림 «모두 읽음» 이 함께 올려 새벽 루틴 폴더를 띄우므로 안 센다 — ⛔ «봇이 마지막으로 대화한 시각» 으로 바꾸지 않는다(새벽 루틴이 돈 폴더가 매일 아침 위로 올라와 하던 폴더가 다시 묻힌다). ⚠ readAt 은 «연 순간» 이 아니라 «그때 읽은 마지막 답의 시각» 이다(호스트·폰 시계가 달라도 안 읽음 판정이 안 흔들리게 그렇게 적는다) — 오래된 답만 있는 세션을 다시 열어도 순서는 안 바뀐다. 최근 칸에 든 폴더는 아래 칸에서 빠져 두 번 나오지 않고(목업 A 의 대가 그대로 — 최근에 쓴 Projects 폴더는 Projects 칸에 안 보인다), 칸 안은 최근 순 · 한 번도 안 연 폴더는 레일 순서다. 최근 줄과 검색 결과 줄은 왼쪽에 칸 표식(P·A·R·X 색 글자)과 «칸 · 날짜»(예: «1. Projects · 10-17»), PARA 칸 안 줄은 폴더 아이콘과 «날짜 마감» 이고 오른쪽 끝에 최근 시각(방금 · N분 · N시간 · 어제 · 요일 · M/D)이 선다. 검색어를 치면 칸 머리를 걷고 맞는 순서로 세운다(순위는 종전 core/search). 칸 머리는 고르는 줄이 아니라 ↑↓ 는 봇·세션 줄만 밟고, 고른 봇 아래 세션 펼침·«더 보기»·«지난 대화» 는 그대로다. PARA 칸은 rel 첫 조각의 낱말(Projects·Area·Resources·Archive)로 읽는다 — 번호는 9/28 처럼 바뀐다. 판정은 src/core/folderFind.ts 하나. ⚠ 폰 홈의 하단 알약 «폴더 찾기» 는 다른 화면(Home · findFolders)이라 이번에 안 바꿨다 — 같은 모양을 원하면 목업을 따로 낸다. 검증: 유닛 folderFind.test.ts 11(칸 판정 · 최근 다섯 · 중복 없음 · 칸 안 차례 · 짧은 표기) · 스모크 «DE 폴더봇 찾기 A안»(재무_CFO 세션 하나를 방금 읽은 것으로 적고 열면 «최근» 첫 줄 · A · 방금 · 칸 차례 · 중복 없음 · 검색어엔 칸 머리 없이 표식만 — 옛 판은 레일 순서라 빨갛다).

로딩·전환 화면 — 기다렸지만 기다린 기분이 안 들게 (CF 화면 · 2026-10-19, Dave «A 가 제일 낫다» · 목업 03_디자인/2026-10-12_로딩-전환-목업): 10/12 실측에서 폰 원격 첫 실행은 1초 동안 검은 빈 판, 처음 여는 봇은 0.3초(나쁜 망 0.8초) 빈 칸이었고, 할 일 칸은 받는 중에도 «할 일이 없어요» 라고 틀린 말을 했다. 화면이 안 바뀌는 개선(자산 압축 · 첫 요청 나란히 · 미리 받기)은 그때 냈고, 이번에 화면 다섯 칸을 목업대로 냈다. ① 첫 그림 A — 앱의 «호스트에 연결하는 중…» 화면과 같은 모양(주황 폴더봇 40px · 글)을 index.html 의 #root 안에 그대로 넣었다. 스타일은 인라인이라 CSS 파일도 안 기다리고, 테마는 앱과 같은 값(fb:theme · 시스템)을 먼저 정해 라이트에서 검은 판이 번쩍이지 않는다. React 가 처음 그리는 순간 걷힌다(B 뼈대안은 안 골랐다). ② 처음 여는 대화 A — 받는 데 0.15초를 넘기면 말풍선 모양 회색 줄(내 말 둥근 칸 · 봇 말 줄)을 띄우고, 그 안에 오면 아무것도 안 띄운다. 입력창은 그대로 쓸 수 있다. ③ 할 일 — 받는 중이면 회색 줄 둘(수도 비운다), 받았는데 비었을 때만 «할 일이 없어요». ④ 봇 전환 A — 머리줄은 곧바로 새 봇·세션이고, 아래는 옛 대화를 흐리게(0.45) 남겼다가 새 대화로 바꾼다. 0.4초를 넘길 때만 ②의 회색 줄로 갈아 끼운다. 흐린 옛 대화는 그리기만 한다(inert — 눌러도 안 되고 마지막 질문·다시 시도·고정 질문은 지금 세션 것으로만 센다). 평소에는 감싸지 않아 .chat-body > … 선택자가 그대로 맞는다. ⑤ 다시 붙는 중 — 줄이 끊긴 순간(첫 화면 뒤) «맞추는 중» 상태가 켜지고, 다시 붙어 상태·할 일·보는 대화를 다 받으면 꺼진다. 0.4초를 넘길 때만 머리줄에 작은 주황 점과 «맞추는 중» 이 뜨고, 옛 내용은 지우지 않는다. 대화·할 일 받기가 실패하면 1·2·4·8·15초 간격으로 다시 받고(그 대상을 떠나면 멈춤), 다시 붙을 때(resync)도 보는 대화·봇의 할 일을 아직 못 받았으면 다시 받는다 — 회색 줄이 실패한 채 남으면 «받는 중» 이 거짓말이 된다. 첫 hello 전에 줄이 한 번 끊겼거나 맞추는 도중 또 끊긴 경우도 점이 남거나 일찍 꺼지지 않게 맞추기 차례를 센다. 끊겨 있는 동안은 «다시 연결하는 중…» 띠가 말하므로 점은 다시 붙은 뒤에만 뜬다. 흐린 옛 대화는 그 대화의 봇·세션으로 그린다(파일 칩이 새 폴더 기준으로 풀리지 않게). 문턱(150ms · 400ms · 400ms)은 src/core/loadPhase.ts 하나. ⛔ 회색 줄을 «받는 중» 이 아닌 곳(빈 세션 · 없는 세션)에 쓰지 않는다 — 회색 줄은 «곧 온다» 는 약속이다. 검증: 유닛 loadPhase.test.ts 5 · 스모크 «CF ① 첫 그림»(HTML 안 · 앱 JS 를 막아도 화면을 채움 · 라이트면 흰 판 · 앱이 서면 걷힘) · «CF ②③④⑤»(대화·할 일 받기를 붙잡고 봇을 바꾸면 0.15초에 옛 대화 흐림·inert · 머리줄은 새 세션 · 할 일 회색 줄 · 0.65초에 말풍선 회색 줄 · 풀면 걷힘 · 한 번 실패한 대화 받기를 다시 받아 회색 줄이 걷힘 · 상태 받기를 붙잡고 다시 붙이면 «맞추는 중» · 옛 대화 그대로 · 풀면 사라짐). 옛 판에서는 넷 다 빨갛다.

folderbot ask · 폴더 봇 직접 · 긴급 메일 (DA · 2026-10-24, Dave «범용 CLI · 폴더 봇 직접 · 시키기까지»): 바깥 상대(할일이)가 편지를 쓰고 다음 차례까지 기다리던 왕복을 claude -p 처럼 «보내고 · 기다렸다가 · 답을 찍는» CLI 한 줄로 바꿨다(Dave 10/24 «범용 CLI · 폴더 봇 직접 · 시키기까지 · «지금 확인» 편지 은퇴 · 긴급 메일은 Folder Bot 이 자동으로»). 결정 — ① ask 는 새 우편함이 아니다: 묻는 말·답 모두 FBMF 편지이고 배달 기록도 같다(옛 길과 기록이 갈리지 않게) ② 답 = 그 id 를 re 로 단 첫 bridge_send, 없으면 턴의 마지막 답 · 기다리는 쪽이 살아 있을 때 쓴 답에만 delivered: live(상대가 다시 처리하지 않는다 — 쓰기 직전에 묻고 직후에 돌려줘 그 사이에 비동기 틈이 없다) ③ may_ask 안의 폴더 봇은 바로 부르되 그 채널의 bot_send 는 자기 봇에게만(볼트 규칙 ① 유지) · 오케스트레이터 기록에 한 줄 ④ 긴급 메일 wake: { mail, from } — 급한 편지 한 통에 한 통 · 10분에 한 통 · 본문에 편지 내용 없음 ⑤ CLI 는 토큰을 0600 파일에서만 읽고 config.json 을 안 읽는다(비밀 없는 host-info.json · 앱 안 CLI Resources/folderbot) · 꺼져 있으면 편지만 남기고 4 ⑥ 끝 상태 0·2·3·4·5 는 상대가 외우는 숫자라 바꾸지 않는다. ⑦ (10/24 12:40 개정 · 할일이 셸 기본 30초·최대 780초·안전 60초) 기다림은 기본 50초 · 0 은 한 번 보기 · 최대 720초, 편지 id 를 기다리기 전 첫 줄에 찍는다(창구는 NDJSON 두 줄 — 셸이 먼저 끊겨도 id 가 남는다) · --wait 에 id 가 없으면 그 상대의 마지막 ask · --urgent ask 의 답이 기다림이 끝난 뒤 나면 답 편지를 쓸 때 긴급 메일. 금기 — 채널에 다른 봇 bot_send 를 열지 않는다 · 메일 본문에 편지 내용을 싣지 않는다 · 토큰을 인자로 받지 않는다. 검증: 유닛 askCli.test.ts 23(기다림 0·720·900 · 카드 줄마다 · id 첫 줄을 느린 창구로 잼 · 급한 늦은 답 메일) · 스모크 «folderbot ask»(S0 답·live · S1 폴더 봇 채널·기록·자기 봇만·folder_move 막힘 · 없는 봇 3 · S2 기본 50초에 2·id 첫 줄 → id 없이 --wait · --timeout 0 · 900→720 · S3 메일 한 통 · 급한 ask 늦은 답 메일 · S4 꺼짐 4 → 편지 줍기). 옛 판에는 ask 명령이 없어 블록 전체가 빨갛다.

ask 세션은 자동화 묶음에 숨지 않는다 (DB · 2026-10-25, Dave «계획대로 두 자리»): folderbot ask 로 온 말은 상대의 채널 세션에 들어가고, 채널 세션은 접힌 «자동화» 묶음에 살아서 Dave 가 «답했다» 를 알고도 그 세션을 못 찾았다. 결정 — ① 영역 판정(sessArea)은 그대로 두고, 세션 기록에 ask 표시(asks · 누가·언제·어느 편지)를 달아 화면에서만 자리를 옮긴다 ② 두 자리 — 왼쪽 목록(폰은 첫 화면) 맨 위 «할일이가 묻는 중 · 폴더» 줄과 그 봇 세션 목록 맨 위 ③ 턴이 끝나면 «답함 · 방금», 10분이 지나거나 Dave 가 열면(ask-seen) 원래 자리 ④ «열었다» 는 읽음(readAt)으로 갈음하지 않는다 — 폰·좁은 창은 목록이 서랍으로 덮일 뿐이라 아래 대화가 읽음을 적는다 ⑤ 여럿이면 모두, 시작한 순서대로 ⑥ «ask 대기 N건» 칩·자동화 묶음 펼치기는 설정에서 켜는 선택(기본 끔). 덤으로 --wait 가 id 없이 같은 초의 두 ask 중 앞의 것을 집던 결함을 파일 시각으로 가렸다(종료 코드·옵션 불변). 검증: 유닛 askMark.test.ts · 스모크 DB(답과 함께 닫힘 · ask-seen · 시작 3초 안 «묻는 중» · --no-wait 표시) · 캡처 05_캡처/2026-10-25_ask-세션-보이기/.

폰 푸시를 누르면 그 세션으로 (DK · 2026-10-26, Dave «모바일에서 알림을 클릭했을 때 해당 섹션으로 바로 이동하지 않는 버그»): 서비스 워커가 client.navigate('/#bot=…&s=…') 로 주소만 바꾸던 길이 iOS 홈 화면 앱에서 막혔다 — 뒤에 떠 있던 앱은 navigate() 가 실패해 보던 화면 그대로(WebKit 263687), 닫혀 있던 앱은 openWindow 가 해시를 버리고 첫 화면으로 뜬다. 결정 — 워커는 «어디로» 를 캐시 저장소 한 칸(fb-open)에 먼저 적고 떠 있는 화면에는 메시지도 보낸다 · navigate 는 쓰지 않는다 · 화면은 첫 로드·보임·포커스·pageshow·메시지 때마다 칸을 한 번 꺼내 지우고 go() 한다 · 5분 넘은 칸은 버린다(옛 알림 자리로 끌려가지 않게). 금기 — 칸 이름은 sw.js 와 pushOpen.ts 가 같아야 한다(워커는 모듈을 못 가져온다). 검증: 유닛 pushOpen.test.ts(가짜 self 위에서 sw.js 를 돌려 navigate 가 실패해도 칸·메시지·포커스 · 옛 sw.js 에서 2건 빨강) · 스모크 DK(떠 있던 앱 포커스 · 해시 없는 첫 로드에서 대화 칸 · 워커 메시지 · 5분 넘은 칸 버림). 실기기(iPhone) 확인은 배포 뒤. 같은 판에 스모크 경합 둘을 고쳤다(제품 코드는 그대로) — BR-1 은 루틴 세션을 회차가 끝나기 전에 지워 end 줄이 안 남았고(바쁜 맥미니에서 be013b5 도 2/2 빨강 · remove() 는 state 를 안 낸다), 편지 창 접기는 감시기 편지를 «지금 분의 01·02초» 로 찍어 분이 막 바뀐 때 실제 시각 편지가 끼었다(그날 00:00:0i 로 찍는다).

질문 카드의 «보낼 글» 블록 — 보이고 · 고치고 · 고친 글 그대로 (DL · 2026-10-26, Dave «수정·배포까지 맡김» · 오케스트레이터 중계): 전역 규칙이 «실행할까요?» 를 AskUserQuestion 카드로 묻고 보낼 글 전문을 카드에 넣으라고 바뀌었는데, 카드는 선택지의 preview 를 아예 안 읽고 질문을 평문 한 줄로 그렸다 — 박찬수 대표 확정 메일 카드는 «보낼까요?» 만 보였다(스크린샷_1706). 이제 ① 선택지(또는 질문)에 preview 가 있으면 질문 아래에 인용 상자 블록으로 그린다 — 줄바꿈 그대로 · 접지 않음 · 상자 그림/코드 울타리만 고정폭 · 질문 글도 줄바꿈을 지킨다 ② 블록의 [고치기] 로 그 자리에서 여러 줄 편집(⎋ 는 편집만 닫고 턴을 멈추지 않는다 · 폰은 16px 라 화면이 확대되지 않는다) ③ 고친 채로 보내면 답 끝에 « (고친 글로)» 를 붙이고 annotations[질문] = { preview: 고친 전문, notes: «Dave 가 고쳤다 — 그대로 쓸 것» } 을 같이 넘긴다 — 실 CLI(2.1.x)는 이것을 "질문"="실행 (고친 글로)" selected preview:\n… notes: … 로 모델에게 준다(바이너리에서 확인). 미뤄진 질문은 호스트가 같은 모양의 글을 tool_result 로 쓴다(종전 JSON 은 고친 글을 실을 자리가 없었다). 고치지 않으면 종전과 같다. ④ 글 없는 선택지(«고쳐서 실행»)를 골라도 고치던 블록은 남는다. 에이전트 입력 모양: 질문은 짧게(받는 사람·제목), 전문은 «실행» 선택지의 preview. 판정 src/core/askPreview.ts · 화면 PermCard/PreviewBox · 스모크 «DL 미리보기».