From 6870f84ef230e6d934b56d4e2ea2dd571672a3e8 Mon Sep 17 00:00:00 2001 From: jisangs Date: Sun, 17 May 2026 18:26:25 +0900 Subject: [PATCH] =?UTF-8?q?Codex=20=EC=9E=91=EC=97=85=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..12a8fdb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,149 @@ +# AGENTS.md + +This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. + +## Commands + +```bash +npm start # Run the Electron app in development +npm run build # Build macOS app bundle (no DMG, arm64 + x64) +npm run dist # Build and package as DMG for distribution +./setup.sh # First-time setup: npm install + npm start +``` + +There are no tests or linting configured. + +## Architecture + +This is a macOS Electron desktop app for batch-renaming and moving photo files. It follows strict Electron security practices: `contextIsolation: true`, `nodeIntegration: false`. + +**Process boundary:** All filesystem access lives exclusively in `src/main/` (main process). The renderer never touches `fs` directly. + +### File structure + +``` +assets/ +├── icon.png # Source icon (2048×2048, square) +└── icon.icns # macOS app icon for electron-builder + +src/ +├── main/ +│ ├── index.js # Main process entry point; app.setName(), BrowserWindow, registerAll() +│ └── ipc/ +│ ├── index.js # registerAll(mainWindow) — delegates to folder and files modules +│ ├── folder.js # select-folder, list-directories, get/set-last-folder, +│ │ # get/set-preview-on, get-home-dir, reveal-folder +│ └── files.js # list-images, read-image, rename-files, +│ # check-files-exist, move-files +├── preload/ +│ └── index.js # Bridges IPC to window.api via contextBridge (isolated context) +└── renderer/ + ├── index.html # App shell; dark theme, CSS variables in :root, modal HTML + ├── style.css # All CSS + ├── state.js # Shared mutable state: files[], currentFolder, selectedIdxs, sortMode + ├── renderer.js # Entry point: init, loadFolder, sortFiles, undo/redo, keyboard shortcuts + ├── ui.js # showLoading, hideLoading, setStatus, showToast + ├── grid.js # renderGrid, createCard, card UI update functions, showEmptyGrid + ├── rename.js # doRename + rename-conflict modal; resets history after rename + ├── move.js # handleFolderDrop + move modals + _doMoveFiles; resets history after move + ├── history.js # Undo/redo stack (max 50 entries); tracks file order + affected paths + └── handlers/ + ├── drag-drop.js # Card reorder (single/multi) + drag-to-folder in sidebar + ├── file-explorer.js # Sidebar directory tree (lazy-load, expand/collapse) + ├── selection.js # Rubber-band, Shift-click, Cmd+click, resetAnchor + ├── pinch-zoom.js # Ctrl+scroll to resize thumbnails (CSS --thumb-size, 150–400px) + └── image-preview.js # Hover preview overlay with circular loading indicator +``` + +### IPC surface + +Defined in `src/main/ipc/`, bridged via `src/preload/index.js` as `window.api`: + +| Channel | Direction | Description | +|---|---|---| +| `select-folder` | invoke | Opens native folder picker, returns path or null | +| `list-images` | invoke | Reads directory, returns `{name, path, mtime}[]` filtered to image extensions, sorted by filename | +| `read-image` | invoke | Reads file, returns base64 data URL (supports jpg/png/gif/webp/tiff/bmp/heic) | +| `rename-files({folderPath, files, prefix, digits})` | invoke | Two-phase rename: temp names (crypto.randomUUID) first, then final names | +| `check-files-exist(folderPath, names[])` | invoke | Returns subset of names that already exist in folderPath | +| `move-files({targetFolder, files[]})` | invoke | Moves files; each file may have optional `newName`; handles cross-device (EXDEV) | +| `list-directories(folderPath)` | invoke | Returns `{name, path, hasChildren}[]` subdirs, or `null` on access error | +| `get-home-dir` | invoke | Returns `os.homedir()` | +| `get-last-folder` / `set-last-folder` | invoke | Persists last opened folder in userData config.json | +| `get-preview-on` / `set-preview-on` | invoke | Persists hover preview toggle state in userData config.json | +| `reveal-folder` | invoke | Opens folder in Finder via `shell.openPath` | +| `fullscreen-change` | push (main→renderer) | Sent on `enter-full-screen` / `leave-full-screen` window events | + +### Renderer (`src/renderer/`) + +Uses ES module imports (`type="module"`). No framework. + +**Dependency direction** (no cycles): +``` +renderer.js → rename.js, move.js → grid.js → handlers/* + ↘ ↘ ↗ + history.js ui.js +``` + +**State** (`src/renderer/state.js`): +- `files[]` — ordered array of `{name, path, mtime}`; order determines rename sequence +- `selectedIdxs` — `Set` of selected card indices +- `currentFolder` — active directory path +- `sortMode` — `'name'` | `'date'` | `null` + +**Keyboard shortcuts** (모두 `e.code` 기준 — 한글 입력 모드에서도 동작): +- `O` — 폴더 열기 +- `S` — 이름 변경 실행 +- `A` — 전체 선택 +- `Cmd+Z` — Undo (되돌리기) +- `Cmd+Shift+Z` — Redo (다시하기) +- `P` — 미리보기 on/off 토글 + +**Undo / Redo** (`history.js`): +- 파일 순서 변경(드래그, 정렬)만 추적; 파일시스템 변경(rename, move)은 히스토리 리셋 +- 각 엔트리: `{ files: [...], affectedPaths: Set | null }` +- `affectedPaths`: undo/redo 실행 후 해당 파일들을 선택 상태로 복원하는 데 사용 +- `setOnUpdate(fn)`: push/reset/undo/redo 시 자동 호출되는 콜백 등록 (버튼 상태 갱신용) +- 최대 50개 엔트리 유지 + +**Card selection** (`selection.js`): +- Click — 단독 선택 (재클릭 시 해제) +- Shift+Click — 범위 선택 +- Cmd+Click — 개별 토글 +- Rubber-band drag — 영역 선택 (rAF throttle 적용) +- Shift+Rubber-band — 토글 선택 (드래그 시작 시점 스냅샷 기준으로 선택/해제 반전) +- `resetAnchor()` — 그리드 재렌더 시 Shift 기준점 초기화 (grid.js에서 호출) + +**Drag-and-drop** (`drag-drop.js`): +- 그리드 내 카드 순서 재배치 (single/multi) + - 드래그 중인 카드는 `.drag-hidden`으로 숨김; 삽입 위치에 `.card-placeholder` 표시 + - 마지막 카드 이후 빈 영역에 드롭하면 맨 끝에 삽입 + - 드롭 완료 후 이동한 파일들이 선택 상태로 유지되고 history에 push +- 사이드바 폴더로 드래그: 선택된 파일들을 해당 폴더로 이동 + - 파일명 충돌 시 rename-and-move 모달 표시 + - 드래그 중 `.tree-row.drop-target` 클래스로 대상 폴더 하이라이트 + +**Rename** (`rename.js`): +- 선택된 파일만 이름 변경 (선택 없으면 전체) +- 변경될 이름이 미선택 파일과 충돌하면 충돌 목록 모달 표시 +- 완료 후 history 리셋 (rename은 파일 경로가 바뀌므로 undo 대상 아님) + +**Move** (`move.js`): +- 충돌 없으면 즉시 이동 +- 충돌 있으면 prefix + digits 입력 모달 → 새 이름으로 이동 +- 새 이름도 충돌이면 에러 모달 +- 완료 후 history 리셋 + +**Thumbnail loading:** `IntersectionObserver` with `rootMargin: '400px 0px'` — lazy load as cards enter viewport. `entry.target.isConnected` 체크로 폴더 전환 중 스탈 카드 방지. Drag reorder remaps card elements via `card.dataset.filepath` to avoid re-fetching. + +**Hover preview** (`image-preview.js`): 1초 지연 후 전체 크기 팝업. 썸네일 캐시 우선 사용. 상태는 config.json에 영속. + +**Sidebar tree** (`file-explorer.js`): Lazy-load 자식 폴더. 접근 불가 폴더는 `null` 반환 → "접근 불가" 표시. 행 클릭 시 항상 펼침, 화살표 클릭 시 토글. + +**Styling:** CSS는 `src/renderer/style.css`에 집중. Dark theme, CSS variables in `:root`. macOS traffic-light은 `header`의 `-webkit-app-region: drag`. + +**App icon:** 소스는 `assets/icon.png` (2048×2048). `assets/icon.icns`는 10개 해상도(16px~1024px@2x)를 포함하며 electron-builder가 사용. `app.setName('Photo Renamer')`로 개발 모드 메뉴바 이름도 지정. + +**Build output:** `electron-builder` targets DMG for arm64 and x64. + +**Config persistence:** `app.getPath('userData')/config.json`에 `lastFolder`, `previewOn` 저장.