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

풀스택 코딩 에디터 · E10

UX Spec — 에디터 화면 설계

상위 UX 결정을 전제로 에디터 모듈이 더하는 화면과 결정

목차

파이프라인 4단계(Design) 산출물 · 2026-09-16 · rev.1(UX 리뷰 UR-01~15 · 흐름 순열 Critical E-C1~3 · Important 규칙 반영) · rev.1a(2라운드 UR-16 필수 + UR-17~19 권고 반영) 권위: docs/seed-spec-fullstack-code-editor.md v1.1.1 LOCKED. 파생: docs/prd-fullstack-code-editor.md(FR · U 표) · docs/bdd-fullstack-code-editor.md · docs/architecture-fullstack-code-editor.md(rev.3, ED-01~16) · docs/flow-permutation-fullstack-code-editor.md(FP-E · E-C/E-I/E-N). 동일 단계 입력: 상위 docs/ux-spec-cocode.md(§2.1 골격 · §2.2 진입점 · §2.4 상태바 · §2.5 빈 상태 · §6 CoUI 매핑 · §8 K1~K4 · UX-D-18 I1~I3 · UX-D-31) · docs/coui-inventory-cocode.md §1 · §3 · §4 · cc-coui 스킬(tabs · empty-state · alert · banner · button · menubar · dialog · tooltip · text-field · listing · status · dropdown-menu). 이 문서는 상위 ux-spec 의 결정(UX-D-01~31)을 그대로 전제하고, 에디터 모듈이 더하는 화면과 결정(UX-E-nn)만 적는다.

0. 표기 규칙 (상위 §0.2 승계)

  • [경계] Seed/PRD 문면 · [도출] 상위 규약에서 판정 없이 연역 · [규약] 조직 스킬 · [UX-E-nn] 이 문서가 새로 정하는 결정(§9 전량).
  • 수치는 출처가 있을 때만 — 셸 비율 · 치수는 cocode_density.dart(navigation 0.2 · workbench 0.55 · inspector 0.25 · editor 0.7 · rowHeight 24 · statusBar 28 · toolbar 32 · pointer 44)를 그대로 쓴다. 와이어프레임 안의 파일명 · 건수 · 시각은 전부 예시다. 지연 시간 · 개수 상한 값은 쓰지 않는다.
  • 문구는 승인 주체가 정해질 때까지 «자리표시자» 표기(상위 UX-D-31). 팔레트 라벨 형식도 같다.
  • CoUI 표기는 스킬 표기를 따른다: Alert.info(...) · Alert.destructive(...) · Banner(variant: .destructive) · EmptyState.compact(...) · Button(variant: ghost, shape: square).

1. 설계 원칙 (상위 5개 위에 더하는 2개)

# 원칙 근거
E1 편집은 사람 편집답게 — 저장 · 되돌리기 · 탭 전환에 승인 · 스냅샷 · 잠금이 끼어들지 않는다. 에이전트 작업의 존재는 표시로만 알린다 Seed C-03 · AC-10
E2 실패는 그 자리에서 보인다 — 저장 · 기동 · 서버 · PTY 실패는 발생한 표면(탭 · 세션 · 문제 패널)에 남고 토스트로만 지나가지 않는다 Seed AC-23 · flutter_ide §6-c 교훈

2. 화면 구조 — 상위 §2.1 골격 안에서 채우는 곳

┌──────────────────────────────────────────────────────────────────────────────┐
                    │ File   Edit   View   Delegate   Run   Help                        [Menubar]  │  ← 순서는 상위 §2.1 목업 그대로
                    ├───────────────┬──────────────────────────────────────┬───────────────────────┤
                    │ [탐색][위임][검색] │ lib/a.dart ●  b.dart ×  server.dart ×│ Task Inspector (상위)  │
                    │               ├──────────────────────────────────────┤                       │
                    │ ▾ app/mobile  │ ▲ 작업 T-042 편집 대상 — 저장은 막히지 않습니다  [작업 보기][닫기] │
                    │   ▾ lib       │  1  import 'package:flutter/...';    │                       │
                    │     a.dart ●  │  2                                   │                       │
                    │ ▾ backend/    │  3  class LoginPage extends ... {    │                       │
                    │   ▾ server    │  4    @override   ⚠                  │                       │
                    │     server.d  │                                      │                       │
                    │ ▸ build (숨김)│        [ CocodeEditorView ]            │                       │
                    │               ├──────────────────────────────────────┤                       │
                    │               │ [터미널 ▾2] [문제 3] [검증 로그]  [+ ▾] │                       │
                    │               │ ● Serverpod 풀스택 실행   ○ zsh(0)   │                       │
                    │               │ $ serverpod start                    │                       │
                    ├───────────────┴──────────────────────────────────────┴───────────────────────┤
                    │ unibook │ 실행중 2 · 승인대기 3 · 실패 1 │ 잠금 1 │ Dart LS ● │ 저장됨       │
                    └──────────────────────────────────────────────────────────────────────────────┘
                    
슬롯(CocodePaneId) 이 모듈이 채우는 것 상위와의 관계 AC
navigation 탭 3개 — [탐색](디렉터리 트리) · [위임](상위 복귀 보드) · [검색](신설, §4.3) 상위 §2.1 의 [탐색]``[위임] 에 [검색] 추가 [UX-E-01] AC-01 · AC-06
editorStack 탭 스트립(CoUI TabPane 합성) + CocodeEditorView + 작업 대상 배너(§4.6) + 빈 상태 · 로딩 · 읽기 실패 상위 §2.1 "코드 에디터(탭 스택)" 실체화 AC-01 · 03 · 04 · 07 · 10
terminal(하단) 탭 3개 — [터미널](세션 탭 바 §4.5) · [문제](§4.4) · [검증 로그](상위 위젯 배치만) 상위 §2.1 하단 탭 3종과 동일, [문제] 내용을 이 모듈이 정의 AC-07 · 08 · 09
inspector 상위 그대로 — —
statusBar 상위 4칸 뒤에 Dart LS 상태 칸 · 저장 상태 칸 추가 [UX-E-02] — CocodeStatusBar 는 named 파라미터 4칸 고정 조립(cocode_status_bar.dart:70-99)이라 상위 위젯의 additive 변경(trailingCells: List<Widget>, 기본 빈 목록 + 위젯북 use-case 갱신)이 필요하다 — architecture §2 ui 행에 등재(UR-02) 상위 §2.4 의 4칸은 불변 AC-07 · AC-04
menuBar 상위 §2.1 목업의 메뉴 6개(File Edit View Delegate Run Help) 순서 그대로. 이 모듈은 항목을 더한다(§3.1 명령 표) — Run 메뉴 자체는 상위 목업에 이미 있다(UR-05) flat 1단(상위 §6.5) K1
  • 페인 비율 · 접기 · 리사이즈는 상위 UX-D-02/03 그대로 — 이 모듈은 새 페인을 만들지 않는다 [도출].

3. 명령 표 (정본) · 화면 인벤토리

3.1 1급 동작 명령 표 — K1 커버리지의 정본 [UX-E-03]

CocodePaletteCoverage 테스트는 이 표로 판정한다. 팔레트 라벨은 «자리표시자»(라벨의 첫 단어는 사용자가 떠올릴 동사 — 상위 K1 주석). K4 = Enter 기본 활성화 없음.

명령 id 메뉴 위치 팔레트 라벨(«») K4 출처
editor.save File «저장» — FR-204
editor.saveAll File «모두 저장» — FR-206
editor.closeTab File «탭 닫기» — (dirty 면 S-E02) FR-206
workspace.close File «워크스페이스 닫기» — (S-E02 일괄 변형) FR-206 · E-C1
editor.undo · editor.redo Edit «실행 취소» · «다시 실행» — FR-207
editor.find · editor.replace Edit «찾기» · «바꾸기»(파일 내, 기존 CocodeEditorFindPanel) — 기존
editor.reloadFromDisk · editor.keepMyEdits (배너 버튼) «디스크에서 다시 읽기» · «내 편집 유지» 다시 읽기 K4 FR-205
editor.splitRight View «오른쪽으로 분할» (⌘\) — (활성 탭을 복제해 오른쪽 새 그룹에 연다) D-085
view.navigation · view.search · view.problems · view.terminal View «탐색 보기» · «검색 보기» · «문제 보기» · «터미널 보기» — FR-105 · 106
search.workspace View › 검색 «워크스페이스 검색» — FR-304
search.replaceAll (검색 패널 버튼) «모두 바꾸기» K4 FR-306
search.stop (검색 패널 버튼) «검색 중지» — ED §4.4
run.start(<id>) Run «실행: <이름>» — FR-504
run.stop(<session>) Run · (세션 툴바) «중지: <이름>» K4 FR-505
run.rerun(<session>) (세션 툴바) «다시 실행: <이름>» — UX-E-23
run.edit Run «실행 구성 편집(JSON)» — FR-508 · UX-E-15
terminal.new Run · (세션 탭 바 [+]) «새 터미널» — FR-606
terminal.kill(<session>) (세션 툴바) «터미널 종료» K4 FR-606 · E-C3
terminal.closeTab(<session>) (세션 탭 ×, exited 만) «세션 탭 닫기» — UX-E-20
language.restart (문제 패널 상태 줄) «언어 서버 다시 시작» — FR-406 · ED-10
crossing.showTask(<taskId>) (배너 [작업 보기]) «작업 보기» — FR-701
app.quit File «종료» — (S-E02 일괄 변형 + 프로세스 확인) E-C1

3.2 화면 인벤토리 (신설 · 확장)

ID 화면/표면 상태(전부) 슬롯 신설 위젯(ui)
S-E01 에디터 탭 스트립 + 본문 파일 0(빈 상태) · 읽는 중 · 열림 · dirty · 외부 변경 대기(modified/deleted/moved) · 작업 대상 · 읽기 실패(권한 · 바이너리 U-12) editorStack — (CoUI TabPane 합성 + CocodeEditorView)
S-E02 dirty · 프로세스 처분 다이얼로그 (단일 · 일괄) 단일 탭 · 일괄(워크스페이스 닫기 · 앱 종료) · 저장 진행 중 · 부분 실패 Dialog CocodeDirtyDispositionDialog
S-E03 검색 패널 입력(빈 검색어 = 비활성) · 검색 중 · 결과 · 결과 0 · 정규식 오류 · 치환 중 · 치환 부분 실패 navigation [검색] — (CoUI TextField + Button(ghost, square) + CocodeVirtualList)
S-E04 문제 패널 서버 시작 중 · 실행 중 · 서버 부재 · 서버 종료 · 진단 0 · 진단 목록 · 재시작 실패 terminal [문제] CocodeProblemsList
S-E05 세션 탭 바 + 터미널 세션 0 · PTY 기동 중 · running · exited(code) · PTY 실패 · 즉시 종료 terminal [터미널] CocodeSessionTabBar
S-E06 실행 구성 목록/실행 프로젝트 0 · 목록 · 전제 검사 중 · 전제 미충족 · 디바이스 선택 · JSON 손상 · 실행 중 Run 메뉴 · 팔레트 · [+ ▾] · Dialog — (CoUI Command/Dialog + Listing + DropdownMenu)
S-E07 작업 대상 배너 없음 · 표시 · 닫힘(세션 내 재표시 안 함) · 갱신(작업 종결) editorStack 상단 CocodeTaskFileBanner

4. 표면별 명세

4.1 에디터 탭 스트립 · 본문 (S-E01) — AC-01 · AC-03

  • 탭 = 파일 이름 + dirty 점(●) + 닫기(×) — CoreTabItem 의 label 이 Widget 슬롯이므로 합성(사다리 ③, UR-07). 탭 재정렬은 TabPane 계약(드래그 재정렬 실재). 오버플로는 FadeScroll/OverflowMarquee.
  • 읽는 중: 파일 선택 직후 본문 자리에 CoUI Loading(탭은 즉시 생성). 읽기 실패(권한 없음 · U-12 바이너리 거부): 탭은 열리되 본문 자리에 Alert.destructive(사유 «자리표시자») + 편집 불가, 탭 × 로 닫기 [UX-E-04](E-I2).
  • 활성 탭 재클릭은 디스크를 다시 읽지 않는다(flutter_ide §6-b5 교훈) [도출 AC-03].
  • 활성 탭이 닫힌 뒤 활성 탭 = 직전 활성 탭(MRU), 없으면 왼쪽 이웃 [UX-E-05](E-I6).
  • 탭 닫기: dirty 면 S-E02(단일). 중간 클릭 닫기 · "다른 탭 닫기"는 첫 릴리스 밖 [UX-E-06].
  • 편집 그룹 · 화면 분할(D-084 · D-085): 탭을 끌어 탭 줄에 놓으면 그 자리에 끼운다(좌/우 반쪽이 앞/뒤 — 세로 선으로 미리 보인다, 빈 자리는 끝). 본문 가장자리 25% 띠에 놓으면 그 쪽에 새 그룹이 서고(놓을 반쪽이 미리 칠해진다), 가운데에 놓으면 그 그룹 끝으로 옮긴다. ⌥(Option)를 누른 채 놓으면 복사다 — 떠난 그룹에도 남고, 끄는 탭 그림에 + 가 붙는다. «오른쪽으로 분할»(⌘\)은 활성 탭을 복제해 오른쪽 새 그룹에 연다. 같은 축으로 거듭 나누면 한 줄에 선다(「A | B | C」). 그룹이 비면 걷히고(마지막 그룹은 남는다) 분할이 접힌다.
  • 같은 문서를 여러 그룹에(D-085): 한 그룹 안에서는 한 번이다. 트리 · 검색 · 문제에서 여는 파일은 다른 그룹에 열려 있어도 활성 그룹에 선다(모두 바꾸기는 탭을 불리지 않게 열린 탭을 쓴다). 그룹마다 Monaco 모델이 따로지만 정본은 버퍼 하나이고, 한쪽 편집은 다른 쪽에 실행 취소가 되는 구간 교체로 비친다(커서 · 스크롤 유지). 탭 닫기는 그 문서의 마지막 탭일 때만 처분(S-E02)을 묻는다 — 다른 그룹에 남아 있으면 탭만 걷힌다.
  • 실행 취소 이력은 그룹을 따라간다(D-085): 탭을 다른 그룹으로 옮기거나 복제하면 Monaco 의 실행 취소 · 다시 실행 단계가 새 그룹 편집기로 이어진다(단계 경계는 Monaco 의 것 그대로 — 한글 조합 묶음 포함, 앞뒤 각 300단계까지).
  • 분할선 비율(D-085): 구조가 바뀌어도 남은 칸끼리의 비율을 지킨다 — 칸이 더해지면 새 칸이 1/n, 원래 칸들은 서로의 비율대로 나머지를 나눈다. 칸이 빠지면 남은 칸들이 비율대로 나눈다. 트리 · 검색 · 문제에서 여는 파일과 Cmd+S · Cmd+W 는 활성 그룹으로 가고(탭 줄 · 본문을 누르거나 Monaco 가 포커스를 받으면 그 그룹이 활성), 나뉘어 있을 때 활성 그룹 탭 줄 위에 2 px 강조선이 선다. 활성 탭이 닫히면 같은 그룹 안에서 직전 활성(MRU)을 고른다. 탭을 떼어 별도 창으로 분리하는 것은 후속(D-084 · Flutter 실험 windowing).
  • 빈 상태: 파일 0 → EmptyState.compact(상위 §2.5 ⓐ행과 같은 계약) — 제목 «열린 파일 없음», 동작 [파일 열기…] → 탐색 탭 포커스.
  • 외부 변경 대기(external_change_pending): 탭에 ⟳ 배지 + 본문 상단 인라인 Banner(variant: .warning) («디스크에서 바뀜/삭제됨») — 다이얼로그가 아니라 배너인 이유: I3(조합 중 포커스 이동 금지)와 P1 [UX-E-07]. 선택 [디스크에서 다시 읽기](K4) [내 편집 유지] — CoUI Banner 의 action 슬롯은 하나이므로 두 버튼은 그 슬롯 안에 Row 로 합성한다(UR-18).
  • 저장 실패(HumanWriteFailed): 같은 자리에 Banner(variant: .destructive) («저장 실패: 사유» + [다시 시도]) + 상태바 «저장 실패», 버퍼는 dirty 유지. 토스트를 쓰지 않는다(상위 UX-D-08 — 이 문서의 화면 계약은 Toast 를 쓰지 않는다) [UX-E-37].
  • path_key 정규화 충돌(대소문자 폴딩 볼륨에서 A.dart/a.dart)의 표시 이름은 처음 연 이름을 유지 [UX-E-08](E-I4).

4.2 dirty · 프로세스 처분 다이얼로그 (S-E02) — AC-03 · E-C1 · E-C2

변형 트리거 본문 버튼(순서 고정) K4
단일 dirty 탭 닫기 파일 1개 [저장] [폐기] [취소] [폐기] Enter 없음 · [취소] 가 Esc
일괄 workspace.close · app.quit · 창 닫기 ① dirty 목록(CocodeVirtualList, 부분 선택 없음 [UX-E-09]) ② 실행 중 프로세스 목록(세션 이름 · origin · «종료됩니다») — 둘 중 하나라도 있으면 표시, 둘 다 없으면 다이얼로그 없이 진행 [모두 저장하고 계속] [모두 폐기하고 계속] [취소] 두 "계속" 버튼 모두 Enter 없음(프로세스 종료를 동반하므로)
외부 변경 (배너로 대체, §4.1) — — —
  • [UX-E-18] 종료 · 닫기 시 실행 중 프로세스 처분(E-C1): 일괄 다이얼로그가 실행 중 세션(사용자 셸 · 실행 구성)을 반드시 나열하고, "계속" 선택 시에만 terminate({grace}) 로 전부 종료한 뒤 진행한다. 언어 서버는 앱 소유 프로세스라 목록에 없고 무조건 종료된다(architecture ED-16). 고아 프로세스 0 — 닫기 완료 전에 TerminalSessionManager 가 exited 를 확인한다.
  • [UX-E-19] 일괄 저장 부분 실패(E-C2): [모두 저장하고 계속] 중 한 파일이라도 실패하면 진행하지 않는다 — 다이얼로그가 실패 목록(파일 · 사유)을 남기고 성공한 파일은 clean 으로 바뀐 채 유지된다. 사용자는 [다시 시도] [실패분 폐기하고 계속](K4) [취소] 중 하나를 고른다. Seed 불변식 (2) — 처분 없는 소멸은 없다.
  • 저장 진행 중 상태: 버튼 비활성 + Loading. CoUI Dialog + Button(destructive) 그대로(사다리 ①). 포커스 트랩은 Dialog 에만(K3).
  • 닫기 단계 순서(E-I29): 이 다이얼로그 → 검색 취소 → 언어 서버 종료 → 세션 종료 → 감시 해제 → 버퍼 해제(architecture ED-16). 취소는 다이얼로그 단계에서만 가능하고 그 뒤 단계는 되돌리지 않는다 [UX-E-26].

4.3 검색 패널 (S-E03) — AC-06

  • 입력: 검색어 TextField + 토글 버튼 3개 [Aa](대소문자) [ab](단어 단위) [.*](정규식) — 에디터 찾기(Monaco)와 같은 순서 · 같은 판정 — Button(variant: ghost, shape: square) + pressed 상태(UR-06; CoUI Toggle 은 스위치라 쓰지 않는다) [UX-E-10]. 단어 단위는 D-083 로 범위에 들어왔다 — 경계는 Monaco wordSeparators 판정이라 한글은 단어 글자다.
  • 제외할 파일(D-083): 쉼표로 가른 glob — .gitignore 한 줄 문법(트리 무시 규칙과 같은 해석기 · build/ 는 어느 깊이든 · /gen/ 은 루트 고정 · ! 부정, 단 제외된 디렉터리 아래는 되살리지 않는다). Enter 로 실행한다. 해석되지 않는 glob 은 제외 입력 아래 인라인 오류(검색 미실행, 정규식 오류와 다른 칸). 요약에 «N개 파일 제외». [검색어 지우기] 는 토글 · 제외 glob 을 남긴다. 포함할 파일(include) glob 은 범위 밖.
  • 빈 검색어(공백만 포함)는 실행하지 않는다 — Enter 무시 · 검색 버튼 비활성 [UX-E-11](E-I10).
  • 검색 시작: Enter. 실행 중: Loading + [검색 중지](중지 시 부분 결과 유지). 결과: CocodeVirtualList 행 = 경로:행 — 미리보기(일치 구간 강조), 파일별 그룹 접기. 결과 0: EmptyState.compact(«일치 없음», 동작 [검색어 지우기] — UX-D-31 세 칸 충족, UR-08).
  • 정규식 오류: 입력 필드 아래 인라인 오류(검색 미실행).
  • 결과 선택 규칙 [UX-E-12](UR-10): 화살표 이동 · 단일 클릭 = 에디터에 미리보기 열기(포커스는 패널에 남는다, K2) · Enter · 더블클릭 = 열고 에디터로 포커스 이동. 결과 행의 파일이 그 사이 삭제되면 행에 «삭제됨» 표시 + 열기 시 Alert.destructive(E-I12).
  • 치환: 치환 필드 + [AB](대소문자 보존 — 맞은 자리마다 대소문자 모양을 따른다, VS Code 판정 · D-083) + [모두 바꾸기](K4). 치환 문자열은 글자 그대로다 — 정규식 캡처 참조($1)는 해석하지 않는다. 확인 다이얼로그 없음 — 사람 편집이며 각 버퍼의 실행 취소로 되돌린다 [UX-E-13]. 치환 기준은 버퍼 내용(dirty 버퍼도 버퍼 기준, E-I14) · 저장 부분 실패 시 실패 파일 목록을 패널 상단 Alert.destructive 로, 성공분은 유지 [UX-E-14](E-I15).
  • 한글 조합 중 Enter 는 조합 확정만(I2, 상위 소관).

4.4 문제 패널 (S-E04) — AC-07

  • 상단 상태 줄: «시작 중» / Status(dot) ● «실행 중» / ○ «없음 — 실행 파일을 찾을 수 없음» / ○ «종료됨(코드 N)» + [언어 서버 다시 시작]. 자동 재기동 없음(ED-10). 다시 시작 실패(부재 · 즉시 종료)는 같은 줄에 사유(E-I16).
  • 목록: CocodeProblemsList(CocodeVirtualList 위) 행 = 심각도 아이콘(+ Semantics 라벨) · 파일 · 행:열 · 메시지. 선택 → 에디터 이동(포커스 이동). 필터는 첫 릴리스 밖.
  • 표시 범위 [UX-E-22](E-I18): 언어 서버가 보낸 진단은 파일이 열려 있든 아니든 전부 목록에 표시된다(서버가 정본). 파일을 닫아도 유지되고, 서버가 빈 목록을 보내면 제거된다. 재시작 중 도착한 진단은 새 서버의 것으로 교체된다.
  • 진단 0: EmptyState.compact(«문제 없음», 동작 없음 — 의도적 무동작, UX-E-17).
  • 탭 라벨 배지 [문제 3] = 오류 + 경고 개수(정보 제외) [UX-E-15].
  • 거터: 오류 ⛔ · 경고 ⚠ 표식, hover 툴팁 = 메시지(CoUI Tooltip).
  • Dart 이외 파일: 상태 줄은 워크스페이스 단위 — "이 파일에는 언어 서버가 없다"를 표시하지 않는다(AC-07 후단).

4.5 세션 탭 바 + 터미널 (S-E05) — AC-08 · AC-09

  • CocodeSessionTabBar: 세션 탭 = Status(dot, semanticLabel)(running ● / exited ○ + 종료 코드) + 이름(origin: 실행 구성 이름 또는 셸 이름; 같은 구성의 두 번째 실행은 «이름 #2», UX-E-24) + ×. [+] = 새 터미널(PTY), [+ ▾] = DropdownMenu(실행 구성 목록, UR-07). 탭 순서 = 생성 순, 재정렬 없음 [UX-E-16](UR-13).
  • running 세션의 × 는 비활성이다 — 종료는 툴바 [터미널 종료]/[중지](K4)로만 하고, exited 뒤에 × 로 탭을 닫는다 [UX-E-20](E-C3). 비활성 × 에는 Tooltip + Semantics 라벨 «실행 중 — 먼저 종료하세요» 를 둔다(UR-19). 툴바는 Button(ghost, square) + Divider 클러스터(CoUI 에 Toolbar 컴포넌트 없음, UR-07).
  • 세션 0: EmptyState.compact(«터미널 없음») + [새 터미널]. PTY 기동 중: 탭 생성 + 본문 Loading.
  • 실행 구성 세션: 탭 앞 ▶ + 툴바 [중지](running) / [다시 실행](exited) — 다시 실행 = 이전(exited) 세션 탭을 닫고 같은 자리에 새 세션을 만든다 [UX-E-23](E-I20). 이전 출력이 사라지는 것은 「닫기」의 결과이므로 AC-09 「닫기 전까지 남는다」 문면과 어긋나지 않는다(UR-19). 사용자 셸 세션: [터미널 종료](running) / [닫기](exited). 종료된 세션의 출력은 닫기 전까지 남는다(AC-09).
  • 전제 미충족 · 기동 실패 · PTY 실패 · 셸 즉시 종료: 세션 자리에 Alert.destructive(사유 «자리표시자» + [닫기]) — 세션이 만들어지지 않은 경우(전제 미충족)도 같은 자리에 한 번 표시 [UX-E-21]. 즉시 종료는 exited(code) 로 남고 사유 = 종료 코드(E-I21).
  • scrollback 상한 도달: 표시 없음(오래된 줄 폐기 — AC-09). 값은 kCocodeDefaultScrollbackLines 현행 5000 을 설정으로 노출(architecture §10 U-2).
  • IME: I1 preedit 밑줄 · I2 조합 중 전송 0바이트(기존 CocodeTerminalImeConnection) · I3 포커스 유지. 판정은 상위 cocode/AC-16(분리).

4.6 작업 대상 배너 (S-E07) — AC-10

  • CocodeTaskFileBanner(taskIds, titles): 에디터 본문 상단 1줄 스트립 — 상위 CocodeFailureBanner 와 같은 조립인 CoUI Banner(cocode_failure_banner.dart:87-89 는 Banner(variant: .destructive); 이 배너는 variant: .info)(UR-01). 문구 «작업 T-042 가 이 파일을 편집하는 중입니다 — 저장은 막히지 않습니다» + [작업 보기] + [닫기].
  • 두 작업 이상이 같은 파일에 스냅샷을 남겼으면 전부 나열 [UX-E-25](E-I28). 작업이 종결(완료 · 실패 · 롤백됨 · 거부됨 · 중지됨)되면 배너는 즉시 제거되고, 다른 실행 중 작업이 남아 있으면 그 목록으로 갱신 [UX-E-27](E-I27).
  • 닫으면 그 세션 동안 같은 작업 · 같은 파일에 재표시하지 않는다 [UX-E-28].
  • 트리 배지: 탐색 탭의 파일 행에 작업 배지(CocodeFileTree.badgeBuilder) — 상위 🔒(리스) 배지와 다른 기호 ◐ + Semantics 라벨 [UX-E-29].
  • 저장 시 어떤 경고 · 확인도 띄우지 않는다(E1).

4.7 실행 구성 (S-E06) — AC-08

  • 진입점 3개(상위 §2.2 원칙): Run 메뉴 · 팔레트(«실행: <이름>») · 세션 탭 바 [+ ▾] [UX-E-30].
  • 목록 항목 = 이름 · template_kind 아이콘 · working_dir 상대 경로. 프로젝트 디렉터리 0 → EmptyState.compact(«실행할 프로젝트 없음», 동작 [실행 구성 JSON 열기] — UX-D-31 세 칸, UR-08).
  • 전제 검사 중: 항목에 Loading. 전제 미충족: 실행하지 않고 §4.5 의 Alert 로 사유 + (serverpodStart 4.x 미충족 시) [Serverpod 3.x 서버 실행으로 대신 실행] [UX-E-31].
  • flutterRun 디바이스 미지정 [UX-E-32](E-I23): flutter devices --machine 결과를 CoUI Dialog + Listing 으로 제시 → 선택 시 그 디바이스로 실행 · 취소 시 미실행. 선택은 그 실행에만 쓰이고 저장하지 않는다(저장은 JSON 편집으로). 목록은 화살표 이동 + Enter 선택 · Esc 취소(K2, 첫 항목에 초기 포커스, UR-19).
  • working_dir 소실(E-I24): 실행 거부 + 사유(§4.5 Alert). JSON 손상(E-I25): 목록 상단 Alert.destructive(«실행 구성 파일을 읽을 수 없음») + [JSON 열기] — 기본 템플릿을 덮어쓰거나 재생성하지 않는다(사용자 파일 보호) [UX-E-33].
  • 같은 구성 중복 실행은 허용 — 새 세션(«이름 #2») [UX-E-24](E-I26).
  • 실행 구성 편집은 첫 릴리스에서 JSON 파일 열기(.cocode/editor/run_configurations.json 을 에디터 탭으로) — 전용 폼은 이후 [UX-E-34]. 저장 시 목록을 다시 읽는다(architecture ED-07 두 쓰기 주체 규칙).

5. 사용자 흐름 (PRD §4 흐름 1~6 + 교차 흐름 7)

흐름 화면 순서 오류 · 이탈
1 열린 뒤 앱 셸 S-E01(빈 상태) · 탐색 트리 · S-E05(세션 0) 디렉터리 읽기 실패 → 노드 사유 · 파일 읽기 실패 → S-E01 읽기 실패 · git 부재 → 트리 상단 «git 판정 불가» + 파일 규칙 후퇴(architecture ED-06a)
2 편집 · 저장 · 외부 변경 S-E01 → 저장(상태바 «저장됨»/«저장 실패») → 배너 → S-E02 저장 실패: 본문 상단 Banner(variant: .destructive)(«저장 실패: 사유» + [다시 시도]) + 상태바 «저장 실패» + dirty 유지 — 토스트 없음(상위 UX-D-08) [UX-E-37]
3 검색 · 치환 S-E03 → 결과 → 미리보기/열기 → [모두 바꾸기] 정규식 오류 인라인 · 빈 검색어 비활성 · 부분 실패 Alert
4 실행 구성 S-E06 → (디바이스 선택) → S-E05 세션(▶) → [중지] 전제 미충족 · working_dir 소실 · JSON 손상 · 기동 실패
5 터미널 S-E05 [+] → 기동 중 → PTY 셸 → 다중 탭 → [터미널 종료] → × PTY 실패 · 즉시 종료 Alert
6 사람 편집 × 작업 S-E07 배너 → 저장(차단 없음) → 작업 종결 시 배너 제거 → 상위 롤백 충돌 화면(CocodeRollbackConflictView, D-013 후) —
7 워크스페이스 닫기 · 앱 종료 S-E02 일괄(dirty + 실행 중 프로세스) → 계속/취소 → 닫기 순서(UX-E-26) 부분 저장 실패(UX-E-19) → 진행 취소

6. CoUI 매핑 (사다리 위치)

요소 CoUI 사다리 근거
탭 스트립 · 하단 탭 · 세션 탭 TabPane 아님 — CoreTabItem.label 이 String 이라(coui_core tabs_contract.dart:29) 탭 안에 ●/×/⟳/▶ 위젯을 넣을 수 없다. Button(ghost/secondary, sm) + 닫기 Button(ghost, square) 를 가로 스크롤 목록으로 합성한다 ③ #317 실측 정정
빈 상태 5종 EmptyState.compact ① 상위 §2.5
처분 · 디바이스 선택 다이얼로그 Dialog + Button(destructive) + Listing ① §3
배너(작업 대상 · 외부 변경) Banner(variant: .info / .warning) ① 상위 CocodeFailureBanner 선례(Banner(.destructive))
실패 표시(세션 자리 · 패널 상단 · 읽기 실패 본문) Alert.destructive(...) ① coui-alert(인라인 · 본문 안)
저장 실패 배너 Banner(variant: .destructive) + action: [다시 시도] ① UX-E-37 · 상위 UX-D-08(토스트 없음)
읽는 중 · PTY 기동 중 · 전제 검사 중 · 저장 진행 중 CoUI Loading ① UR-17
검색 입력 · 토글 버튼 · 툴바 클러스터 TextField + Button(variant: ghost, shape: square) + Divider ① coui-inventory §3 "툴바" 행 · UR-06/07
실행 구성 드롭다운 DropdownMenu ① UR-07
세션 · 서버 상태 점 Status(dot, semanticLabel) ① UR-07 · UR-09
결과 · 문제 · dirty 목록 CocodeVirtualList(상위 신설) 기존 IDE 위젯 coui-inventory §4-10
메뉴 · 팔레트 Menubar(flat) · Command ① §3 · 상위 §6.5
상태바 추가 칸 CocodeStatusBar.trailingCells(상위 위젯 additive) 기존 위젯 변경 UR-02
신설 ui 4종 CocodeDirtyDispositionDialog · CocodeProblemsList · CocodeSessionTabBar · CocodeTaskFileBanner 합성(③) CoUI 확장(④) 없음 — 위젯북 use-case 동반

7. 접근성 · 키보드 · IME

  • K1: §3.1 명령 표 전부 팔레트 등재 — CocodePaletteCoverage 테스트의 정본.
  • K2: 포커스 순환에 [검색] 탭과 세션 탭 바를 포함(좌 → 중 → 우 → 하단 → 순환). K3: 트랩은 Dialog · Command 만. K4: §3.1 의 K4 열(search.replaceAll · run.stop · terminal.kill · editor.reloadFromDisk)과 S-E02 의 [폐기] [모두 폐기하고 계속] [실패분 폐기하고 계속] [모두 저장하고 계속](프로세스 종료 동반) [UX-E-35].
  • 키 조합 표: 상위 UX-D-16 대로 정하지 않는다 — Breakdown 이슈(U-11).
  • Semantics · 이중화 [UX-E-36](UR-09): 신설 글리프는 색 + 텍스트/라벨로 이중화한다 — ● dirty → «저장되지 않음», ⟳ → «디스크에서 바뀜», ◐ → «작업 편집 대상», ▶ → «실행 구성 세션», 세션 ●/○ → Status.semanticLabel «실행 중»/«종료됨(코드 N)», 거터 ⛔/⚠ → «오류»/«경고». UI 크롬 대비는 상위 §8.3(4.5:1 / 3:1) · 구문색 대비는 CocodeSyntaxScheme WCAG AA 검사.
  • IME I1~I3: 검색 입력 · 치환 입력 · 팔레트 입력 · 실행 구성 JSON(에디터) · 터미널은 같은 규약. 판정은 상위 cocode/AC-16 재생 프로브에 표면 추가(architecture §6).
  • 히트영역 44dp(CocodeTouchTarget.pointer) · 고정폭 CocodeMonoFont.

8. 빈 상태 표 (상위 §2.5 · UX-D-31 세 칸 형식)

빈 상태 ① 보이는 것 ② 동작 라벨 ③ 여는 화면
ⓔ 열린 파일 0 — editorStack EmptyState.compact «열린 파일 없음» [파일 열기…] 탐색 탭 포커스
ⓕ 세션 0 — 터미널 탭 EmptyState.compact «터미널 없음» [새 터미널] PTY 셸 세션
ⓖ 검색 결과 0 EmptyState.compact «일치 없음» [검색어 지우기] 검색 입력 포커스
ⓗ 진단 0 EmptyState.compact «문제 없음» 없음 — 의도적 무동작 [UX-E-17](진단은 서버가 만든다) —
ⓘ 프로젝트 디렉터리 0 — 실행 구성 EmptyState.compact «실행할 프로젝트 없음» [실행 구성 JSON 열기] S-E01(JSON 탭)

9. 이 문서가 새로 정한 결정 (UX-E 목록)

ID 결정 근거
UX-E-01 navigation 에 [검색] 탭 추가(새 페인 아님) AC-06 · UX-D-03
UX-E-02 상태바에 Dart LS · 저장 상태 칸 — 상위 위젯 additive 변경(trailingCells) AC-07 · AC-04 · UR-02
UX-E-03 1급 동작 명령 표(§3.1)가 메뉴 · 팔레트 · K4 의 정본 K1 · UR-04
UX-E-04 읽는 중 = Loading, 읽기 실패 = 탭 열림 + 본문 Alert.destructive + 편집 불가 UR-03 · E-I2
UX-E-05 활성 탭 닫힘 후 활성 = 직전 활성(MRU), 없으면 왼쪽 E-I6
UX-E-06 중간 클릭 닫기 · 다른 탭 닫기는 첫 릴리스 밖 YAGNI
UX-E-07 외부 변경은 Banner(.warning) 인라인, 다이얼로그 아님 I3 · P1
UX-E-08 path_key 충돌 표시 이름 = 처음 연 이름 E-I4
UX-E-09 일괄 처분에 부분 선택 없음 YAGNI
UX-E-10 검색 토글은 Button(ghost, square) pressed 2개(정규식 · 대소문자) AC-06 · UR-06
UX-E-11 빈 검색어는 실행하지 않음 E-I10
UX-E-12 결과 선택: 화살표/클릭 = 미리보기(포커스 유지), Enter/더블클릭 = 열고 포커스 이동; 소실 파일 «삭제됨» UR-10 · E-I12
UX-E-13 모두 바꾸기 확인 다이얼로그 없음 — undo 로 되돌림 C-03
UX-E-14 치환 기준 = 버퍼 내용 · 부분 실패 Alert + 성공분 유지 E-I14 · E-I15
UX-E-15 문제 탭 배지 = 오류 + 경고 판정 없는 규칙
UX-E-16 세션 탭 순서 = 생성 순, 재정렬 없음 UR-13
UX-E-17 진단 0 빈 상태는 의도적 무동작 UX-D-31 · UR-08
UX-E-18 종료 · 닫기 시 실행 중 프로세스를 나열하고 "계속" 에서만 전부 종료 — 고아 0 E-C1 · K4
UX-E-19 일괄 저장 부분 실패 시 진행하지 않고 실패 목록 제시(다시 시도 · 실패분 폐기 · 취소) E-C2 · Seed 불변식 (2)
UX-E-20 running 세션 × 비활성 — 종료는 툴바 K4 버튼으로만 E-C3 · K4
UX-E-21 전제 미충족 · 기동 · PTY · 즉시 종료 실패는 세션 자리 Alert.destructive AC-23 · E2 · E-I21
UX-E-22 진단은 열림 여부와 무관하게 전부 표시, 서버 빈 목록으로만 제거, 재시작 시 교체 E-I18 · E-N17
UX-E-23 다시 실행 = 이전 세션 탭 닫기 + 같은 자리에 새 세션(AC-09 문면 유지) E-I20 · UR-19
UX-E-24 같은 구성 중복 실행 허용(«이름 #2») E-I26
UX-E-25 배너는 실행 중 작업 전부 나열 E-I28
UX-E-26 닫기 단계 순서 고정, 취소는 다이얼로그 단계에서만 E-I29 · ED-16
UX-E-27 작업 종결 시 배너 즉시 제거/갱신 E-I27
UX-E-28 배너 닫기 후 세션 내 재표시 없음 피로 방지
UX-E-29 트리 작업 배지 ◐ + Semantics — 리스 🔒 와 구분 CH-001 · UR-09
UX-E-30 실행 구성 진입점 3개 상위 §2.2
UX-E-31 serverpodStart 4.x 미충족 시 3.x 대안 버튼 PRD §2.5
UX-E-32 flutterRun 디바이스 미지정 → 선택 다이얼로그, 선택은 저장하지 않음 E-I23
UX-E-33 working_dir 소실 → 거부 + 사유 · JSON 손상 → Alert + JSON 열기, 재생성 없음 E-I24 · E-I25
UX-E-34 실행 구성 편집 = JSON 파일 열기(전용 폼은 이후) YAGNI · U-7
UX-E-35 K4 대상 목록(§3.1 K4 열 + S-E02 버튼 4종) 상위 K4
UX-E-36 신설 글리프 전부 색 + 라벨 이중화 · Semantics 라벨 표 상위 §8.3 · UR-09
UX-E-37 저장 실패 = 본문 상단 Banner(.destructive) + 상태바 «저장 실패», 토스트 없음 상위 UX-D-08 · UR-16

10. 값이 없어 정하지 않은 것

문구 전부(«자리표시자») · 키 조합 표(U-11) · scrollback 기본값 근거(U-2) · 아이콘 세트(파일 타입 아이콘은 상위 규약의 raw Color 예외 범주 — Design 후속) · terminate grace 값(architecture §10).

11. Seed · PRD · 상위 ux-spec 과의 어긋남 / 공백 (숨기지 않고 적는다)

# 어긋남 처리
1 rev.0 이 Run 메뉴를 "신설"이라 적고 목업 순서를 상위(File Edit View Delegate Run Help)와 다르게 그렸다 rev.1: 상위 순서로 정정, 신설은 항목뿐(§2 · §3.1)
2 rev.0 §8 ⓖⓗⓘ 의 ② "없음" 은 UX-D-31 세 칸 계약과 어긋났다 ⓖ · ⓘ 에 동작 부여, ⓗ 는 의도적 무동작으로 등재(UX-E-17)
3 rev.0 이 CocodeFailureBanner 를 Alert(info) 조립이라 오인용 — 실제는 Banner(.destructive) 배너 2종을 Banner 로 정정(§4.1 · §4.6 · §6)
4 상태바 추가 칸은 상위 소유 위젯의 API 변경이 필요하다(rev.0 미기재) UX-E-02 + architecture §2 등재
5 PRD FR-406 에 언어 서버 수동 재기동 조항이 없다(ED-10 · §4.4 [언어 서버 다시 시작]) PRD FR-406 갱신(Design 반영 v1.1)
6 rev.1 §5 흐름 2 가 저장 실패에 「토스트」를 적어 상위 UX-D-08(Toast 없음)과 충돌했다 rev.1a: Banner(.destructive) + 상태바로 정정(UX-E-37)
7 rev.1a 까지 「탭 label 은 Widget 슬롯(사다리 ③)」이라 적었으나 실측은 String 이다 — TabPane 으로는 탭별 dirty 점 · 닫기 버튼을 그릴 수 없다(상위 coui-inventory-cocode.md:77 이 그 자리 후보로 든 것도 같은 한계를 갖는다) #317 에서 확인하고 §6 을 정정 — 스트립은 Button 합성. Alert 의 title·description 이 String(EmptyState 는 Widget)인 것도 같이 확인했다

Generated by cc-product Design stage (UX Designer) · 2026-09-16 · rev.1a