Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
287b262
feat(rust): 세션에 최근 파일 목록을 저장한다
Bori-github Aug 3, 2026
2f76f03
feat(ipc): 저장 다이얼로그가 열린 폴더 루트에서 시작한다
Bori-github Aug 3, 2026
8445fa7
feat(shared): 최근 파일 목록을 세션과 함께 저장·복원한다
Bori-github Aug 3, 2026
88d8875
refactor(shared): 경로 이름 헬퍼 2벌을 entryNameOf 하나로 통합한다
Bori-github Aug 3, 2026
600d93e
feat(editor): 빈 상태를 버튼과 최근 파일 목록으로 채운다
Bori-github Aug 3, 2026
4f201a7
test(desktop): 빈 상태 버튼·최근 파일의 실앱 E2E를 추가한다 (M9 완료)
Bori-github Aug 3, 2026
2169474
feat(editor): 빈 상태 버튼과 단축키 표기를 다듬는다
Bori-github Aug 6, 2026
7ce3856
feat(editor): 빈 상태 단축키를 키별 키캡으로 표기한다
Bori-github Aug 6, 2026
54cd9d7
feat(sidebar): 최근 파일을 사이드바 영역으로 옮긴다
Bori-github Aug 6, 2026
20aed85
test(desktop): 실앱 E2E의 표시 단언과 리로드 대기를 안정화한다
Bori-github Aug 6, 2026
27d5d03
feat(ds): 상태 색 전환을 적용하고 모션 결정 기록을 추가한다
Bori-github Aug 8, 2026
c8f30cf
feat(sidebar): 접기·펼치기에 크기 전환을 적용한다
Bori-github Aug 8, 2026
001b5ce
feat(ds): 다이얼로그에 진입·퇴장 모션을 적용한다
Bori-github Aug 8, 2026
1e30c36
test(desktop): 실앱 E2E의 확정 키와 전환 대기를 안정화한다
Bori-github Aug 8, 2026
6a82382
test(desktop): 실앱 E2E에 데모 감속과 최근 파일 접기 단계를 추가한다
Bori-github Aug 8, 2026
6c3cbc7
docs: 계약 문서 수동 대조를 PR 올리기 전 절차로 명시한다
Bori-github Aug 8, 2026
9e88ca3
docs: 계약 문서를 구현과 대조해 어긋난 서술을 고친다
Bori-github Aug 8, 2026
8c1a9b3
fix(rust): 저장 다이얼로그가 열린 폴더에서 시작하도록 rfd 제약을 우회한다
Bori-github Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions .claude/commands/pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,27 @@ argument-hint: "[base 브랜치 — 생략 시 main]"
- `git log origin/<base>..HEAD --oneline` — 커밋들이 곧 의도다
- `git diff origin/<base>..HEAD --stat` — 변경 파일
- 커밋 본문과 `.claude` 문서 변경에서 결정·이탈을 읽는다
3. **본문 작성** — `.github/pull_request_template.md`를 읽어 각 섹션을 diff·커밋을 근거로 채운다.
3. **계약 문서 수동 대조** — 기계 대조 밖 계약을 코드와 대조한다. 대상·수정 처리는 [작업 규칙](../rules/project-rules.md)을 따른다.
4. **본문 작성** — `.github/pull_request_template.md`를 읽어 각 섹션을 diff·커밋을 근거로 채운다.
- 범위의 "의도적으로 안 한 것/후속"과 검증(값)은 **반드시** 채운다 — diff에 안 담기는 정보다
- 확실치 않으면 추측하지 말고 사용자에게 묻는다
- diff·커밋·`.claude` 문서에서 읽을 수 있는 것은 중복하지 않는다(의도·제외·검증 결과·이탈 위주)
4. **다이어그램(mermaid)** — 다음 중 하나가 변경에 포함되면 본문에 넣는다. 없으면 넣지 않는다(그림을 위한 그림은 노이즈다).
5. **다이어그램(mermaid)** — 다음 중 하나가 변경에 포함되면 본문에 넣는다. 없으면 넣지 않는다(그림을 위한 그림은 노이즈다).
- **여러 층을 가로지르는 흐름** — 프론트·IPC·Rust를 관통하는 경로(예: 저장 파이프라인) → `sequenceDiagram`
- **상태 기계** — 전이가 3개 이상이고 잘못된 전이가 버그가 되는 것(예: 탭 dirty·충돌) → `stateDiagram-v2`
- **결정론적 분기 파이프라인** — 순서가 규칙인 것(예: 열기 인코딩 판정) → `flowchart`
- **원칙**: 계약 문서에 이미 그림이 있으면 **링크**하고, PR에는 리뷰어가 diff 전에 방향을 잡아야 하는 **핵심 1개만 인라인**한다. 다이어그램의 단일 출처는 계약 문서다(→ [작업 규칙](../rules/project-rules.md)).
- **넣지 않는 것**: 레이어 구조도(설계 문서에 이미 있음)·파일 나열형 그림 — diff와 중복이다.
5. **제목** — 지배적 변경으로 type 결정. 형식·subject-case 등은 [커밋 컨벤션](../rules/commit-convention.md)을 따른다.
6. **라벨** — 변경 유형에 맞는 라벨을 고른다(라벨 세트는 [커밋 컨벤션](../rules/commit-convention.md)).
7. **검증 상태** — `mise run check` 결과를 본문에 값으로 적는다(못 돌리면 그 이유). 실앱 E2E·번들 크기는 mac 실기 항목이라 통과/미검증 상태만 표기한다.
8. **데모 영상 (사용자에게 보이는 동작이 바뀐 PR이면 첨부)** — E2E 화면을 그대로 녹화해 붙인다. UI·동작 변경이 없는 PR(문서·리팩터링·내부 정리)은 건너뛴다.
6. **제목** — 지배적 변경으로 type 결정. 형식·subject-case 등은 [커밋 컨벤션](../rules/commit-convention.md)을 따른다.
7. **라벨** — 변경 유형에 맞는 라벨을 고른다(라벨 세트는 [커밋 컨벤션](../rules/commit-convention.md)).
8. **검증 상태** — `mise run check` 결과를 본문에 값으로 적는다(못 돌리면 그 이유). 실앱 E2E·번들 크기는 mac 실기 항목이라 통과/미검증 상태만 표기한다.
9. **데모 영상 (사용자에게 보이는 동작이 바뀐 PR이면 첨부)** — E2E 화면을 그대로 녹화해 붙인다. UI·동작 변경이 없는 PR(문서·리팩터링·내부 정리)은 건너뛴다.
- 앱 실행: `mise run dev-webdriver` (다른 터미널, 백그라운드)
- 녹화: `mise run demo` — E2E 시나리오를 실행하며 앱 창을 녹화한다(→ `scripts/record-demo.sh`)
- 업로드: `mise run upload-demo <파일>` — GitHub CDN URL을 받는다(리포에 바이너리를 커밋하지 않는다, → `scripts/upload-attachment.sh`)
- 받은 URL을 PR 본문의 "스크린샷 / 데모" 섹션에 그대로 넣는다(영상은 URL만 두면 GitHub이 플레이어로 렌더한다)
- 첫 실행에서 GitHub 로그인이 없다고 나오면 스크립트가 안내하는 한 줄을 실행해 한 번만 로그인한다 — 이후는 프로필에 저장돼 자동이다
9. **확인 후 생성** — 초안(제목·본문·라벨·담당자·데모 영상)을 사용자에게 보여주고 **승인받은 뒤에만** 실행한다:
10. **확인 후 생성** — 초안(제목·본문·라벨·담당자·데모 영상)을 사용자에게 보여주고 **승인받은 뒤에만** 실행한다:
- 브랜치가 원격에 없으면 먼저 `git push -u origin <branch>` (승인 후)
- `gh pr create --base <base> --title "…" --body "…" --assignee @me` 후 `gh pr edit --add-label <label>`
- 담당자는 `@me`(현재 인증 사용자)로 자동 배정한다 — 계정명을 하드코딩하지 않는다.
Expand Down
28 changes: 28 additions & 0 deletions .claude/docs/design/decisions/motion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 모션 — 상태 변화를 설명하는 전환만 둔다

## 지금의 결정

적용된 모션과 방식:

- **상태의 색 전환** — 호버·눌림·선택에서 배경·테두리·글자색을 전환한다.
- **접기/펼치기의 크기 전환** — 사이드바는 폭, 최근 파일 목록·트리 하위는 grid 행 높이를 전환한다.
- **접기 셰브론 회전** — 펼침 상태에서 90° 회전한다.
- **다이얼로그 진입·퇴장** — `dialogIn`·`dialogOut` 키프레임. 값은 `panda.config.ts`가 소유한다.

공통 규칙:

- 지속시간·이징은 토큰 `fast`·`out`만 쓴다.
- **모든 모션은 `_motionReduce`로 끈다** — OS의 "동작 줄이기"에서는 전환 없이 즉시 상태를 보인다. 누락을 잡는 자동 검사는 없다 — 추가할 때 직접 확인한다.
- 상호작용이 트리거일 때만 돈다 — 시간·스크롤에 반응해 스스로 도는 모션은 없다(idle 비용 0).
- 흐림(backdrop-filter)·그림자는 전환하지 않는다(→ [작업 규칙 · 성능](../../../rules/project-rules.md#성능-규칙)).

## 예외 — 그대로 구현하면 깨지는 지점

- **접기/펼치기는 언마운트하지 않는다.** 언마운트하면 전환할 요소가 사라지고, 다시 펼칠 때 트리·목록 상태가 초기화된다. 크기를 0으로 두고 `visibility: hidden`·`aria-hidden`으로 포커스·스크린리더에서 뺀다. 다이얼로그 퇴장도 같다 — 전환이 끝난 뒤 `close()`와 언마운트를 한다.
- **안쪽 여백은 접힘과 함께 0으로 전환한다** — 여백은 상자의 최소 높이로 남아, 두면 접힌 뒤에도 그만큼 빈 자리가 남는다.
- **lazy로 읽힌 하위의 첫 펼침은 접힘으로 붙인 뒤 타이머로 펼친다** — 마운트와 펼침이 같은 프레임이면 전환이 시작점 없이 끝 상태로 그려진다. `requestAnimationFrame`이 아니라 타이머인 이유는 [테스트 전략 — 성숙도 주의](../../testing.md#성숙도-주의)가 소유한다.

## 바뀐 것

| 언제 | 무엇이 | 왜 |
| ---------- | ------------------------ | ------- |
12 changes: 6 additions & 6 deletions .claude/docs/design/window-chrome.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ TITLEBAR_CUTOUT_WIDTH = 32 그 영역의 폭(px). 높이는 띠 전체다.

그 띠에서 눌린 마우스는 웹뷰를 거치지 않고 AppKit이 직접 처리하므로(`isMovableByWindowBackground`), IPC를 타지 않아 tauri#9503에 걸리지 않는다 — 네이티브 앱과 같은 경로다.

**웹 쪽은 이 띠를 타이틀 스트립이 채운다.** `title-strip` 위젯이 창 최상단 full-width로 놓이고, `_glass`에서 높이 `36px`로 이 띠와 겹친다. 그 아래에 사이드바·탭바·본문이 온다. 스트립 높이와 위 상수는 같아야 하고, 위 상수가 단일 출처다(docs-drift가 대조).
**웹 쪽은 이 띠를 타이틀 스트립이 채운다.** `title-strip` 위젯이 창 최상단 full-width로 놓이고, 높이 `36px`로 이 띠와 겹친다. 그 아래에 사이드바·탭바·본문이 온다. 스트립 높이와 위 상수는 같아야 하고, 위 상수가 단일 출처다(docs-drift가 대조).

**스트립 안에는 앱 이름과 클릭 통과 영역 하나가 있다.** 앱 이름은 클릭을 받지 않는 글자다(`pointer-events: none`). 그 밖에 띠 안에서 눌리는 컨트롤을 두려면 **클릭 통과 영역**을 둬야 한다 — 띠의 `hitTest:`가 그 사각형 안에서 `nil`을 반환해 클릭이 웹뷰로 내려간다. 영역은 하나뿐이고 사이드바 토글이 쓴다. 그 자리는 드래그 손잡이가 아니다.

Expand Down Expand Up @@ -133,8 +133,8 @@ main.tsx applyBootFlags() → createRoot(...).render(<App />)
**창은 보이지 않는 상태로 만들고, 저장된 설정과 지난 세션을 적용한 뒤 보인다.** 읽기는 비동기라 위 절의 동기 경로(`applyBootFlags`)에 넣을 수 없고, 이펙트로 미루면 같은 한 프레임을 잃는다. 무엇을 저장하는지는 [파일 생명주기 정책](../file-lifecycle.md#설정-저장)과 [문서 모델](../document-model.md#세션-복원)이 소유한다.

```text
main.tsx ① 설정을 읽어 스토어에 넣는다 (await, 상한 있음)
② 지난 세션의 탭·루트를 복원한다 (await, 상한 있음 — 탭 본문까지 읽는다)
main.tsx ①② 설정 읽기와 지난 세션의 탭·루트 복원을 동시에 기다린다 (Promise.all, 각자 상한
세션은 탭 본문까지 읽는다)
③ applyBootFlags() — 표식·CSS 변수를 심는다
④ createRoot(...).render(<App />)
⑤ 창을 보인다 — 권한 core:window:allow-show
Expand All @@ -148,12 +148,12 @@ main.tsx ① 설정을 읽어 스토어에 넣는다 (await, 상한 있음)

```text
자동 ① 크롬 틴트 대비 기준 — 토큰 값만으로 계산되는 순수 함수라 유닛 테스트가 막는다
(→ design-system.md 대비 게이트). **지금 도는 자동 검사는 이것뿐이다.**
(→ design-system.md 대비 게이트).

미구현 ② 편집면이 불투명 배경을 칠하는가 (빈 상태·짧은 문서 포함)
③ 캔버스가 유리 표식에 따라 갈리는가
둘 다 렌더 결과를 봐야 하는데 vitest가 node 환경이라 돌릴 수 없다. 브라우저/DOM 환경
도입은 별도 결정이다(→ testing.md). 그때까지는 아래 수동 검증이 이 자리를 지킨다.
둘 다 렌더 결과를 봐야 하므로 Vitest Browser Mode로 검증한다(→ testing.md). 테스트를
작성하기 전까지는 아래 수동 검증으로 확인한다.

수동 1. 창 뒤에 흰 창을, 다음엔 어두운 창을 두고 크롬 픽셀을 각각 잰다
2. 두 값이 크게 벌어져야 한다 — 뒤가 비친다는 증거다
Expand Down
1 change: 1 addition & 0 deletions .claude/docs/development-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ mise run docs-drift # 계약 문서 ↔ 코드 기계 대조 (scripts/docs-dri
사용자에게 보이는 동작이 바뀐 PR에는 데모 영상을 붙인다(→ [/pr 커맨드](../commands/pr.md)). **E2E 시나리오를 그대로 녹화**하는 것이 규칙이다 — 별도 데모 스크립트를 두면 테스트와 데모가 따로 낡는다. E2E가 늘면 데모도 자동으로 풍부해진다.

- `mise run demo`(`scripts/record-demo.sh`) — 앱 창을 논리 좌표에 배치하고 `mise run e2e`를 실행하며 그 화면을 녹화한다. **좌표 주의**: WebDriver의 창 크기는 Retina 픽셀이고 `screencapture -R`은 논리 좌표다 — 섞으면 창 밖이 찍힌다. 그래서 창 배치는 AppleScript(논리 좌표)로 한다.
- `NORII_E2E_SLOWMO_MS=1000 mise run demo` — 동작(클릭·입력·훅 트리거)마다 지정 밀리초를 쉬어 사람이 볼 속도로 녹화한다. 대기 폴링에는 걸리지 않는다.
- `mise run upload-demo <파일>`(`scripts/upload-attachment.sh`) — GitHub 자산 CDN에 올려 URL을 받는다. **리포에 영상 바이너리를 커밋하지 않기 위한 유일한 경로**다. 인증은 `agent-browser` 브라우저 프로필에 저장되며, 첫 실행에서 로그인이 없으면 스크립트가 한 줄 안내를 출력한다(이후 자동).
- 전제: macOS **화면 기록 권한**(시스템 설정 → 개인정보 보호), `jq`, `agent-browser`. 권한·도구가 없으면 스크립트가 이유를 출력하고 멈춘다.

Expand Down
Loading
Loading