Docs · Cocode ADE · 분석 · 설계 문서

풀스택 코딩 에디터 · E14

Decision Log — 결정 기록

확장을 다듬으며 내린 결정

목차

Records all key decisions made during the spec clarification process. Uses an append-only markdown log instead of Ouroboros Event Sourcing. 번호 공간은 이 문서 고유다. 상위 파이프라인의 결정은 cocode/D-005 처럼 접두어를 붙여 인용한다. ⚠️ D-001~D-008 의 출처는 오너의 개별 확답이 아니라 "계속 진행 해줘"(2026-09-16) 한 마디로 Discovery §5.1 권장안을 일괄 수용한 것이다. 오너가 개별 항목을 뒤집으면 Seed Spec 진화 프로토콜로 반영한다.

Decision List

D-001: "풀스택" = Dart 풀스택 (Flutter · Jaspr · Serverpod · PostgreSQL)

  • Date: 2026-09-16
  • Context: 요청 원문 "풀스택 코딩 에디터"는 Dart 풀스택(H1)과 다국어(H2) 두 해석이 가능했다(Discovery §0). H2 는 상위 cocode/AC-05(Must-Not) · C-03 과 정면 충돌한다
  • Options:
    1. H1 Dart 풀스택 — 상위 제약과 합치, 조직 표준 스택과 일치, Level 3 유지
    2. H2 다국어 1급 — 상위 Seed 재작성 필요(Core Problem 변경 불가), Level 4 재분류
  • Decision: 옵션 1
  • Rationale: 권장안 Q1 수용. 비-Dart 파일 편집은 상위 C-03 후단이 이미 허용하므로 풀스택 워크스페이스의 SQL · YAML · Dockerfile 편집에 지장이 없다
  • Impact: Seed C-02 · AC-11 · A-01
  • Source: Discovery §5.1 Q1 권장안 → 오너 "계속 진행"

D-002: cocode ADE 의 하위 모듈로 app/cocode 안에 구축

  • Date: 2026-09-16
  • Context: 별도 앱을 만들면 셸 · 패키지가 중복되고 Level 4 가 된다. 상위 cocode/AC-03 이 요구하는 표면이 바로 이 작업이다
  • Options: 1. 하위 모듈 2. 별도 앱
  • Decision: 옵션 1
  • Rationale: 권장안 Q2 수용. Discovery §1.3 — 패키지는 있고 셸만 비어 있다
  • Impact: Seed C-01 · AC-01 · A-02. Level 3 유지
  • Source: Discovery §5.1 Q2 권장안 → 오너 "계속 진행"

D-003: 첫 릴리스 범위 = 셸 통합 + 에디터 코어 + 실행 구성 · PTY 터미널

  • Date: 2026-09-16
  • Context: Discovery §4.2 의 네 트랙(A 셸 통합 · B 에디터 코어 · C 풀스택 표면 · D 보조 사이드바) 중 어디까지를 첫 릴리스로 볼지
  • Options: 1. A+B 만 2. A+B+C(실행 구성 · PTY)까지 3. A~D 전부
  • Decision: 옵션 2. DB 브라우저 · API 클라이언트 · DAP 디버거 · git · pub.dev · 분할 뷰 · 세션 복원 · 대체 화면은 May(AC-14~AC-21)로 두고 Discovery §1.5 도그푸딩 실험 ⓑ 결과로 순서를 정한다
  • Rationale: 권장안 Q3 수용. 옵션 3 은 Discovery §5.2 위험 5(범위 팽창 → Level 4)
  • Impact: Seed §4 Must 10 · Must-Not 3 · May 8
  • Source: Discovery §5.1 Q3 권장안 → 오너 "계속 진행"

D-004: 에디터 코어는 re_editor 0.10.0 유지

  • Date: 2026-09-16
  • Context: flutter_ide 는 Monaco(WebView)를 쓴다. 최신 flutter_monaco 3.4.3 도 Linux 미지원 · 에셋 약 30MB(pub.dev, 2026-09-16)
  • Options: 1. re_editor 유지(상위 D-066) 2. Monaco 재검토 3. 자체 텍스트 레이어
  • Decision: 옵션 1. 옵션 3 은 상위 D-066 의 유보를 그대로 둔다
  • Rationale: 권장안 Q4 수용. 상위 C-01 의 데스크톱 3종 앞에서 Monaco 는 탈락. 다중 탭 조건의 응답성은 미검증(A-03)
  • Impact: Seed A-03 · AC-03~AC-07
  • Source: Discovery §5.1 Q4 권장안 → 오너 "계속 진행"

D-005: 별도 Seed Spec 을 쓰되 상위 불변 제약을 상속

  • Date: 2026-09-16
  • Context: 상위 Seed(docs/seed-spec-cocode.md v3.4.0)는 DRAFT 라 evolve 프로토콜 대상이 아니고, 범위가 다르다
  • Options: 1. 별도 Seed + 상속 조항 2. 상위 Seed evolve
  • Decision: 옵션 1
  • Rationale: 권장안 Q5 수용
  • Impact: Seed C-01. 번호 공간 분리 규칙(Metadata)
  • Source: Discovery §5.1 Q5 권장안 → 오너 "계속 진행"

D-006: Serverpod 4 serverpod start 는 명령 템플릿으로 시작, stable 후 1급 승격

  • Date: 2026-09-16
  • Context: 4.0 은 2026-07-08 public beta. Discovery 차별화 1 은 이 명령을 1급 실행 구성으로 둔다고 했다(Socratic L3 C-03)
  • Options: 1. 명령 템플릿(설정 값)으로 시작 2. 1급 실행 구성으로 즉시 구현
  • Decision: 옵션 1
  • Rationale: 권장안 Q6 수용. beta 명령 변경 위험을 코드가 아니라 설정이 흡수한다
  • Impact: Seed AC-08 · A-05. 템플릿 스키마는 Design 결정
  • Source: Discovery §5.1 Q6 권장안 → 오너 "계속 진행"

D-007: 도그푸딩 · 벤치 워크스페이스 = unibook (상위 D-019 의 SHA 고정)

  • Date: 2026-09-16
  • Decision: 권장안 Q7 수용
  • Impact: Seed A-08 · Discovery §1.5 실험
  • Source: Discovery §5.1 Q7 권장안 → 오너 "계속 진행"

D-008: 터미널 PTY 는 flutter_pty2 1.0.2 평가를 허용

  • Date: 2026-09-16
  • Context: 상위 D-067 은 xterm2 뷰의 IME 결함 재현으로 xterm2/flutter_pty2 를 미채택했다. PTY(프로세스 층)와 터미널 뷰는 분리 가능하다
  • Options: 1. flutter_pty2 평가 후 채택 여부 결정 2. 자체 FFI PTY 구현
  • Decision: 옵션 1
  • Rationale: 권장안 Q8 수용. 뷰는 terminal 자체 구현을 유지하고 백엔드만 교체한다(CocodeTerminalBackend 계약)
  • Impact: Seed A-04 · AC-09
  • Source: Discovery §5.1 Q8 권장안 → 오너 "계속 진행"

D-009: 편집 리스가 걸린 파일의 사람 편집 — 저장은 막지 않고 표시하며, 에이전트 쪽이 해시 불일치 충돌을 진다

  • Date: 2026-09-16
  • Context: 상위 cocode/AC-12(같은 파일 동시 편집 방지)는 두 DelegatedTask 사이의 규칙이고 사람은 대상이 아니다. 사람이 리스가 걸린 파일을 편집할 때의 규칙이 상위 문서에 없다(Socratic L3 C-02)
  • Options:
    1. 사람의 저장을 차단 — 사람이 에이전트에 종속됨, ADE 원칙과 충돌
    2. 저장을 막지 않고 리스 상태를 표시 — 에이전트의 다음 편집은 상위 AC-02 의 result_content_hash 불일치로 충돌 처리(기존 메커니즘 재사용)
    3. 사람 편집도 리스를 획득 — 상위 C-02 후단(사람 편집은 대상 아님)과 충돌
  • Decision: 옵션 2
  • Rationale: 새 규칙 없이 기존 충돌 메커니즘으로 닫힌다. 진행자 판단 — 오너 재확인 대상
  • Impact: Seed C-03 · AC-10
  • Source: Socratic Interview Level 3 C-02

D-010: 사람 편집은 EditSnapshot 을 만들지 않는다 (상위 C-02 후단 승계)

  • Date: 2026-09-16
  • Decision: 상위 cocode/C-02 의 문면을 그대로 따른다. 전역 검색 치환도 사람 편집이다
  • Impact: Seed C-03 · AC-06 · AC-13 · A-07
  • Source: 상위 Seed Spec 문면 확인(Socratic L2 A-07)

D-011: 앱 자신의 명령은 사용자 터미널의 표준 입력에 쓰지 않는다

  • Date: 2026-09-16
  • Context: flutter_ide 는 브랜치 체크아웃 · flutter run · flutter pub add 를 터미널 PTY 에 문자열로 타이핑한다(git_sidebar.dart:880,883, editor_screen.dart:894). 브랜치명 셸 인젝션과 포그라운드 프로세스 오입력이 실재한다(Discovery §4.1)
  • Options: 1. 같은 패턴 허용 2. 단일 런처 전용
  • Decision: 옵션 2
  • Rationale: 상위 DD-15 와 check_process_launch_allowlist.py 가 이미 같은 방향을 강제한다
  • Impact: Seed C-05 · AC-12 · TerminalSession 불변식 1
  • Source: Discovery §4.1 실사

D-012: 이 명세가 정하지 않는 값 3종의 이관

  • Date: 2026-09-16
  • Decision: (1) DB 접속 비밀의 저장 위치와 CredentialVault 키 네임스페이스 → Design (2) Dart 이외 언어 서버(SQL · YAML)의 설치 · 기동 정책 → Planning/Design (3) 응답성 · IME 목표치 → 상위 D-065/D-069 절차. Seed §4 의 어떤 항목도 이 값에 의존하지 않는다
  • Source: Socratic Interview Level 5

When adding a new decision, copy the template above and increment the number.

D-013: 롤백 사전검사에 "체인 불연속" 충돌 조건을 추가한다 (CH-001) — 상위 파이프라인 결정 필요

  • Date: 2026-09-16
  • Context: v1.0.0 C-03 은 "에이전트의 다음 편집은 상위 AC-02 의 해시 불일치 규칙으로 충돌 처리된다"고 했으나, 상위 AC-02/D-036 의 사전검사는 롤백 대상 작업의 마지막 스냅샷 result 해시만 대조한다. 작업 T 의 S1 → 사람 편집 → T 의 S2(base=사람 편집본) 뒤 T 를 시작 시점으로 롤백하면 현재 해시 == S2.result 라 충돌 없이 통과하고 사람 편집이 고지 없이 소멸한다(Contrarian CH-001, Critical)
  • Options:
    1. 사람 편집도 EditSnapshot 을 남긴다 — 상위 C-02 후단과 충돌, C-03 폐기
    2. 롤백 사전검사에 조건을 하나 더한다 — 롤백 구간 안에 체인 불연속(S2.base ≠ S1.result)이 있으면 충돌로 제시. 상위 AC-02 의 강화(충돌을 더 보고)이며 완화가 아니다
    3. 규칙을 두지 않고 위험을 인수 — flutter_ide 를 "구조적 데이터 손실"로 폐기한 문서가 같은 결함을 승인하는 셈
  • Decision: 옵션 2. Seed §0 "체인 불연속" · C-03 · AC-22(Must-Not)
  • Rationale: 매 에이전트 편집이 스냅샷을 남기고(상위 C-02) 다른 작업의 편집은 상위 AC-02 두 번째 조건(전역 마지막 스냅샷 소유)이 잡으므로, 같은 작업의 연속 스냅샷 사이 불연속은 사람 편집 또는 외부 도구 변경뿐이다 — 판정이 필요 없는 결정적 조건
  • Impact: 구현은 상위 workspace RollbackPrecheck 확장 — 상위 파이프라인(cocode ADE)의 결정 사항이므로 오너 재확인 1순위에 D-009 와 함께 올린다. D-009 의 "리스 상태 표시"는 "실행 중 작업의 편집 대상 표시"로 대체된다(D-045 리스 구간은 게이트 호출 한 번뿐이라 사람이 만날 수 없다)
  • Source: Contrarian Review CH-001

D-014: A-01(Dart 풀스택)은 "오너 일괄 수용으로 잠정 결정"으로 표기하고 Verified 로 적지 않는다 (CH-002)

  • Date: 2026-09-16
  • Context: 상위 Contrarian CH-001 은 "미해결 입력 위에 세운 제약"을 Critical 로 잡았고 그 스펙은 8라운드 게이트를 넘지 못했다. 이 문서의 H1 확정 근거는 오너의 "계속 진행 해줘" 한 마디다
  • Decision: §5 A-01 = Unverified(잠정 결정, 개별 확답 대기). Metadata 에 "잠금 근거" 줄을 두어 근거의 실체와 H2 정정 시 문서 폐기를 명시. 잠금은 이 표기 위에서 진행하고, 평가 보고서 · 최종 보고에 오너 재확인 1순위로 올린다
  • Rationale: 결정과 검증을 섞지 않는다(상위 자기 인용 패턴 회피). 비대화형 세션에서 개별 확답을 받을 수 없으므로 정직한 표기가 유일한 선택지
  • Impact: 복잡도 +2(미검증 가정). Seed Metadata · §5 A-01
  • Source: Contrarian Review CH-002

D-015: 풀스택 워크스페이스 판정 = pubspec.yaml 디렉터리(깊이 무관)의 의존 판정, 둘 이상 (CH-003)

  • Date: 2026-09-16
  • Context: v1.0.0 정의 "루트 바로 아래"는 unibook(app/ · backend/ 아래) 을 배제해 도그푸딩 워크스페이스가 정의상 대상 밖이었다
  • Decision: 판정 입력은 flutter SDK 의존 + lib/main.dart(Flutter 앱) · serverpod 의존(Serverpod 서버) · jaspr 의존(Jaspr 웹) 세 가지뿐. 통과한 디렉터리를 "프로젝트 디렉터리"라 부르고 RunConfiguration.working_dir 은 그중 하나
  • Rationale: 상위 D-052(플랫폼 디렉터리는 루트 바로 아래)와 반대 방향의 실수를 피한다. 서버 여러 개인 모노레포에서는 실행 구성이 프로젝트 디렉터리를 골라 가리키므로 "어느 서버인가"가 실행 구성 단위로 닫힌다
  • Impact: Seed §0 · §3 RunConfiguration 불변식 (1) · AC-08 · AC-14
  • Source: Contrarian Review CH-003

D-016: Simplifier 제안 처분과 AC 병합 2건

  • Date: 2026-09-16
  • Decision: S-001 Accept · S-002 Accept · S-003 Accept · S-004 Reject(핵심 가치) · S-005 Defer · S-006 Accept · S-007 Defer · S-008 Accept. 저자 추가로 AC-02(디렉터리 트리)를 AC-01(셸 통합)에, AC-05(외부 변경)를 AC-04(버퍼-디스크 동기화)에 병합 — 각각 한 표면의 한 계약이라 판정 단위가 어긋나지 않는다. 번호는 재사용하지 않는다
  • Rationale: Contrarian 이 요구한 추가 항목(AC-22 · AC-23 · A-10 · A-01 잠정 표기)으로 복잡도가 32.5 까지 오르므로, 판정 단위를 해치지 않는 병합으로 ≤ 30 안에 둔다. 예상 29.5, 정본은 Round 2 재측정
  • Impact: Seed §4 머리말 · §5 머리말
  • Source: Simplifier Review S-001~S-008

D-017: D-012 이관 목록 확장 (CH-004 · CH-006 · 채점 완전성 지적)

  • Date: 2026-09-16
  • Decision: D-012 의 세 항목에 더해 (4) 진단 수신 계약의 소유 패키지 — 제안: 에디터 측 sink(상위 SemanticTokenSink 와 같은 결), lsp 는 전달만, 자가 검증 ④ 입력 아님 → Design (5) 기본 실행 구성 템플릿 3종(flutterRun · serverpodStart · dartTest)의 정본 내용과 실행 전제 목록 → Planning (6) 바이너리 · 비 UTF-8 파일 열기 정책(읽기 전용 열기 또는 거부 + 사유 표시) → Design. Seed §4 의 어떤 항목도 이 값에 의존하지 않는다
  • Source: Contrarian Review CH-004 · CH-006 · 모호성 채점 Round 1 권고 4(d)

D-003 (v1.1.0 추가 근거, CH-009): 실행 구성이 첫 릴리스 Must 인 이유

  • Date: 2026-09-16
  • Rationale 추가: 사용자 명령 셸에 명령을 타이핑하면 프로세스의 기원 추적 · 일괄 종료 · 실행 전제 확인이 없다. 실행 구성은 세션 목록에 origin 을 남기고, 한 번의 조작으로 종료되며, 실행 전제(CLI 존재 · 디바이스 지정)를 실행 전에 확인해 미충족 사유를 표시한다 — 셸 타이핑으로는 얻을 수 없는 성질이다(Seed AC-08 Then). ⚠️ v1.1.1 교정: 환경변수 화이트리스트는 상위 architecture DD-23a 에 따라 사람 터미널에도 적용되므로 실행 구성 고유의 가치가 아니다 — v1.1.0 의 "사용자 명령 셸은 전체 환경을 상속한다"는 서술은 오류였고 삭제한다(Round 2 감사 CH-009 WEAK 판정). A-06(DB · API 내장 선호)은 §5 에서 이 결정으로 이관: May 항목 AC-14 · AC-15 의 채택 여부는 도그푸딩 실험(Discovery §1.5 ⓑ) 결과로 판단한다(S-003)

D-018: "기존 로컬 폴더를 워크스페이스로 열기"는 상위 파이프라인의 미결 항목이다 — 이 Seed 는 "열린 뒤"만 규정한다

  • Date: 2026-09-16
  • Context: 상위 ux-spec §2.5 UX-D-30 이 명시한다 — "「기존 워크스페이스(로컬 폴더) 열기」는 이 판본의 범위가 아니다 — 미정 · 결정 필요. seed-spec §4 인수 경계 어디에도 기존 워크스페이스를 여는 조항이 없다(AC-05 · AC-13 은 생성만 다룬다)." 이 Seed 의 AC-01~AC-10 은 전부 "워크스페이스가 열렸을 때"를 Given 으로 두며, 여는 경로(생성 · 기존 폴더 열기)는 규정하지 않는다. 그런데 D-007(unibook 도그푸딩)과 Discovery §1.5 실험은 스캐폴딩되지 않은 기존 저장소를 여는 것을 전제한다
  • Options:
    1. 이 Seed 에 Must 를 신설해 기존 폴더 열기(스캐폴딩 없이 .cocode/ 초기화 · applied_bricks 빈 집합)를 규정 — 상위 Workspace 엔티티의 "applied_bricks MVP 원소 1"(상위 PRD FR-105) 과 어긋나는 새 상태를 하위 문서가 만든다
    2. 상위 파이프라인의 결정으로 이관하고, 이 Seed 는 "열린 뒤"만 규정 — 도그푸딩 실험은 그 결정에 종속됨을 명시
  • Decision: 옵션 2. Seed 본문은 바꾸지 않는다(AC-01~AC-10 의 Given 은 이미 "열렸을 때"). 오너 재확인 항목에 D-013 · D-014 와 함께 올린다
  • Rationale: 워크스페이스 생명주기(생성 · 열기 · 닫기)는 cocode ADE 의 도메인이지 에디터 모듈의 도메인이 아니다. 하위 문서가 상위 엔티티의 상태 공간을 늘리면 상위 spec-evaluation 이 경계한 "판정 주체 미정" 결함을 하위에서 재생산한다
  • Impact: Planning 입력(docs/planning-inputs-fullstack-code-editor.md §2)에 "도그푸딩 전제: 기존 폴더 열기 결정" 등재. Seed 복잡도 변화 없음
  • Source: 상위 ux-spec §2.5 UX-D-30 실사(2026-09-16)

D-019: Design 리뷰 · 흐름 순열이 요구한 규칙 확정 (Solutioning Gate 전)

  • Date: 2026-09-16
  • Context: Architect 리뷰(AR-01~21) · UX 리뷰(UR-01~15) · 흐름 순열(127 순열, Critical E-C1~3 · Important 29)이 규칙이 비어 있는 지점을 지목했다. 값이 아니라 규칙이 비어 있었으므로 Seed 를 뒤집지 않는 범위에서 Design 이 정한다
  • Decision:
    1. PTY 기동 = ProcessLauncher.startPty 단일 메서드 + core.PtySpawner 주입(ED-06) — Seed C-05 "단일 런처" 해석을 "메서드 하나 + 주입 구현 1파일"로 명시(architecture §11-1)
    2. 종료 · 닫기 절차 6단계 · 고아 프로세스 0 · 일괄 저장 부분 실패 시 진행 중단(ED-16 · UX-E-18/19) — E-C1 · E-C2
    3. running 세션 × 비활성, 종료는 K4 버튼(UX-E-20) — E-C3
    4. 유즈케이스 6종 + Bloc optional constructor injection(ED-03) · 워크스페이스 스코프 컨테이너(ED-13) · terminate({grace})(ED-15) · DiagnosticsSink 13번째 계약(ED-09) · .cocode/editor/ 소유(ED-07) · Windows 셸 순서(ED-11, U-13 닫힘) · 제3 프로파일 범주(ED-12)
    5. UX 규칙 36건(UX-E-01~36) — 활성 탭 MRU · 진단 표시 범위 · 다시 실행 = 새 세션 · 중복 실행 허용 · 배너 다중 작업 · 디바이스 선택 · JSON 손상 처리 · Semantics 이중화 등
  • Impact: PRD v1.1 부연(FR-206 · 302 · 406 · 501 · 505 · 606) · BDD 시나리오 29건 추가(54 → 83: 흐름 순열 Must-Have 12 · Should-Have · rev.1 재판정 R-0 필수 · R-2 · R-5 · R-9) · architecture rev.4(2라운드 R2-01~06: L0/L2 경로 키 · 해시 String(ED-18) · 포트 분류(ED-19) · PtyHandle.terminate · store write-through · PtyLaunchRequest 합성) · ux-spec rev.1a(UR-16: 저장 실패 토스트 → Banner(.destructive), UX-E-37). Seed §4 문면 변경 0
  • Source: scratchpad/design-review-architect.md · design-review-ux.md · docs/flow-permutation-fullstack-code-editor.md

D-041: 비-git 무시 matcher 는 직접 만들고, 캐시 무효화는 전량이다 (U-14 확정)

  • Date: 2026-09-16
  • Context: FR-302 가 세 모드를 규정하면서 두 가지를 Design 으로 남겼다 — (a) git 저장소가 아닐 때 쓰는 .gitignore matcher 를 무엇으로 할 것인가, (b) 판정 캐시를 언제 무효화할 것인가. #335 가 그 둘을 정한다.
  • Options (a):
    1. pub 의 gitignore matcher 패키지를 의존한다
    2. 필요한 부분집합만 순수 Dart 로 직접 만든다
  • Decision (a): 옵션 2 — 직접 만든다(gitignore_rules.dart).
  • Rationale (a): 이 매처가 도는 것은 후퇴로다. git 저장소에서는 git check-ignore 가 정본이고(ED-06a), 이 경로가 필요한 것은 git 저장소가 아니거나 git 을 쓸 수 없을 때다. 그 두 경우에 필요한 것은 루트 .gitignore 하나이며 하위 디렉터리 · 전역 설정 · .git/info/exclude 는 애초에 대상이 아니다(#335 범위). 그 좁은 몫을 위해 L1 에 의존을 하나 더 들이면, 그 패키지의 해석이 git 과 갈리는 날 어느 쪽이 맞는지 판정할 근거가 우리에게 없다. 직접 만들면 지원 범위를 우리가 적을 수 있고, 그 목록이 소스 주석과 테스트에 그대로 있다.
    • 지원: 주석 · 빈 줄 · 부정(!) · 디렉터리 한정(build/) · 루트 고정(/x) · * ? [abc] · **
    • 미지원: 하위 .gitignore · 전역 설정 · .git/info/exclude · 이스케이프(\#) · 후행 공백 세부. 그 차이가 문제가 되는 상황은 git 이 있는 경우이고, 그때는 이 매처가 아니라 git check-ignore 가 답한다.
  • Options (b): 1. 바뀐 규칙이 영향을 주는 디렉터리만 무효화 2. 전량 무효화
  • Decision (b): 옵션 2 — 전량. 캐시 단위는 디렉터리이고, .gitignore 변경 알림(FileChangeWatcher ⓑ · #331)이 오면 캐시를 모두 비우고 루트 규칙을 다시 읽는다.
  • Rationale (b): 어느 디렉터리가 영향을 받는지 계산하려면 규칙을 해석해야 한다. 그 해석이 정확하다면 애초에 git 에 물어볼 이유가 없다 — 우리가 직접 판정하면 된다는 뜻이고, 그것은 이 설계가 거부한 전제다. 캐시 한 벌을 다시 채우는 비용(디렉터리당 check-ignore 1회, 펼쳐진 것만)이 그 계산의 위험보다 싸다.
  • Impact: PRD U-14 행 → 확정. IgnoreMatcher 3모드 + 디렉터리 단위 캐시 + invalidate(). 검색(FR-304)이 같은 인스턴스를 재사용한다.
  • Source: #335 구현 · package/workspace/lib/src/ignore/

D-042: Diagnostic 은 LSP 필드의 부분집합이고, 고른 기준은 「화면이 그리는 것」이다 (§10-3 확정)

  • Date: 2026-09-17
  • Context: architecture §10-3 이 「DiagnosticsSink 값 타입 상세(LSP Diagnostic 필드 부분집합)」를 EPIC-4 S3(#343)에 위임했다. LSP 3.17 의 Diagnostic 은 9개 필드를 갖는다.
  • Options:
    1. LSP 필드를 전부 가져온다
    2. 화면이 그리는 것만 가져온다
    3. 지금 필요한 것만 가져오되 확장 여지를 Map<String, Object?> extra 로 남긴다
  • Decision: 옵션 2 — range · severity · message · code? 넷.
  • Rationale: [문제] 패널 행은 「심각도 아이콘 · 파일 · 행:열 · 메시지」이고(ux-spec §4.4) 거터 표식은 그 행 번호를 쓴다. 그 밖의 필드는 그릴 자리가 없다:
    • source — 첫 릴리스의 서버가 하나다(Dart). 「어느 서버가 말했나」를 그릴 칸이 없다
    • tags(unnecessary · deprecated) — 흐리게 그리기 · 취소선이 ux-spec 에 없다
    • relatedInformation — 「여기도 보라」는 이동 UI 가 필요한데 그 화면이 없다
    • codeDescription — 규칙 문서 링크. 외부 브라우저를 여는 경로가 첫 릴리스 밖이다
    • data — code action 용 불투명 값. code action 이 없다 옵션 3(extra)을 버린 이유: 쓰지 않는 값을 들고 다니면 「왜 안 보이지」를 다음 사람이 디버깅한다. 없는 필드를 나중에 더하는 것은 값이지만, 있는데 안 그리는 필드는 부채다.
  • 부수 결정 2건:
    • 좌표는 0부터(LSP 그대로) — 화면이 1부터 세는 것과 다르다. 여기서 바꾸면 거터 표식 · 커서 이동이 각자 되돌리기를 해야 한다. 변환은 그리는 자리 한 곳에서 한다.
    • 모르는 심각도는 info — 던지거나 버리면 「서버는 말했는데 화면에 없는」 진단이 생긴다. LSP 1~4 와 Dart enum index(0부터)를 같은 것으로 다루지 않도록 변환은 DiagnosticSeverity.fromLsp 한 곳에서만 한다.
  • Impact: core/src/contract/diagnostics_types.dart 신설 · contractTypes 12 → 13(ED-09) · lsp 선택 인자 diagnostics(기본 NullDiagnosticsSink — 기존 47 테스트 회귀 0) · editor CocodeDiagnosticsSink. 상위 C5(자가 검증 ④ 는 진단을 쓰지 않는다)는 toolchain 소스 스캔 테스트가 판정한다.
  • Source: #343 구현 · docs/architecture-fullstack-code-editor.md §4.7 · §10-3 · ux-spec §4.4

D-074: 터미널은 xterm3 뷰 + flutter_pty 다 — AGPL-3.0 을 오너가 수용했다 (D-008 대체 · 상위 D-067 관련)

  • Date: 2026-09-27
  • Context: D-008 은 PTY 백엔드만 바꾸고 뷰는 자체 구현을 유지하기로 했다. 그러나 자체 파서는 커서 이동 · 대체 화면을 버린다(cocode_ansi_parser.dart 머리말) — 로그인 셸(zsh 줄 편집 · powerlevel10k) · vim · serverpod start TUI 를 그릴 수 없다. 오너가 xterm3 · flutter_pty 채택을 지시했다.
  • Options: 1. xterm3 6.3.4(AGPL-3.0) 2. xterm 4.0.0(MIT) 3. xterm2 5.2.0(MIT, 상위 D-067 이 IME 결함 재현)
  • Decision: 옵션 1 — 라이선스를 명시해 묻고 오너가 AGPL 을 수용했다(2026-09-27 세션). PTY spawn 은 ED-06 그대로 — ProcessLauncher.startPty 가 정책을 적용하고 FlutterPtySpawner(유일한 Pty.start 파일)가 spawn 한다.
  • Rationale: 세 뷰의 API 가 같은 계열이라 되돌림 비용은 import 한 줄이다. 로그인 셸 · TUI 가 실제로 그려지는 것(macOS 실측 — oh-my-zsh 배너 · p10k 프롬프트 · Serverpod TUI)이 IDE 터미널의 최소 조건이다.
  • Impact: ⚠️ ADE 배포물 전체가 AGPL-3.0 의무를 진다(배포 · 네트워크 제공 시 전체 소스 공개). THIRD_PARTY_NOTICES.md 갱신 대상. AC-16(한글 IME)은 xterm3 뷰에서 다시 재야 한다 — 기존 재생 벤치마크는 자체 뷰를 몬다. flutter_pty 0.4.2 결함(실행 파일 부재 시 크래시 · SIGTERM 무시 · Windows 인자 중복)은 런처의 존재 검사 · SIGHUP 종료로 우회했고, 근본 해소는 flutter_pty2 2.0.0(MIT) 재평가다.
  • Source: 오너 지시(2026-09-27) · 패키지 조사(pub.dev 라이선스 태그 · 소스 실측)

D-075: 편집 표면은 macOS · Windows 에서 Monaco(flutter_monaco 3.4.3), Linux 는 re_editor 유지 (D-004 부분 대체)

  • Date: 2026-09-27
  • Context: D-004 는 Monaco 를 Linux 미지원 · 에셋 30MB 로 탈락시켰다. 오너가 Monaco 채택을 지시했다.
  • Options: 1. 3-OS 전부 Monaco(Linux 불가) 2. 플랫폼별 표면 — Monaco 가 되는 곳만 Monaco 3. re_editor 유지
  • Decision: 옵션 2. 본문의 정본은 여전히 EditorDocument.controller 이고 Monaco 는 그것을 양방향으로 비춘다(CocodeMonacoSurface). WebView 는 하나 — 탭마다 모델만 바꾼다.
  • Rationale: 저장 · dirty · 언어 서버 공급 · 모두 바꾸기가 컨트롤러를 읽으므로, 정본을 옮기지 않으면 그 경로를 하나도 고치지 않는다(사본 셋 문제 — discovery 의 flutter_ide 분석). WebView 1개 · 모델 전환 6~12 ms(실측).
  • Impact: 앱 번들 +약 16MB(Monaco 0.55.1 에셋) · 첫 에디터 기동 0.7~1.2 s. WebView 가 키를 가져가므로 단축키는 Monaco 동작으로 등록하고(Cmd/Ctrl+S), WebView 밖 누름에서 releaseNativeFocus() 로 키보드를 Flutter 에 돌려준다. WebView 안 한글 IME 는 AC-16 측정 대상 밖(재생 하네스가 Flutter 입력을 몬다) — 새 측정 경로가 필요하다.
  • Source: 오너 지시(2026-09-27)

D-076: macOS 는 창 안 메뉴바를 두지 않는다 — 셸 메뉴를 시스템 메뉴바에 덧붙인다(표준 Edit 유지)

  • Date: 2026-09-27
  • Context: 창 안 Menubar(File · Edit · View · Delegate · Run · Help)가 시스템 메뉴바와 두 벌이었다(오너 지적). 단, 셸 명령에는 단축키가 없어(U-11) 메뉴가 유일한 진입점이었다.
  • Options: 1. 창 안 메뉴바만 제거 2. Flutter PlatformMenuBar 로 교체 3. 러너(Swift) 다리로 덧붙이기
  • Decision: 옵션 3 — ShellMenuBridge(AppDelegate.swift) + native_shell_menu.dart. Windows · Linux 는 전역 메뉴바가 없어 창 안 메뉴바가 그대로다.
  • Rationale: 1은 저장 · 실행에 닿는 길을 없앤다. 2는 메뉴바 전체를 갈아 끼워 표준 Edit(copy: · paste: · undo:)이 사라지고, WebView(Monaco)의 Cmd+C/V 가 죽는다(WebKit 은 그 키를 메뉴 동작으로 받는다).
  • Impact: 단축키는 관례값을 임시로 붙였다(⌘O · ⌘S · ⌥⌘S · ⌘W · ⌘R · ⇧⌘R · ⌘. · ⌃` · ⌃⇧`) — U-11 확정 시 교체. 빈 메뉴(Delegate · Help 항목)는 싣지 않는다.
  • Source: 오너 지적(2026-09-27)

D-077: 풀스택 실행은 대상의 정본 러너를 PTY 탭에서 띄운다 — 앱은 띄우기 · 끝내기만 한다

  • Date: 2026-09-27
  • Context: 실행 구성(EPIC-5)은 미구현이고, runner 가 전제한 cob dev 는 설치된 cob 0.4.0 에 없다.
  • Options: 1. flutter run --machine JSON 프로토콜을 앱이 직접 해석 2. 정본 러너를 PTY 탭에서 — Serverpod 서버 serverpod start, Flutter 앱 flutter run -d <데스크톱> [--flavor], Dart dart run 3. cob dev 대기
  • Decision: 옵션 2. 대상은 워크스페이스의 pubspec.yaml 에서 찾는다(run_targets.dart, 깊이 3). .fvmrc 가 있으면 fvm 을 앞에 붙인다.
  • Rationale: serverpod start 는 코드 생성 · 서버 watch 핫 리로드 · 동반 앱을 한 번에 띄우는 Serverpod 4 의 풀스택 러너이고, 그 TUI 가 xterm3 에서 그대로 동작한다(실측). 핫 리로드 키는 사용자가 누른다 — 앱은 사용자 셸 stdin 에 명령을 타이핑하지 않는다(C-05 · D-011).
  • Impact: IDE 버튼으로 핫 리로드를 부르는 통합(옵션 1)은 후속. 영속 실행 구성(ED-07)은 여전히 EPIC-5 소관. Dock 에서 뜬 앱을 위해 런처가 $SHELL -l -i 로 PATH 를 한 번 읽는다(adoptLoginShellPath).
  • Source: 오너 지시(2026-09-27) · Lumide lumide_flutter · roxum-ide 조사

D-078: D-013 옵션 2 를 상위에서 승인했다 — 체인 불연속을 롤백 충돌로 제시한다 (#373)

  • Date: 2026-09-28
  • Context: D-013 은 옵션 2 를 채택하되 상위 파이프라인(cocode ADE)의 결정이 필요하다고 남겼고, 결정 이슈 #373 은 12일 동안 답이 없었다. 조사해 보니 구현 지점은 workspace 가 아니라 core 의 순수 함수 RollbackTargets.compute(rollback_targets.dart:549)였다. 사전검사(PrecheckRollbackUseCase)와 적용 엔진(RollbackEngine)이 같은 결과를 쓴다
  • Options: 1. 승인 2. 기각 — AC-22 · C-03 개정, BDD [AC-22] 2건 삭제 3. 조건부 — 에이전트 루프 배선 뒤로
  • Decision: 승인. 상위 기록은 cocode/D-074
  • Rationale: 충돌을 더 보고하는 방향이라 상위 AC-02 의 강화다. 사람 편집이 조용히 사라지는 경로(CH-001 · Critical)를 남길 이유가 없다. 변경은 순수 함수 한 곳이다. 앱이 아직 에이전트 편집을 하지 않아 서두를 필요는 없지만, 결정을 미룰 이유도 없다. 조건부는 원래 착지점(상위 Epic #21)이 닫혀 있어 새 착지점을 정하는 일만 늘린다
  • Impact: #372 차단이 풀린다. BDD [AC-22] 2건의 @pending-d013 은 #372 가 푼다. D-013 Impact 의 「workspace RollbackPrecheck 확장」은 「core RollbackTargets.compute 에 조건 추가」로 읽는다
  • Source: 오너가 판단을 위임(2026-09-28 — 「모두 직접 판단해서 처리」) · #373 개선 패스 현황 절

D-079: D-018 해소 — 「폴더 열기…」를 기존 폴더 열기의 정식 경로로 승격하고, .cocode/ 는 첫 위임 때 만든다 (#375, 옵션 (a′))

  • Date: 2026-09-28
  • Context: #437(2026-09-27)이 「폴더 열기…」 · 마지막 폴더 기억 · 「워크스페이스 닫기」를 「D-018 전 임시 경로」로 넣었다. 09-27 부터 앱은 마지막 폴더로 바로 뜬다. 이 경로는 .cocode/ 를 만들지 않는다. 반대로 상위 UX-D-30 이 정한 생성 진입점(「새 워크스페이스…」)은 앱에 없다. 워크스페이스 스코프 계약 3종(SnapshotStore · WorkspaceFileGate · EditLockRegistry)은 이 결정을 사유로 미등록이었다
  • Options: (a) 승격 + 열 때 .cocode/ 초기화 (a′) 승격 + 첫 위임 때 초기화 (b) 첫 릴리스는 생성 워크스페이스만 (c) 기타
  • Decision: (a′).
    • 「폴더 열기…」 · 마지막 폴더 기억 · 「워크스페이스 닫기」가 정식 경로다. 최근 목록 · CLI 인자는 #448 이 채운다.
    • 여는 것만으로는 .cocode/ 를 만들지 않는다. 편집 · 검색 · 실행 · 터미널은 .cocode/ 없이 동작한다.
    • 첫 위임 직전에 확인을 받고 .cocode/ 를 만들며 applied_bricks = [] 로 둔다. 스코프 계약 3종은 그 시점에 등록한다 — 에이전트 루프 앱 배선(#447) 소관이다.
    • 상위 Workspace applied_bricks 의 「MVP 원소 1」(상위 PRD FR-105 · cocode/D-037)은 「생성 워크스페이스 1 · 위임한 기존 폴더 0」으로 개정한다. 개정은 #447 의 Seed 에서 한다
  • Rationale: 1차 고객은 외부 Dart/Flutter 개발자(cocode/D-005)이고, 그들의 첫 동선은 기존 저장소를 여는 것이다. (b) 는 D-007 도그푸딩과 정면 충돌한다. (a) 는 여는 것만으로 사용자 저장소에 .cocode/ 를 남겨 gitignore 안내가 필요해진다. (a′) 는 지금 동작 그대로이고 상위 개정 범위가 가장 좁다 — 위임한 기존 폴더에만 새 상태가 생긴다
  • Impact: 「D-018 전 임시」 표기 3곳을 정식 표기로 고쳤다(workspace_history.dart · commands.dart · app/cocode/pubspec.yaml). bootstrap.dart pendingContracts 의 스코프 3종 사유는 #447 을 가리킨다. Patrol 픽스처(스캐폴딩되지 않은 임시 폴더)는 그대로 유효하다. 상위 기록은 cocode/D-075
  • Source: 오너 위임(2026-09-28) · #375 개선 패스 현황 절

D-080: A-01 확정 — 「풀스택」= Dart 풀스택(H1). 1급 프로젝트 타입의 정본은 C-02 3종이다 (#377, D-014 종결)

  • Date: 2026-09-28
  • Context: D-014 는 A-01 을 「오너 일괄 수용으로 잠정 결정」으로 두고 개별 확답을 기다렸다. 그 사이 #399 · #433 · #437 이 H1 위에 머지됐고, 09-27 오너 지시(D-074~D-077)도 전부 H1 을 전제했다. 1급 열거는 세 곳에서 달랐다.
    • Seed C-02: Flutter 앱 · Serverpod 서버 · Jaspr 웹
    • 상위 CocodeProjectType: Dart 패키지 · Flutter 앱 · Flutter 패키지
    • D-077 RunTargetKind: Serverpod · Flutter · Dart
  • Options: 1. H1 확정 2. H2(다국어) 정정 — 이 Seed 폐기 · Level 4 재분류 · 상위 Seed 개정 선행
  • Decision: H1 확정. §5 A-01 = Verified.
    • 1급 프로젝트 타입의 정본은 C-02 3종이다.
    • RunTargetKind(Dart 실행 파일 포함)와 CocodeProjectType(Dart · Flutter 패키지 포함)은 「Dart 이외 없음」(AC-11)을 지키는 변형으로 인정한다.
    • Jaspr 웹은 판정 · 실행 대상이 없다. 이 공백은 EPIC-5(#311) 재착지 때 ProjectDirectoryDetector 와 실행 대상에 더해 메운다
  • Rationale: 상위 AC-05 · C-03 과 cocode/D-002(Dart/Flutter 깊이 포지셔닝)에 맞는다. H2 는 상위 Seed 개정이 먼저라 이 트리에서 고를 수 있는 선택지가 아니다. 머지된 코드와 오너 지시가 모두 H1 이다
  • Impact: #371 의 AC-11 판정 문장은 「Dart 이외의 1급 항목 없음」이 기준이다. Seed §5 A-01 문면과 Metadata 「잠금 근거」 갱신은 Seed 진화(#443)에서 한다
  • Source: 오너 위임(2026-09-28) · #377 개선 패스 현황 절

D-081: 에이전트 루프의 앱 배선은 새 Project(#447) 로 세우고 #305 잔여와 병행한다 (#446)

  • Date: 2026-09-28
  • Context: 앱은 계약 13종 중 5종만 등록한다. AgentRuntime · VerificationRunner · SandboxPolicy · 스코프 3종 · FileFormatParser 는 「상위 결정」「상위 Epic 소관」을 사유로 미등록인데, 그 사유가 가리키던 상위(#2 · #21 · #33 · #43 · #62 · #79)는 전부 닫혔다. 이 Seed 는 이 배선을 범위 밖에 둔다
  • Options: (1) 새 Project (2) #305 에 EPIC-8 추가 — Seed 개정 · Level 재평가 (3) #305 출하 뒤로 미룸
  • Decision: (1).
    • #305 는 Seed v1.1.1 범위로 닫는다. 새 Project 는 #305 출하를 기다리지 않는다.
    • 첫 목표는 수직 슬라이스다. 백엔드 1종으로 위임 → 게이트 · 스냅샷 → 자가 검증 1종 → 승인 · 되돌리기를 앱에서 관통한다.
    • 권고(Design 이 바꿀 수 있다): ACP 먼저, 검증 실행기는 정적 분석 먼저. AgentRuntime 등록 모양은 그 Project 의 Design 이 정한다
  • Rationale: (2) 는 D-003 이 「범위 팽창 → Level 4」로 기각한 축과 같다. (3) 은 ADE 의 차별점(위임 루프) 없이 IDE 표면만 출하하는 것이고, 상위 Must AC-01 · 02 · 06 · 07 · 08 · 09 를 앱에서 미충족으로 남긴다. 새 Project 는 파이프라인으로 범위와 AC 를 새로 잡을 수 있다. 지금은 구현 처리량보다 결정이 병목이라 병행 비용이 작다
  • Impact: pendingContracts 사유가 #447 을 가리킨다. D-079 의 .cocode/ 초기화 · 상위 applied_bricks 개정 · 생성 마법사 배선이 #447 로 모인다. #372 · #193 · #314 배너의 앱 경로도 #447 로 모인다. 상위 기록은 cocode/D-076
  • Source: 오너 위임(2026-09-28) · #446

D-082: xterm3(AGPL-3.0)을 담은 빌드는 법무 회신 전까지 외부에 배포하지 않는다 (D-074 후속, #440)

  • Date: 2026-09-28
  • Context: D-074 로 오너가 AGPL-3.0 을 수용했다. 그런데 docs/oss-notices-cocode.md §4.4 는 번들에 포함하면 법무 검토 요청의 발신 사실과 회신을 결정에 링크하라고 요구하고, 그 기록은 0건이다. AGPL 은 배포물 전체에 대응 소스 제공 의무를 걸 수 있다
  • Decision: 요청안(질문 · 배포 형태 · 대안)을 oss-notices §2.1.1 에 준비했다. 발신은 오너가 지정할 법무 창구가 한다. 회신 전에는 xterm3 를 담은 빌드를 외부에 배포하지 않는다 — 내부 빌드와 도그푸딩만 한다. 회신이 부정적이면 MIT xterm 4.0.0 으로 되돌린다(import 한 줄 — D-074 Rationale)
  • Rationale: 오너의 수용(D-074)은 유지한다. 다만 수용과 법무 판단은 다른 일이다. 되돌림 비용이 작은 동안 배포를 막아 두면 판단이 늦어져도 잃는 것이 없다. 지금은 릴리스 브랜치도 태그도 없어 이 제약의 비용은 0 이다
  • Impact: 첫 외부 릴리스의 전제 조건에 #440 회신이 붙는다
  • Source: 오너 위임(2026-09-28) · #440 · oss-notices §4.4

D-083: 워크스페이스 검색이 에디터 찾기(Monaco)와 같은 토글을 준다 — [ab] 단어 단위 · [AB] 대소문자 보존 · 제외 glob (ux-spec §4.3 「첫 릴리스 밖」 해제)

  • Date: 2026-09-28
  • Context: D-075 로 편집 표면이 Monaco 가 되면서 에디터 안 찾기 위젯은 [Aa] [ab] [.*] 와 치환의 [AB] 를 준다. 워크스페이스 검색(S-E03)은 [.*] [Aa] 만 있었다(ux-spec §4.3 「단어 단위 · 글로브는 첫 릴리스 밖」). 같은 검색어 · 같은 토글이 두 곳에서 다른 자리를 맞히면 사용자는 어느 쪽이 맞는지 판정할 수 없다. 오너가 Monaco 찾기/바꾸기를 워크스페이스 검색에 반영하고 제외 형식을 더하라고 지시했다.
  • Options: 1. 토글만 더하고 판정은 Dart \b 2. Monaco 판정 이식 — wordSeparators 경계 · VS Code buildReplaceStringWithCasePreserved 3. 워크스페이스 검색 대신 에디터 찾기만 쓴다
  • Decision: 옵션 2. 정규식 조립을 core searchPatternOf 한 함수로 모아 검색 isolate 와 치환이 함께 쓴다(이전에는 두 벌을 같게 유지했다). 대소문자 보존은 preserveCaseOf. 제외는 쉼표로 가른 glob 이고 .gitignore 한 줄 문법이다 — GitignoreRule 을 재사용하며, 조상 디렉터리가 제외되면 그 아래를 ! 로 되살리지 않는다(git 규칙). 해석되지 않는 glob 은 스트림을 열기 전에 SearchExcludeException 으로 던진다.
  • Rationale: Dart \b 는 ASCII 단어 글자만 알아 한글 옆 경계가 Monaco 와 갈린다(id값 에서 \bid\b 는 맞고 Monaco 는 안 맞는다). 맞은 첫 · 끝 글자가 구분자인 경우(.foo)도 판정이 다르다. glob 을 새 문법으로 만들면 트리의 무시 규칙(#335)과 같은 글이 다른 뜻이 된다. 제외 오류를 FormatException 으로 올리지 않는 것은 정규식 오류와 고칠 입력칸이 달라서다.
  • Impact: WorkspaceSearchPort.search · SearchWorkspaceUseCase 에 wholeWord · excludes 인자(기본값 — 호출부 호환). SearchSummary.excludedFileCount → 요약 «N개 파일 제외». [검색어 지우기] 가 토글 · 제외 glob 을 남긴다(입력칸과 상태가 갈리지 않게). 로컬 아이콘 묶음에 case-upper 추가. 남는 차이: 치환의 정규식 캡처 참조($1)는 여전히 글자 그대로다(ux-spec §4.3 결정) · 포함 glob · 결과 행 단위 바꾸기는 범위 밖.
  • Source: 오너 지시(2026-09-28) · Monaco USUAL_WORD_SEPARATORS · VS Code src/vs/base/common/search.ts

D-084: 편집 그룹(화면 분할)은 세션 상태의 배치다 — 탭 끌어 놓기로 옮기고 나누며, 그룹마다 Monaco 표면 하나 (D-075 부분 개정 · 창 분리는 후속)

  • Date: 2026-09-28
  • Context: 오너가 Flutter 3.47 데스크톱 멀티 윈도우로 ① 탭을 끌어 별도 창으로 분리 ② 그 창을 탭 줄에 다시 붙이기 ③ 탭 끌기로 화면 분할을 지시했다. 3.47.3 실측: windowing API 는 package:flutter/src/widgets/_window.dart 의 @internal 실험 기능(공식 지원은 main 채널 + --enable-windowing, 「프로덕션 금지 · 패치 버전에서도 깨짐」). stable 엔진도 FFI 심볼 22개를 싣고 있어 --dart-define=FLUTTER_ENABLED_FEATURE_FLAGS=windowing 으로 켜지기는 한다. 그러나 ⓐ RegularWindowController 에 창 위치 get/set · 이동 이벤트가 없고(windowHandle 로 네이티브 보강 필요) ⓑ 창 기능을 켜면 러너가 「창 없는 엔진 + Dart 가 창 생성」이 되고 뷰 id 가 1부터라 implicit 뷰가 사라진다 → flutter_monaco 의 포커스 전환(registrar.view)이 메인 창에서도 깨지고, ShellMenuBridge(D-076)가 mainFlutterWindow 에 묶여 있다.
  • Options: 1. 분할 먼저(stable API) · 창 분리는 개발 빌드 실험 플래그 후속 2. 둘 다 지금 실험 API 로 3. 분할만(창 분리는 stable 공식 지원 대기)
  • Decision: 옵션 1(오너 선택). 분할의 모델:
    • EditorSessionState 에 layout(EditorLayout — 분할 트리 · 그룹{탭 순서 · 활성 탭} · 활성 그룹) 필드를 둔다. activeTab 은 필드가 아니라 활성 그룹에서 읽는 getter 다. 필드 열거 가드(ED-02)를 이 결정으로 갱신했다 — 배치는 경로 키와 그룹 번호뿐이고 열기 · 닫기 · 옮기기 · 떼기에서만 바뀐다.
    • 한 문서는 한 그룹에만 열린다 — Monaco 모델이 WebView 마다 생기므로, 같은 문서를 두 그룹에 열면 정본 하나에 사본이 둘 붙고 닫기 처분(#328)이 「마지막 사본인가」를 물어야 한다.
    • 닫기의 다음 활성은 같은 그룹 안의 MRU다. 비는 그룹은 걷고(마지막 그룹은 남긴다) 트리를 접는다. 그룹 번호는 재사용하지 않는다.
    • 그룹마다 CocodeMonacoSurface 하나(D-075 의 「WebView 하나」를 「그룹당 하나」로 개정). 그룹 칸은 GlobalKey 로 붙잡는다 — Resizable 이 칸을 키 없이 자리 순서로 맞춰, 트리가 바뀌면 다른 그룹의 WebView 가 재사용되거나 다시 뜬다.
  • Rationale: 배치를 세션 밖 모델로 두면 「같은 그룹 안 MRU」 닫기 규칙이 두 곳에 생긴다. 분할은 실험 API 가 필요 없어 지금 안전하게 낼 수 있고, 창 분리는 러너 재작성 · flutter_monaco 포크 · 네이티브 창 위치 보강이 한 덩어리라 따로 판정할 가치가 있다.
  • Impact: ~~문서를 다른 그룹으로 옮기면 그 문서의 Monaco 실행 취소 이력이 끊긴다~~ → D-085 으로 이어진다. ~~한 문서는 한 그룹에만~~ → D-085 으로 해제. ~~분할선 비율은 상태가 아니라 구조가 바뀌면 같은 비율로 다시 선다~~ → D-085 으로 남은 칸끼리의 비율을 지킨다. 옮기기 직전 150 ms 창의 편집은 모델을 닫기 전에 끌어와 잃지 않는다. WebView 위 누름은 Flutter 로 오지 않아 활성 그룹 판정은 Monaco onFocusChanged 로 한다. 분할선 비율은 상태가 아니라 구조가 바뀌면 같은 비율로 다시 선다. 후속(창 분리): 개발 빌드 한정 FLUTTER_ENABLED_FEATURE_FLAGS=windowing · 러너(AppDelegate · MainFlutterWindow) 재작성 · flutter_monaco 포커스의 다중 뷰 대응(포크 또는 상류 PR) · windowHandle 기반 창 위치 · 이동 추적(탭을 끌어 창 밖에 놓으면 그 자리에 창, 창의 탭을 다른 창 탭 줄에 놓으면 합치기).
  • Source: 오너 지시 · 선택(2026-09-28) · Flutter 3.47.3 SDK 실측(_features.dart · _window_macos.dart · FlutterEngine.mm addViewController · FlutterCompositor.mm) · flutter_monaco 3.4.3 FlutterMonacoPlugin.swift

D-085: 분할의 남은 제약을 푼다 — 실행 취소 이력 이관 · 같은 문서 여러 그룹 · 분할선 비율 유지 (D-084 개정 · UX-E-13 결함 정정)

  • Date: 2026-09-28
  • Context: D-084 는 세 제약을 남겼다 — ⓐ 문서를 다른 그룹으로 옮기면 Monaco 실행 취소 이력이 끊긴다(그룹마다 WebView 가 따로) ⓑ 한 문서는 한 그룹에만 ⓒ 분할 구조가 바뀌면 비율이 균등으로 돌아간다. 오너가 모두 풀라고 지시했다. 조사 중 기존 결함도 드러났다: CocodeMonacoSurface 가 버퍼 쪽 변경(모두 바꾸기 · 디스크에서 다시 읽기)을 MonacoDocument.setText(= model.setValue, 실행 취소 스택을 비운다)로 비춰, Monaco 에서 모두 바꾸기를 Cmd+Z 로 되돌릴 수 없었다(UX-E-13 위반). applyEdits 도 model.applyEdits 라 실행 취소에 오르지 않는다.
  • Options (ⓐ): 1. 실행 취소를 버퍼(Dart) 쪽으로 옮기고 Monaco 의 Cmd+Z 를 가로챈다 2. Monaco 이력을 걸어서 꺼내 새 모델에 다시 쌓는다 3. 문서마다 WebView(D-075 가 비용으로 탈락시킨 길)
  • Decision:
    • ⓐ 옵션 2. 페이지에 cocode 다리(kCocodeMonacoBridgeScript)를 심는다: exportHistory 가 원래 모델에서 undo()·redo() 를 끝까지 밟아 단계별 본문을 **차이(구간 교체)**로 모으고 제자리로 돌려놓는다(편집기 뷰 상태도 복원). importHistory 가 새 모델을 가장 오래된 본문에서 그 차이들을 pushEditOperations + pushStackElement 로 한 단계씩 다시 쌓는다(다시 실행 단계는 쌓은 뒤 되돌려 복원). 옮기는 쪽이 레이아웃을 바꾸기 전에 CocodeMonacoHistoryHandoff.capture 로 꺼내 두고, 새 표면이 그 문서의 모델을 열 때 이어 받는다. 앞뒤 각 300단계 상한.
    • 버퍼 쪽 변경은 applyText — 바뀐 구간만 pushEditOperations 로 교체한다. 실행 취소가 되고 커서 · 스크롤이 그대로다(UX-E-13 결함 정정, 같은 문서의 다른 그룹 동기화도 이 길).
    • ⓑ 같은 문서를 여러 그룹에 연다(한 그룹 안에서는 한 번). 트리 · 검색 · 문제의 열기는 활성 그룹에 선다(모두 바꾸기는 preferExisting 으로 열린 탭을 쓴다). 닫기는 그룹을 받아 다른 그룹에 남으면 탭만 걷고, 마지막 탭일 때만 처분(#328)을 묻고 버퍼를 놓는다. ⌥ 끌기는 복사, editor.splitRight(⌘) 는 활성 탭을 복제해 오른쪽에 연다.
    • ⓒ EditorSplitRatios 가 화면 곁에서 줄마다(칸별 첫 그룹 번호 목록) 비율을 들고, 칸이 더해지면 새 칸 1/n · 원래 칸은 서로의 비율 유지, 빠지면 남은 칸이 비율대로 나눈다.
  • Rationale: 옵션 1 은 Monaco 의 단계 경계를 잃는다 — 150 ms 묶음이 단계가 되어, 쉬지 않고 친 문장이 한 번에 사라지거나 한글 조합 도중 상태가 단계로 남는다. 옵션 2 는 Monaco 자신의 경계를 그대로 옮긴다. 옵션 3 은 탭마다 30~100 MB 다. 같은 문서의 여러 그룹은 VS Code 편집 그룹과 같은 기대이고, 정본이 버퍼 하나라 저장 · dirty · 언어 서버 경로는 그대로다.
  • Impact: 이력을 걸어서 꺼내는 동안 모델 이벤트가 단계 수만큼 다리를 건넌다(상한 300 이유). 줄바꿈이 CRLF 인 파일은 모델이 줄바꿈을 자기 것으로 맞춰 구간 교체가 넓어질 수 있다. 같은 문서가 두 그룹에 있으면 한쪽 Cmd+Z 가 다른 쪽에서 비친 편집도 되돌린다(공유 모델과 같은 동작). Linux(re_editor)는 정본 컨트롤러가 이력을 들고 있어 원래 끊기지 않았다. 창 분리는 여전히 후속(D-084).
  • 실측에서 함께 잡은 것:
    • 분할 회귀(D-084) — 표면마다 「이 표면 밖의 누름이면 키보드를 Flutter 로 돌려준다(releaseNativeFocus)」를 판정해, 한 편집기를 누르면 다른 표면들이 그 누름을 밖으로 보고 방금 받은 키보드를 빼앗았다. 캐럿은 서는데 타이핑이 안 된다(macOS 실측). 판정을 모든 표면의 밖으로 바꾸고 한 표면만 돌려준다.
    • 기호 키 단축키 — 네이티브 메뉴(D-076)는 단축키를 글자로 맞춘다. 한글 2벌식에서 \ · ` 키는 ₩ 를 내 ⌘\ · ⌃가 메뉴에 닿지 않는다.ShellMenuBridge` 가 기호 키 단축키를 ANSI 물리 키 코드로도 맞춘다(로컬 키 모니터 — WebView 가 키보드를 쥐고 있어도 같은 명령으로 간다).
    • ⚠️ 이 개발 머신에서는 1Password 기본 단축키(자동 채우기 ⌘) 가 ⌘\ 를 전역으로 가져가 앱에 오지 않는다(같은 경로의 ⌃` 는 온다). 메뉴 · 탭 끌기로는 된다. 단축키를 바꿀지는 오너 결정으로 남긴다.
  • Source: 오너 지시(2026-09-28) · flutter_monaco 3.4.3 editor-api.js(document.setText = setValue · document.applyEdits = model.applyEdits) · Monaco ITextModel.undo/redo/canUndo/pushEditOperations

D-086: xterm3 를 co-package 포크(6.3.4+cocode.1)로 바꾼다 — 데스크톱 입력기(한글 두벌식) 결함 수정 (D-074 후속, #452)

  • Date: 2026-09-28
  • Context: 제품 터미널(D-074 · xterm3 6.3.4)에서 두벌식으로 친 한글테스트 가 ㅎㅏㄴㄱㅡㄹㅌㅔㅅㅡㅌㅡ 로 셸에 갔다. SoFluffyOS/lumide#64 와 같은 결함이고, 상위 D-067 이 xterm2 에서 「재현」 판정한 경로다. 원인은 두 겹이고 둘 다 xterm3 안에 있다
    • ① terminal_view.dart 의 텍스트 폴백이 event.character 를 바로 쓰고 키를 처리했다고 보고한다. 데스크톱 임베더는 키를 프레임워크에 먼저 주므로 입력기가 키를 보지 못한다
    • ② custom_text_edit.dart 가 확정 직후 버퍼를 비운다. 두벌식은 한 키에서 확정과 다음 초성 조합을 함께 하므로, 그 비움이 늘 조합 중에 도착해 macOS 가 discardMarkedText 로 다음 음절을 끊는다
  • Options: 1. terminal 이 쓰기 세션의 입력기를 소유(xterm3 공개 API 만, 포크 없음) 2. xterm3 를 co-package 에 포크해 결함 위치를 고치고 업스트림에 PR 3. MIT xterm 4.0.0 으로 되돌림(같은 CustomTextEdit 계열이라 ②를 그대로 가진다)
  • Decision: 옵션 2 — 오너 선택(2026-09-28, #452 계획 게이트). package/terminal 은 co-package 태그 xterm3-v6.3.4+cocode.1 을 git 의존으로 쓴다. 업스트림(klc/xterm3)이 수정을 릴리스하면 pub.dev 로 되돌린다
  • Rationale: 결함이 있는 곳을 고친다 — 뷰의 입력 경로를 우리 쪽에 다시 구현하지 않는다. 업스트림 기여로 같은 결함을 가진 다른 사용자도 풀린다. 옵션 1 은 포크가 필요 없어 권장안이었지만 xterm3 의 입력기 층을 그림자 구현으로 대체해야 했다
  • Impact:
    • AGPL 의무는 그대로다(D-074 · D-082). 포크도 AGPL-3.0-or-later 이고, 번들 경계 가드는 계속 ⚠️ 로 드러낸다
    • 고지 생성기는 git 의존을 원칙적으로 판정하지 않는다. 그대로 두면 xterm3 가 강한 copyleft 표와 번들 가드에서 조용히 빠진다. 그래서 IDENTIFIED_GIT_PACKAGES 를 신설했다(이 결정 번호로만 행을 더한다). 고지의 출처 링크는 해석된 커밋의 포크 소스 트리 — 대응 소스 위치다
    • D-082 로 MIT xterm 4.0.0 으로 되돌아가면 이 수정을 그 패키지에 다시 옮겨야 한다
    • AC-16 재측정(#368)은 제품 표면에서 이 포크를 잰다. 옛 재생 하니스는 「키 → 프레임워크 → 입력기」 순서와 엔진의 setEditingState 의미를 재지 못한다 — 포크의 test/_support/macos_text_input.dart 가 그 두 축을 모사한다
  • Source: 오너 선택(2026-09-28) · #452 · coco-de/co-package#14