diff --git a/CLAUDE.md b/CLAUDE.md index 041302f..e587871 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,23 +17,57 @@ There are no tests or linting configured. This is a macOS Electron desktop app for batch-renaming photo files. It follows strict Electron security practices: `contextIsolation: true`, `nodeIntegration: false`. -**Process boundary:** All filesystem access lives exclusively in `src/main.js` (main process). The renderer never touches `fs` directly. +**Process boundary:** All filesystem access lives exclusively in `src/main/` (main process). The renderer never touches `fs` directly. -**IPC surface** (defined in `main.js`, bridged via `preload.js` as `window.api`): -- `select-folder` → opens native folder picker, returns path -- `list-images` → reads directory, returns `{name, path, mtime, size}[]` filtered to image extensions -- `read-image` → reads file, returns base64 data URL for display -- `rename-files({folderPath, files, prefix, digits})` → two-phase rename (temp names first, then final names) to avoid collisions when new names overlap old names -- `reveal-folder` → opens folder in Finder via `shell.openPath` +### File structure -**Renderer (`src/renderer.js`):** Single-file, no framework. All state is module-level globals: +``` +src/ +├── main/ +│ ├── index.js # Main process entry point; creates BrowserWindow, calls registerAll() +│ └── ipc/ +│ ├── index.js # registerAll(mainWindow) — delegates to folder and files modules +│ ├── folder.js # select-folder, reveal-folder +│ └── files.js # list-images, read-image, rename-files +├── preload/ +│ └── index.js # Bridges IPC to window.api via contextBridge (isolated context) +└── renderer/ + ├── index.html # App shell (dark theme, CSS variables in :root) + ├── style.css # All CSS + ├── renderer.js # Main renderer logic; imports state and handlers + ├── state.js # Shared mutable state: files[], currentFolder, selectedIdxs, sortMode + └── handlers/ + ├── drag-drop.js # Card drag-and-drop reorder (single and multi-card) + ├── selection.js # Rubber-band selection, Shift-click, Cmd+click + ├── pinch-zoom.js # Trackpad pinch to resize thumbnails (CSS --thumb-size, 150–400px) + └── hover-preview.js # Hover preview overlay +``` + +### 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, size}[]` filtered to image extensions, sorted by filename | +| `read-image` | invoke | Reads file, returns base64 data URL for display | +| `rename-files({folderPath, files, prefix, digits})` | invoke | Two-phase rename: temp names first, then final names to avoid collisions | +| `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. + +**State** (`src/renderer/state.js`): - `files[]` — ordered array of file objects; order determines rename sequence - `selectedIdxs` — `Set` of selected card indices -- Thumbnail grid uses CSS `--thumb-size` custom property (150–400px), adjusted via trackpad pinch (`ctrlKey + wheel`) -- Rubber-band selection: drag on empty grid area to select multiple cards; `Shift` adds to existing selection; `Cmd+click` toggles individual cards -- Drag-and-drop reorder: single card drag or multi-card drag (moves selected group); DOM reorder avoids re-fetching images by remapping existing card elements via `card.dataset.filepath` -- Thumbnails load in batches of 10 (`Promise.all`) to avoid blocking the UI +- `currentFolder` — active directory path +- `sortMode` — `'name'` | `'date'` | `null` -**Styling:** All CSS is inline in `src/index.html`. Dark theme with CSS variables defined in `:root`. The macOS traffic-light buttons sit in `#titlebar` which uses `-webkit-app-region: drag`. +**Thumbnail loading:** Uses `IntersectionObserver` with `rootMargin: '400px 0px'` — images load lazily as cards enter the viewport. DOM card elements are created immediately without `src`; the observer fills in `img.src` on entry. Drag-and-drop reorder remaps existing card elements via `card.dataset.filepath` to avoid re-fetching images. + +**Styling:** All CSS lives in `src/renderer/index.html`. Dark theme with CSS variables defined in `:root`. The macOS traffic-light buttons sit in `#titlebar` with `-webkit-app-region: drag`. **Build output:** `electron-builder` targets DMG for arm64 and x64. The app icon must be at `assets/icon.icns`. diff --git a/src/main/index.js b/src/main/index.js index c18f4f7..dfad217 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -13,7 +13,7 @@ function createWindow() { titleBarStyle: 'hiddenInset', backgroundColor: '#0f0f17', webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, '../preload/index.js'), contextIsolation: true, nodeIntegration: false, }, diff --git a/src/main/preload.js b/src/preload/index.js similarity index 100% rename from src/main/preload.js rename to src/preload/index.js