feat(desktop): 폴더 없는 상태의 새 문서 길과 최근 파일 - #22
Merged
Conversation
Bori-github
force-pushed
the
feat/m9-no-folder
branch
from
August 5, 2026 02:55
2f09968 to
39f3a46
Compare
recent_files는 tabs[].path와 같은 규칙으로 걸러진다 — 파일만, 존재하는 경로만, 읽을 때 파일 단위 허용 루트로 등록. 필드가 없는 옛 session.json은 빈 목록으로 읽어 기존 세션을 잃지 않는다. 갱신 시점·상한 정책은 document-model.md#최근-파일이 소유한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
show_save_dialog에 start_dir를 추가한다. 존재하는 디렉터리면 다이얼로그 시작 위치로 쓰고, 아니면 OS 기본(마지막 사용 위치)이다. 웹뷰는 사이드바 rootDir를 넘기고, 폴더를 열지 않았으면 null이다. 시작 위치는 허용 루트 등록과 무관하다 — 등록은 사용자가 선택한 경로로만 한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
workspace 스토어에 recentFiles를 둔다 — 맨 앞 삽입, 중복은 앞으로 이동, 상한 10. 파일 열기(새 탭·기존 탭 합류)와 저장 다이얼로그의 새 경로 확정이 canonical 경로를 올린다. 세션 복원은 저장된 목록을 그대로 세우고 순서를 바꾸지 않는다. 표시(빈 상태 안내)는 다음 커밋이 맡는다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
document-store의 fileNameOf와 sidebar의 folderNameOf는 같은 구현이었다. shared/lib(path-segments)로 옮겨 탭 제목·사이드바 헤더·최근 파일 목록이 같은 규칙으로 이름을 만들게 한다. 백로그의 해당 항목을 지운다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
폴더를 열지 않고 앱을 켰을 때 이 화면에서 문서 작업을 시작한다. 폴더 열기 · 파일 열기 · 새 문서 버튼을 이 순서로 두고, 새 문서 버튼은 Cmd+N과 같은 Untitled 길을 탄다. 최근 파일 목록은 파일명으로 표시하고 누르면 그 파일을 연다 — 비면 표시하지 않는다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
빈 상태 행동 버튼 셋의 존재·순서, 새 문서 버튼의 Untitled 길(활성 탭 경로 없음), 닫은 파일이 최근 파일 목록에 남고 눌러 다시 열리는 흐름, 세션 파일의 recentFiles 기록을 실앱에서 검증한다(21/21 통과). 파일 열기·폴더 열기 버튼 클릭은 네이티브 다이얼로그라 수동 검증 대상이다. 구현 계획의 M9 상태와 문서 링크를 갱신한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bori-github
force-pushed
the
feat/m9-no-folder
branch
from
August 5, 2026 03:10
39f3a46 to
4f201a7
Compare
폴더 열기는 accent 버튼 하나로 남기고, 파일 열기·새 문서는 테두리 없는 ghost 행으로 바꾼다 — 이름은 왼쪽, 단축키는 오른쪽의 고정폭 칩이다. 최근 파일 행도 같은 ghost 행이고 카드 폭을 60에서 80으로 넓힌다. 모양을 서술하던 주석은 지우고 기능 제약(투명 창 배경)만 남긴다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
단축키를 악센트 배경의 키캡으로 그린다 — 아랫변 테두리만 2px라 눌린 깊이가 생기고, 한 글자가 키캡 하나다(문자열은 STRINGS 그대로, 렌더에서 나눈다). 키마다 최소 폭을 줘 글자가 달라도 크기가 균일하다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
사이드바를 파일 트리·최근 파일·설정 영역으로 나눈다. 최근 파일 영역은 헤더로 접고 펼치며, 접으면 헤더만 하단(설정 위)에 남고, 목록이 비면 표시하지 않는다. 목록은 Tab 정지점 하나이고 방향키·Home/End로 항목을 옮긴다(트리와 같은 규칙). 항목 버튼의 포커스 링이 스크롤 면 가장자리에서 잘리지 않게 목록 안쪽 여백을 링보다 넓게 둔다. 빈 상태 화면의 목록은 제거한다 — 표시 자리는 사이드바 한 곳이다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
isDisplayed()가 긴 시퀀스 중 보이는 요소에 false를 주는 간헐 어긋남을 실측했다(DOM은 정상, 창 최상위·포커스·숨김 상태와 무관). 설정 창의 표시 단언을 DOM(hidden)으로 바꾼다. 세션 복원의 리로드 대기는 리로드 전 화면이 같은 단언을 만족해 그대로 통과하던 것을, 리로드 직전 표식을 심고 표식 없는 새 페이지에서 훅 존재까지 확인하는 방식으로 바꾼다. 두 한계는 testing.md#성숙도-주의가 소유한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
버튼·탭의 배경/테두리/글자색에 fast·out 전환을 걸고, 동작 줄이기에서는 전환을 끈다. 적용된 모션의 종류·공통 규칙·예외는 decisions/motion.md가 소유하고 DESIGN.md가 링크한다. "장식적 모션을 두지 않는다"는 "상태 변화를 설명하는 것만 둔다"와 같은 말이라 삭제한다. 탭 호버 테스트는 전환 도입으로 즉시 단언이 깨져 완료 대기로 바꾼다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
사이드바(폭)·최근 파일·트리 폴더(grid 행)를 언마운트 없이 크기 전환으로 여닫는다. 접힌 쪽은 숨김 상태(aria-hidden·visibility)로 두고 키보드 탐색·Tab 정지점에서 뺀다. 트리 폴더의 첫 펼침은 접힘으로 마운트한 뒤 타이머로 펼친다(예외 근거는 decisions/motion.md). document-model.md에 접힌 children이 DOM에 남는 렌더 범위를 기록한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
닫힘은 dialogOut 전환이 끝난 뒤 close()·언마운트하고, animationend 유실은 타이머 상한으로 막는다. 동작 줄이기에서는 전환 없이 즉시 닫는다. 확인 다이얼로그는 닫힘 전환 동안 마지막 내용을 유지한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
browser.keys()의 확정 Enter가 세션의 활성 요소로 가서 간헐 실패하던 것을 대상 요소에 합성 KeyboardEvent를 디스패치하는 방식으로 바꾼다. 렌더링되지 않는 창에서 CSS 전환이 진행되지 않아 완료 대기가 간헐 실패하던 것은 전환을 끄고 최종 상태(폭·테두리 0)를 즉시 단언한다. 두 실측 한계를 testing.md 성숙도 주의에 기록하고, 사이드바 접힘·다이얼로그 닫힘 대기를 시나리오에 추가한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
데모 녹화가 기계 속도라 사람이 볼 수 없어 NORII_E2E_SLOWMO_MS로 동작(클릭·입력·훅 트리거) 앞에서만 쉬게 한다 — 대기 폴링에는 걸지 않아 일반 실행은 그대로다. 최근 파일 헤더 접기는 새 표면인데 실앱 E2E 커버가 없어 기존 최근 파일 시나리오에 단계를 더한다. 전체 스위트 일반 속도 21/21 통과. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
수동 대조 시점이 "마일스톤 종료 시"로만 있어 PR 생성 때 수행하는지가 규정에 없었다. 모든 PR 전에 대조하고 어긋남 수정을 그 PR에 담는 것으로 정하고, /pr 커맨드 절차에 단계를 추가한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR 전 수동 대조에서 확인한 불일치를 문서 쪽에서 수정한다(코드 변경 없음). - rust-commands: 세션 커맨드 2개를 async fn으로, 미구현 plugin-window-state 행 제거 - document-model: 세션 저장 파일 표에서 창 크기·위치 행 제거(도입 여부는 열린 결정으로), 재펼침은 캐시 사용, WorkspaceState를 실제 형태로(expandedDirs 필드, 탭은 별도 스토어), 최근 파일 추가 시점에 사이드바 새 파일 만들기 경로 추가 - editor-strategy: 단축키 표에 3행 추가(탭 Delete/Backspace 닫기 · 탭바 방향키 전환 · 전체 접기/펼치기), 확인 다이얼로그 중 전역 단축키 정지 명시 - preview-strategy: 뷰 모드는 설정 파일에 저장·복원(열린 결정 문구 삭제), 복사 버튼 노출 조건에 키보드 포커스·복사됨 상태 포함 - window-chrome: 36px의 _glass 한정 삭제, docs-drift 상수 대조와 모순되는 "자동 검사는 이것뿐" 삭제, 렌더 검증은 Vitest Browser Mode로 가능함을 반영, 부팅 ①②는 Promise.all - file-lifecycle: 충돌 UI를 배너로 통일, 상태 다이어그램에서 코드에 없는 error 상태 제거, 접힘 상태는 저장하지 않음을 명시 - mise verify-native 설명에 한글 조합 확정 입력 포함 데이터를 물건처럼 쓰는 표현(버린다·올린다·되살린다·저장 자리)도 동작 서술로 고치고, 문서를 인용하는 주석 2곳(open-file·E2E 집행)을 같이 맞춘다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rfd가 set_directory와 set_file_name을 함께 받으면 시작 위치를 시작폴더/파일명으로 합쳐 존재하지 않는 경로로 만들어 무효화한다 — 그래서 폴더를 연 상태에서 저장 다이얼로그가 열린 폴더가 아니라 OS 마지막 위치에서 시작했다. 시작 위치를 줄 때는 파일명을 붙이지 않아 다이얼로그가 열린 폴더에서 시작하게 한다. 트레이드오프: start_dir이 있으면 파일명 기본값(default_name)이 제안되지 않는다. rfd가 둘을 동시에 받지 못하므로 시작 위치를 택했다. 근본 원인은 rfd에 있어 업스트림 수정 시 우회를 걷어낸다. 네이티브 저장 다이얼로그라 자동 검증이 불가해 실앱에서 수동 확인했다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약 / 의도
폴더를 열지 않고 앱을 켜도 새 문서 작성과 파일 열기가 되게 한다. 빈 상태(탭 0개) 화면에 버튼(폴더 열기 · 파일 열기 · 새 문서)을 두고, 문서 모델이 규정만 하고 구현이 없던
recentFiles를 세션에 실어 저장·복원해 사이드바에서 다시 열게 한다.범위
이번에 한 것
Session.recent_files— 세션 파일에 최근 파일 목록 저장, 부팅 시 파일 단위 허용 루트 등록(탭 경로와 동일 규칙)show_save_dialog에start_dir— 폴더가 열려 있으면 저장 다이얼로그가 그 루트에서 시작. macOS에서 rfd가 시작 위치와 파일명을 동시에 못 받아 무효화되던 것을 우회(아래 트러블슈팅)recentFiles상태 — 파일 열기·저장 다이얼로그의 새 경로 확정이 canonical 경로를 올림(맨 앞 삽입·중복 이동·상한 10), 세션 복원은 목록 불변decisions/motion.md(신설)가 소유KeyboardEvent디스패치, 전환 완료 대기는 전환을 끄고 최종 상태 단언(실측 한계 2건을 testing.md에 기록)NORII_E2E_SLOWMO_MS가 동작(클릭·입력·훅 트리거) 앞에서만 쉬어 데모 영상을 사람이 볼 속도로 녹화한다(대기 폴링 제외, 일반 실행 불변). 최근 파일 헤더 접기 단계도 시나리오에 추가shared/libentryNameOf로 통합(백로그 소화 — 셋째 사본 방지)의도적으로 안 한 것 / 후속
load_session이 걸러냄. 세션 중 stale 항목 클릭은 기존 열기 실패 안내를 탐주요 변경점
session.rs에recent_files(serde 기본값 — 필드 없는 옛 세션도 읽힘) ·dialog_commands.rs에 시작 디렉터리(존재하는 디렉터리일 때만)recentFiles+noteRecentFile/setRecentFiles· open-file/save-tab에서 갱신 · session-storage 스냅숏·복원 연결 ·widgets/editor-pane/ui/empty-state.tsx신설 ·widgets/sidebar에recent-files-section신설(+recent-section-store), 사이드바·트리에 접기/펼치기 전환 ·shared/uidialog에 닫힘 전환 시퀀스설계 · 결정
session.json이 구조상 강제 — 재시작 뒤 다이얼로그 없이 다시 열려면 부팅 시 허용 루트 등록이 필요하고 그 유일한 통로가load_session이다. 새 문서 버튼은 Cmd+N과 같은 길(계약 변경 없음), 폴더 열기를 첫 자리에 둔다(강제하지 않음)검증 (체크가 아니라 값으로)
mise run check: 통과 (프론트 537 테스트 · Rust 83 테스트 · docs-drift 포함)recent_files필터·옛 세션 호환, 스토어 삽입·상한, 열기/저장 갱신, 복원 불변, 사이드바 접기·최근 파일 영역·방향키 탐색을 테스트로 먼저 고정6a82382상태에서 전체 실행). 저장 다이얼로그 시작 위치·포커스 링은 네이티브라 실앱 수동 확인(아래 트러블슈팅·스크린샷)관련 문서 (단일 출처)
.claude/docs/document-model.md— #최근-파일(신설) · #빈-탭--탭바는-비지-않는다(확장) · 파일 트리 렌더 범위.claude/docs/rust-commands.md— Session 구조체 · 경로 종류 표 · show_save_dialog 시그니처.claude/docs/design/decisions/motion.md— 적용된 모션의 종류·공통 규칙·예외(신설).claude/docs/testing.md— #성숙도-주의(E2E 실측 한계 추가).claude/docs/implementation-plan.md— M9 완료 · 백로그 1건 소화리뷰 포인트 / 위험 지점
session.rs의recent_files허용 등록 — 파일 단위라 스코프 확대는 없으나 보안 관점 확인session.json호환(serde 기본값) — 필드 없음 → 빈 목록, 세션 유실 없음(테스트로 고정)animationend에 걸려 있다 — 유실 대비 타이머 폴백 포함examples/)는 대상 아님 —.md파일 하나로 시연되지 않는 앱 상태 표면(빈 상태·세션·사이드바)트러블슈팅 — 저장 다이얼로그 시작 위치 (rfd 우회)
rust-commands.md의show_save_dialog·start_dir)과 어긋난다.set_directory·set_file_name을 표준대로 넘긴다. rfd 0.16.0의build_save_file이starting_directory와file_name을 함께set_path로 넘기고,set_path가 시작 폴더에 파일명을 이어붙여(/폴더/파일명) 존재하지 않는 경로를setDirectoryURL(isDirectory:true)로 넣어 NSSavePanel이 무시한다. rfd master에도 같은 코드가 있어 버전 상향으로는 해결되지 않는다.start_dir이 있으면set_file_name을 생략해 rfd가 파일명을 붙이지 못하게 한다.start_dir이 있으면 파일명 기본값(default_name)이 제안되지 않는다. rfd가 둘을 동시에 받지 못해 시작 위치를 택했다. 근본 원인은 rfd에 있어 업스트림 수정 시 우회를 걷어낸다.체크리스트 (프로젝트 규칙)
.claude/rules/non-goals.md)를 넘지 않음스크린샷 / 데모
E2E 시나리오를 그대로 녹화한 것이다(동작마다 1초 감속).
빈 상태 — 버튼이 뜨고 새 문서 버튼이 Untitled 탭을 연다
norii-demo-empty-state.mov
최근 파일 — 닫은 파일이 사이드바 목록에 남고 누르면 다시 열리고, 헤더로 접힌다
norii-demo-recent-files.mov
사이드바 — 폴더 트리 펼침·파일 열기, 접기 토글, 만들기·이름 변경·삭제
norii-demo-sidebar.mov
세션 복원 — 다시 띄우면 열려 있던 탭이 돌아온다
norii-demo-session.mov
마일스톤 / 비고
M9. 폴더 없는 상태 — 계획 표의 산출물(폴더 없이 새 문서를 만들어 저장까지, 최근에 연 파일을 그 자리에서 다시 열기)을 충족. 설계 방향(파일 우선 유지)은 사전 논의로 확정.
🤖 Generated with Claude Code