* Add Claude Code project memory for modernization
Create CLAUDE.md with full architecture map, tech stack, commands,
dependency inventory, and build/packaging docs. Add .claude/rules/
with modernization roadmap (8-phase plan) and GitHub context summary
(64 open issues, 8 open PRs categorized). Fix .gitignore to allow
.claude/ directory through the blanket .* rule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Enhance CLAUDE.md with directory reference, build procedures, code patterns
Add directory tree for .resources/, build/, static/, src/ structure.
Document exact dev/compile/pack/release/exiftool-update workflows.
Add code patterns section covering exports, async, DOM, IPC, platform
guards, TypeScript idioms, CSS theming, and i18n conventions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add design vision and dependency philosophy to project memory
Update modernization roadmap: strengthen Phase 7 with hand-roll-first
dependency philosophy, add Phase 9 (UI/UX design overhaul with BEM CSS,
system fonts, micro-animations, native feel), update key constraints.
Update CLAUDE.md conventions with BEM direction, system fonts, and
zero-dependency target.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Swap build system from electron-webpack to electron-vite
Replace abandoned electron-webpack (webpack 4) with electron-vite (Vite 7)
to unblock TypeScript and Electron upgrades.
Changes:
- Remove electron-webpack, electron-webpack-ts, webpack
- Add electron-vite 5.0 and vite 7.3
- Create electron.vite.config.ts with renderer externalization for
nodeIntegration compatibility (temporary until Electron upgrade)
- Rewrite tsconfig.json as standalone (no longer extends electron-webpack)
- Extract IPC event constants to src/common/ipc_events.ts to avoid
cross-process import chains that break Vite's bundling
- Update window_setup.ts to use ELECTRON_RENDERER_URL (replaces
ELECTRON_WEBPACK_WDS_PORT)
- Remove webpack-specific module.hot HMR blocks from both entry points
- Add ELECTRON_RUN_AS_NODE= prefix to dev/start scripts to prevent
Cursor IDE environment contamination
- Delete dead code: electron-webpack.json, src/common/app.ts
- Add safety rule and Docker packaging note to project memory
Verified: tsc --noEmit, electron-vite build, yarn dev, yarn packmactest
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Upgrade TypeScript 3.8 → 5.7 with strict mode, Prettier 3.x
- TypeScript 5.7.3 with strict: true (strictNullChecks, noImplicitAny, etc.)
- tsconfig: target ES2021, module ESNext, moduleResolution bundler
- Prettier 3.8.1 (trailing commas now default to "all")
- Clean up node-exiftool .d.ts: 115 → 31 lines, named result interfaces
- Zero strict mode errors — codebase was already clean
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Playwright e2e, DDD architecture, and CI releases to roadmap
- Playwright e2e tests — deterministic, fast quality gate
- DDD/hexagonal architecture — SRP, DI, functional core
- CI releases with no auto-update (privacy-first)
- Added key constraints: never auto-publish, never auto-update
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Upgrade Electron 11 → 35 with contextIsolation, preload, and sandbox
Move all Node.js/exiftool operations from renderer to main process.
Renderer is now a fully sandboxed browser tab that accesses exiftool,
i18n, and file operations exclusively through window.api (contextBridge).
Three-phase migration:
- Phase A: Upgrade Electron runtime + @types/node, fix API changes
- Phase B: Create preload script, exif IPC handlers, rewire renderer
- Phase C: Enable nodeIntegration: false, contextIsolation: true, sandbox: true
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Enable ESM modules: verbatimModuleSyntax + type: module
- Add verbatimModuleSyntax to tsconfig.json, enforcing import type
- Fix 10 files with type-only imports from electron
- Add "type": "module" to package.json for ESM output
- Update preload path to .mjs (electron-vite ESM output)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reorder modernization roadmap: local quality first, infrastructure later
Mark Phases 1-3 done (build system, core deps, ESM). Reorder remaining
phases per maintainer priorities: verify + cleanup, Playwright tests,
DDD refactor, community features, UI/UX overhaul — then deferred
release infrastructure (CI, dep cleanup, exiftool update, signing).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add QA runbook for Phase 4: Verify + Cleanup
Comprehensive manual testing guide for validating the modernized app
on Apple Silicon Mac, covering dev setup, i18n diagnosis, 47 test
cases across 10 feature areas, and cleanup tasks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix update_exiftool.pl for new ExifTool Windows distribution format
The Windows zip now extracts to a versioned subdirectory (e.g.
exiftool-13.50_32/) and includes a companion exiftool_files/ dir
alongside the exe stub. Updated copy_windows_binary to handle the
new path structure and copy both the exe and companion directory.
Also fixed undef $ARGV[0] warning when run without arguments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix preload script: output as CJS for Electron sandbox compatibility
Electron's sandboxed preload context does not support ESM imports.
With "type": "module" in package.json, electron-vite was outputting
the preload as index.mjs (ESM), causing "Cannot use import statement
outside a module" at runtime. Force preload output to CJS format and
update the preload path from index.mjs to index.cjs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add dev:debug script and Chrome DevTools MCP documentation
Add yarn dev:debug script for launching with --remote-debugging-port=9222.
Document Chrome DevTools MCP setup in CLAUDE.md. Fix outdated preload
output path (index.cjs not index.mjs) and module style description.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix drag-and-drop: use webUtils.getPathForFile() for Electron 35
The deprecated file.path property is undefined in Electron 35's sandboxed
renderer. Expose webUtils.getPathForFile() through the preload API and
update the drag handler to use it instead of the removed file.path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Achieve zero production dependencies
Phase 4 cleanup:
- Remove source-map-support dependency (never imported, obsolete with Node 22)
Chunk 6: Hand-roll ExifTool wrapper
- Create src/infrastructure/exiftool/ with ExiftoolProcess class (~240 lines)
- Implement -stay_open protocol with command queue and stdout buffering
- Replace node-exiftool import in src/main/exif_handlers.ts
- Remove src/types/node-exiftool/index.d.ts (types now in infrastructure layer)
- Remove node-exiftool dependency from package.json
package.json now has "dependencies": {} - zero production dependencies achieved.
All external code is either in devDependencies (build tools) or hand-rolled.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Update documentation for Phase 4 and Chunk 6 completion
CLAUDE.md updates:
- Tech Stack: Document zero production dependencies and hand-rolled ExifTool wrapper
- Architecture: Add Infrastructure layer section (src/infrastructure/exiftool/)
- Directory Reference: Add infrastructure/, remove types/
- Dependencies: Replace table with zero-deps statement
- Code Patterns: Remove node-exiftool type definitions reference
- Current State: Mark Phase 4 and Chunk 6 as complete
Modernization roadmap updates:
- Phase 4 (Verify + Cleanup): Mark as DONE, add completed tasks
- Phase 6 (DDD Refactor): Note infrastructure layer started
- Phase 10 (Dependency Cleanup): Mark as DONE (completed out of order)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix ExifTool v13.50 compatibility (Phase 11)
Two breaking changes in ExifTool v13.50 required fixes:
1. Write operations no longer return JSON
- Removed -json flag from writeMetadata()
- Added plain text parser for write responses
- Fixed "Unexpected end of JSON input" error
2. Charset argument deprecated
- Removed -charset filename=UTF8 from args
- Unicode filenames still work (v13.50 handles UTF-8 natively)
- Eliminated "Tag 'charset' is not defined" warnings
Files modified:
- src/main/exif_handlers.ts: Remove charset args
- src/infrastructure/exiftool/ExiftoolProcess.ts: Handle text responses
Testing:
- Automated: Chrome DevTools MCP tests pass
- Manual: Unicode filenames work, no stderr warnings
- ExifTool v13.50 confirmed working
Security: Fixes CVE-2021-22204 (4.75 years of patches)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Fix type safety: replace any with ExifData, string throws with Error objects
- Replace `any` with `ExifData` in table_update_row.ts (2 occurrences)
- Replace string throws with `new Error()` across 8 files (10 instances)
- Add invariant comment for non-null assertion in ExiftoolProcess.ts
Part of Phase 9 DDD architecture refactor (Step 3: type safety fixes).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Extract domain layer with pure business logic
- Create src/domain/exif.ts: pure cleanExifData() (no mutation, no I/O)
- Move i18n_lookup.ts → domain/ (already pure, zero dependencies)
- Move ipc_events.ts → domain/ipc_channels.ts (pure string constants)
- Update all consumer imports (8 files)
- Domain layer has zero Node/Electron imports
Part of Phase 9 DDD architecture refactor (Step 1: domain extraction).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Consolidate infrastructure and inject dependencies
- Move 5 Node-dependent common/ files to infrastructure/electron/:
binaries.ts, resources.ts, env.ts, browser_window.ts, i18n.ts→i18n_strings.ts
- Refactor exif_handlers.ts: accept injected getProcess, add bounds check
- Refactor app_setup.ts: accept injected onQuit callback
- Make init.ts the composition root: owns ExiftoolProcess singleton,
wires all dependencies explicitly
- common/ reduced to platform.ts only (pragmatic: 9 consumers, 1-line impurity)
Part of Phase 9 DDD architecture refactor (Step 2: infrastructure + DI).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Strengthen tsconfig with 4 additional strict flags
Enable incrementally:
- noFallthroughCasesInSwitch: prevent accidental switch fallthrough
- noImplicitReturns: all code paths must return
- useUnknownInCatchVariables: catch blocks use unknown, not any
- noUncheckedIndexedAccess: array/object indexing returns T | undefined
Fixes for noUncheckedIndexedAccess:
- browser_window.ts: getAllWindows()[0] → ?? null
- ExiftoolProcess.ts: match[1] → match[1]! (regex capture group
guaranteed by match success)
Part of Phase 9 DDD architecture refactor (Step 4: tsconfig strengthening).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* electron and typescript conventions
* Add UI prototype prompts, generated prototypes, and final synthesis
9 prototype prompt specs (A through F + ultimate synthesis) exploring
table, card grid, focused-flow, power-user, and bottom-bar layouts.
7 generated React prototype JSX files from Claude artifact sessions.
6 rounds of iteration on the final prototype with feedback logs.
The ultimate synthesis prompt distills feedback from all variants into
one definitive design: table layout, settings popover, inline metadata
expansion, flat folder paths, and "Clean more" CTA. Remaining visual
polish items captured in ExifCleaner-Final-Remaining-Feedback.md.
Also adds "performance is sacred" principle to CLAUDE.md and roadmap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(01-02): add WebP test fixture with known EXIF metadata
- 100x100 blue WebP image with Artist, GPS, Make, Model, DateTimeOriginal
- Verified ExifTool 13.50 reads and strips metadata via -all= flag
- Fixture preserved with metadata for Phase 8 E2E tests
- 484 bytes, well under 50KB target
* feat(01-01): add Persian and Catalan translations to strings.json
- Cherry-pick Persian (fa) translations from PR #258 for all 45 keys
- Cherry-pick Catalan (ca) translations from PR #239 for all 45 keys
- Confirm Croatian (hr) already present at 45/45 coverage
- Total locale count per key: 24 (22 existing + fa + ca)
* feat(01-01): sync Locale enum and fallback function with all supported locales
- Expand Locale enum from 11 to 25 members matching all 24 locales in strings.json
- Add PortugueseBR as separate enum member (pt-BR has its own translations)
- Add Croatian fallback case using hr-HR (hyphen, fixing PR #206 underscore bug)
- Add Vietnamese fallback case mapping vi to vn (Electron reports vi, strings.json uses vn)
- Update pt-BR fallback to return PortugueseBR instead of Portuguese
* feat(02-01): install Vitest, create Result type, port interfaces, and test fakes
- Add Vitest 3.2.4 with test/test:watch scripts
- Create Result<T> discriminated union in src/common/result.ts
- Create ExifToolPort, SettingsPort, LoggerPort interfaces in src/application/
- Create Settings schema with 5 fields, defaults, validation, migration
- Create FakeExifTool, FakeSettings, FakeLogger test fakes
- All 6 fake tests pass, TypeScript compiles cleanly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(02-01): add domain types with unit tests and barrel file
- Create file_types.ts with SUPPORTED_EXTENSIONS (30 formats) and isSupportedFile()
- Create file_status.ts with FileProcessingStatus enum (6 states)
- Create domain barrel file re-exporting all domain types
- Add settings_schema tests (8 tests: validation, migration, defaults)
- Add file_types tests (6 tests: supported/unsupported/case-insensitive)
- Add domain purity meta-test (verifies zero I/O imports in src/domain/)
- All 21 tests pass, TypeScript compiles cleanly, app build succeeds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(02-02): add failing tests for SettingsService
- 8 tests covering load, save, get, update, atomic write, migration, corruption fallback
- Tests use real filesystem with temp directories for isolation
- Uses FakeLogger from test fakes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(02-02): implement SettingsService with atomic writes and migration
- JSON file-backed settings persistence implementing SettingsPort
- Atomic writes via temp file + rename to prevent corruption
- Schema migration on load when version differs from current
- Falls back to DEFAULT_SETTINGS on missing or corrupt file
- get() returns cached settings synchronously without I/O
- update() merges partial settings and persists atomically
- One retry on save failure, cache stays updated in memory
- All 8 unit tests pass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(02-02): add ConsoleLogger and relocate IPC channels to infrastructure
- ConsoleLogger implements LoggerPort with structured console output
- IPC_CHANNELS const object in infrastructure/ipc with all channel names
- New settings channels: settings:get, settings:set, settings:changed
- Domain ipc_channels.ts becomes backward-compatible re-export shim
- All existing imports continue working unchanged
- Build and type-check pass with zero errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(02-03): add failing tests for application layer commands, queries, and use case
- StripMetadataCommand: 6 tests (flags, rotation, timestamps, abort, error, order)
- ReadMetadataQuery: 3 tests (read+clean, empty, error)
- ExpandFolderCommand: 4 tests (recursive, filter, empty, nonexistent)
- ProcessFilesUseCase: 4 tests (progress, continue-on-failure, abort, settings)
* feat(02-03): implement application layer commands, queries, and use case
- StripMetadataCommand: strips metadata with correct flag order (-all= before -TagsFromFile)
- ReadMetadataQuery: reads metadata and filters computed fields via cleanExifData
- ExpandFolderCommand: recursively finds supported files using node:fs readdir
- ProcessFilesUseCase: orchestrates expand-strip pipeline with progress, abort, continue-on-failure
- All 17 application layer tests pass
* feat(02-03): wire composition root, ExifToolAdapter, IPC handler swap, settings IPC, preload expansion
- ExifToolAdapter bridges ExiftoolProcess to ExifToolPort without modifying ExiftoolProcess
- Composition root (container.ts) wires all dependencies at startup
- IPC handlers delegate to commands (zero inline business logic)
- Settings IPC handlers with validation and change notification events
- Preload API exposes settings namespace (get, set, onChanged)
- init.ts now async, awaited after app.whenReady()
- Build succeeds, all 46 tests pass, tsc clean
* feat(03-01): install React 19, Zod 3, and build tooling dependencies
- Add react, react-dom, zod to production dependencies
- Add @vitejs/plugin-react, @types/react, @types/react-dom to devDependencies
- Add typecheck and preview npm scripts
* feat(03-02): create security modules and Zod IPC schemas
- Navigation hardening: block untrusted origins, open HTTPS in system browser
- Permission gate: deny all browser permissions by default
- Zod schemas for all 11 IPC channels (invoke + fire-and-forget)
- Validated handler/listener wrappers with sender ID verification
- Add theme:get and theme:changed channels to IPC_CHANNELS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-01): configure electron-vite React plugin, CSP injection, and renderer tsconfig
- Add React plugin and CSP injection plugin to electron-vite renderer config
- CSP: default-src 'none', no unsafe-eval/unsafe-inline, ws://localhost:* for dev HMR
- Create tsconfig.renderer.json with jsx: react-jsx for automatic runtime
- Update base tsconfig.json with jsx, lib, and .tsx include
- Update lint/format scripts to cover .tsx files
* feat(03-02): wire validated handlers into all IPC handlers
- exif_handlers: createValidatedHandler for exif:read, exif:remove
- settings_handlers: createValidatedHandler for settings:get, settings:set
- i18n: createValidatedHandler for get-locale, get-i18n-strings
- dock: createValidatedListener for files-added, file-processed, all-files-processed
- init: register sender, hardenNavigation, installPermissionGate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(03-02): add unit tests for IPC validation and security modules
- 21 schema tests: exifRead, exifRemove, settingsSet, filesAdded, getLocale, void schemas
- 9 validation tests: sender auth, handler wrapper, listener wrapper
- 7 navigation tests: file://, dev localhost, prod block, window.open deny, openExternal
- 2 permission tests: deny all permission types
- Refactor navigation/permissions to accept deps for testability (no vi.mock)
- 39 new tests, 85 total passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(03-04): add failing tests for window state persistence
- 7 tests for isWithinDisplayBounds (single/dual display, overlap, gap)
- 7 tests for validateAndLoadState (null, invalid, valid, off-screen, missing fields)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-04): implement window state persistence with display bounds validation
- Pure isWithinDisplayBounds() checks rect overlap against any display work area
- validateAndLoadState() parses/validates JSON with type safety
- loadWindowState() reads from window-state.json (sync, runs once at startup)
- saveWindowState() atomic writes via temp file + rename
- setupWindowStatePersistence() debounced resize/move handlers (300ms)
- Off-screen positions fall back to undefined (OS centers window)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-04): add dynamic theme background, theme IPC, DevTools guard, and fix window creation order
- window_setup.ts: dynamic backgroundColor via nativeTheme.shouldUseDarkColors
- window_setup.ts: loadWindowState() for size/position restoration
- window_setup.ts: devTools: !app.isPackaged (disabled in production)
- window_setup.ts: setupWindowStatePersistence() in setupMainWindow
- theme_handlers.ts: theme:get handler with Zod validation, theme:changed broadcast
- init.ts: wire setupThemeHandlers alongside other IPC handlers
- index.ts: fix window creation order (create -> init -> setup)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-03): replace vanilla renderer with React SPA shell
- Delete all 12 vanilla renderer files and 11 old CSS files
- Create React entry point with createRoot and StrictMode
- Add ThemeProvider, I18nProvider, AppProvider contexts
- Add ErrorBoundary class component with fallback UI
- Add useI18n and useTheme hooks consuming contexts
- Add BEM CSS: reset, tokens (light/dark), app layout, error boundary
- Update preload API with ThemeApi interface
- Replace index.html with minimal #root mount point
- Create EmptyState, DropZone, FileList components
- TypeScript compiles cleanly with zero errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(03-03): add SVG icon to EmptyState component
- Add inline SVG icon from original index.html to EmptyState
- Icon uses currentColor for theme compatibility
- aria-hidden on decorative SVG for screen readers
- All UI components complete: EmptyState, DropZone, FileList
- All BEM CSS complete with prefers-reduced-motion support
- yarn compile and yarn test pass cleanly
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(03-03): allow unsafe-inline CSP for scripts and styles in dev mode
Vite's React Refresh plugin injects inline <script> tags for HMR, and
Vite's CSS HMR injects inline <style> tags. Both are blocked by strict
CSP in dev mode. Production builds remain strict (self only).
Also removed frame-ancestors from meta CSP (only valid in HTTP headers).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(04-01): add failing tests for extended AppContext reducer
- 12 test cases covering all 7 action types
- Tests import appReducer (not yet exported) and extended FileEntry type
- Covers ADD_FILES, CLEAR_FILES, UPDATE_FILE_STATUS, UPDATE_FILE_METADATA,
UPDATE_FILE_ERROR, TOGGLE_FOLDER, TOGGLE_ROW_EXPANSION, exhaustive switch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-01): extend AppContext with per-file processing state model
- FileEntry: 10 fields (id, path, name, extension, size, folder, status, beforeTags, afterTags, error)
- AppState: files array, collapsedFolders Set, expandedRowId
- 7 action types: ADD_FILES, CLEAR_FILES, UPDATE_FILE_STATUS, UPDATE_FILE_METADATA,
UPDATE_FILE_ERROR, TOGGLE_FOLDER, TOGGLE_ROW_EXPANSION
- Export appReducer for testability
- Update DropZone to construct full FileEntry with crypto.randomUUID()
- Update FileList to use file.id as React key
- All 12 reducer tests pass, 111 total tests passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(04-01): add failing tests for utility functions
- 8 test cases for formatFileSize (0 B, KB, MB, GB, fractional sizes)
- 5 test cases for getFileExtension (lowercase, uppercase, no ext, compound)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-01): add formatFileSize and getFileExtension utilities
- formatFileSize: human-readable file sizes (B, KB, MB, GB)
- getFileExtension: uppercase extension extraction from filename
- Both pure functions with explicit return types, no dependencies
- 13 tests passing (8 for size, 5 for extension)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-01): complete CSS token system and component stylesheets
- tokens.css: full light/dark color system, spacing (8px grid), typography
(system-ui font stack), animation tokens, type pill colors, backward-compat aliases
- file_table.css: 5-column grid, sticky header, row states, entrance animation
- status_icon.css: CSS spinner, checkmark pop, keyframes (ec-spin, ec-check-pop)
- type_pill.css: color-coded pills for JPG, HEIC, PNG, PDF, MOV, DNG + variants
- status_bar.css: sticky bottom bar with summary and accent-colored button
- folder_row.css: collapsible sections with chevron rotation animation
- toast.css: fixed notification with opacity transition
- drop_zone.css: updated to use gold border/bg for drag-over (D-10)
- app.css: system font stack via --ec-font-family token (UI-10)
- All animations wrapped in @media (prefers-reduced-motion: no-preference) (UI-09)
- Prettier formatting applied to all modified files
- Build compiles, 124 tests pass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-02): create atomic presentational components (TypePill, StatusIcon, Toast, ErrorExpansion)
- TypePill renders color-coded extension badges with alias mapping (JPEG->jpg, RAW->dng, video->mov)
- StatusIcon renders pending dot, CSS spinner, animated checkmark, and circle-X per FileProcessingStatus
- Toast renders copy confirmation with role="status" and aria-live="polite"
- ErrorExpansion renders selectable error text with click-to-copy via navigator.clipboard
* feat(04-02): create composite components (FileRow, FolderRow, StatusBar, FileTable) and wire into App
- FileTable renders 5-column header (NAME, TYPE, SIZE, BEFORE, AFTER) with folder grouping
- FileRow renders TypePill, StatusIcon, formatFileSize in 5 cells with keyboard navigation
- FolderRow renders collapsible folder headers with chevron toggle and ARIA labels
- StatusBar shows summary text and "Clean more" button dispatching CLEAR_FILES
- FileTable replaces FileList stub in App.tsx, reads state via useAppContext()
- Toast for copy confirmation, ErrorExpansion for click-to-copy error text
- Staggered entrance animation via --ec-stagger-delay CSS variable (D-11)
- One-shot checkmark animation tracked via React ref (D-12)
* test(04-03): add failing tests for processing pipeline hook
- 9 test cases covering processFileEntries: status transitions, metadata
counts, error handling, sequential processing, IPC notifications
- Tests import processFileEntries which does not exist yet (RED phase)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-03): implement processing pipeline and elapsed time hooks
- use_process_files.ts: processFileEntries orchestrates read->strip->read
pipeline with sequential processing, error handling, and IPC notifications
- useProcessFiles hook wraps pipeline with queue ref for rapid drop handling
- use_elapsed_time.ts: timer hook with start/stop/reset for status bar
- All 10 new tests pass, 134 total passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(04-03): wire processing pipeline into DropZone and FileTable
- DropZone: filters through isSupportedFile, calls processFiles after
ADD_FILES dispatch for auto-start on drop (D-06)
- DropZone: File > Open menu handler also filters and auto-processes
- FileTable: uses useElapsedTime hook for real-time status bar timer
- FileTable: timer starts on processing, stops when all done, resets on clear
- 134 tests passing, compile and lint clean
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(04-03): move status icon to first column, remove row border-left
- Status icon column is now first (before filename) per UAT feedback
- Removed border-left from complete and error rows per UAT feedback
- Updated grid-template-columns to 32px 1fr 72px 80px 72px 72px
- Simplified renderBeforeCell and renderAfterCell (status shown in dedicated column)
* docs: add animation principles reference from Emil Kowalski research
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(05-01): add failing tests for settings v2 migration and cleaned path
- Settings schema v2 tests: version constant, defaults, validation, v1->v2 migration
- Cleaned path tests: suffix, collision handling, multiple dots, no extension
* feat(05-01): settings schema v2, cleaned path generator, platform API
- Split preserveRotation into preserveOrientation + preserveColorProfile (schema v2)
- v1->v2 migration: preserveRotation maps to both new fields
- Pure domain cleaned path generator with collision-free _cleaned suffix
- Platform API in preload (static isMac boolean, no IPC overhead)
- Updated StripMetadataCommand signature for split settings + save-as-copy
- Updated all existing tests and IPC schemas for new field names
- 147 tests passing, zero type errors
* test(05-01): add tests for split settings arg combinations and xattr service
- StripMetadataCommand: 11 tests covering all 4 setting combinations
- xattr_service: tests for macOS guard, exec call, non-fatal error handling
* feat(05-01): xattr service, xattr command, ProcessFilesUseCase wiring
- xattr_service.ts: macOS-only xattr -cr with isMac() guard, non-fatal
- XattrCommand: application-layer wrapper with port interface
- ProcessFilesUseCase: wires generateCleanedPath for save-as-copy, xattr after strip
- Container: creates and injects XattrCommand with infrastructure adapter
- 158 tests passing, zero type errors
* feat(05-02): ToggleSwitch and GearIcon components with toggle CSS
- ToggleSwitch: accessible pill switch with role="switch", aria-checked, focus ring
- GearIcon: outlined SVG gear with forwardRef and dynamic aria-label
- toggle_switch.css: instant state changes (no transitions per D-08), dark mode support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(05-02): settings drawer with focus trap, animations, and App.tsx integration
- SettingsDrawer: 320px slide-from-right panel with backdrop, 5 privacy toggles
- Focus trap: Tab cycles within drawer, Escape closes
- Animation: 300ms open / 200ms close, prefers-reduced-motion disables all transitions
- App.tsx: gear icon + drawer mounted, aria-hidden on content when drawer open
- Settings persist immediately via window.api.settings IPC
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(05-03): toggle animation, settings menu items, IPC bridge, blank cells
- Add 200ms spring-eased toggle animation respecting prefers-reduced-motion
- Add SETTINGS_TOGGLE IPC channel and onToggle preload bridge
- Add Settings menu item to App menu (macOS), File menu, and dock menu
- Wire Cmd/Ctrl+comma shortcut via CmdOrCtrl+, accelerator
- Add useEffect listener in App.tsx for settings:toggle IPC
- Replace dash placeholders with blank cells in FileRow
- Add menu.app.settings i18n key to strings.json
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(06-02): metadata groups domain module with grouped key parsing and diff computation
- Create metadata_groups.ts with parseGroupedKey, getFriendlyGroupKey, computeMetadataDiff
- Update cleanExifData to handle grouped keys (e.g., Other:SourceFile)
- Add 16 tests covering all domain logic
* feat(06-01): settings schema v3 with themeMode + accent color parser
- Bump CURRENT_SCHEMA_VERSION to 3, add themeMode field to Settings
- Add v2->v3 migration (themeMode=system default) and v1->v3 chain
- Add validateSettings themeMode handling with fallback to system
- Create accent_color.ts with parseAccentColorHex and ACCENT_COLOR_FALLBACK
- 28 domain tests green (6 accent color + 22 settings schema)
* feat(06-02): ReadMetadataQuery -G2 flag, full metadata capture in AppContext, i18n strings
- Add -G2 flag to ReadMetadataQuery for ExifTool family 2 grouped output
- Extend FileEntry with beforeMetadata/afterMetadata fields
- Update use_process_files to store full metadata objects (not just counts)
- Add 17 i18n strings for metadata groups, theme labels, and metadata UI
- Update existing tests for new action shape
* feat(06-01): theme IPC pipeline with set, accent-color, and preload bridge
- Add THEME_SET, THEME_ACCENT_COLOR, THEME_ACCENT_COLOR_CHANGED IPC channels
- Add themeSetSchema and themeAccentColorSchema Zod validators
- Theme handler sets nativeTheme.themeSource and persists via SettingsService
- Accent color handler serves OS color via systemPreferences with fallback
- Accent color change listener for Windows/Linux, macOS re-reads on nativeTheme.updated
- Extend ThemeApi with set(), getAccentColor(), onAccentColorChanged()
- Wire settingsService into setupThemeHandlers from composition root
- All 186 tests pass, build compiles clean
* feat(06-04): add metadata inspection components and CSS
- ChevronIcon: rotatable SVG with motion-safe transition
- MetadataField: removed/preserved indicator with red/green tint
- MetadataGroup: collapsible category with header count
- MetadataExpansion: container with copy-all and grouped diff
- CSS: BEM styles with dark mode tokens, prefers-reduced-motion
- tokens.css: add removed-bg, preserved-bg, font-family-mono tokens
* feat(06-04): integrate MetadataExpansion and ChevronIcon into FileRow
- Replace placeholder text with real MetadataExpansion component
- Add ChevronIcon in status cell for expandable rows
- Only expandable rows (Complete/NoMetadataFound/Error) are clickable
- NoMetadataFound shows i18n 'noMetadataFound' message
- Add expandable row cursor, chevron positioning, slide-open animation
- All 186 tests pass, build compiles
* feat(06-03): ThemeContext 3-way mode, SegmentedControl, adaptive CSS tokens
- ThemeContext extended with themeMode, setThemeMode, accent color, crossfade
- SegmentedControl component with sliding indicator, keyboard nav, ARIA
- tokens.css: system accent replaces brand plum/wine, muted pill colors
- Adaptive tokens: drawer shadow, backdrop opacity, toggle off-track, status bar border
- Settings drawer: Appearance section with Light/Auto/Dark segmented control
- Toast dark mode inversion, crossfade transition class, ::selection accent
- IPC channel for theme mode sync from View menu
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(06-03): View menu Appearance submenu with Light/Auto/Dark radio items
- Appearance submenu with radio items syncing with nativeTheme
- Broadcasts theme mode to renderer via IPC for segmented control sync
- Menu items use i18n keys with English fallbacks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): settings spacing, checkmark placement, clipboard permission, dark scrollbar
- Remove separator line and tighten padding in settings drawer
- Move checkmark from status column to right of AFTER count
- Allow clipboard-write permission for Copy All button
- Add dark mode scrollbar styles for file table body
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): spacing polish, native dark scrollbars, column widths
- Add more padding above Appearance section in settings drawer
- Narrow status column (32→24px) to tighten chevron-to-name gap
- Widen BEFORE/AFTER columns (72→80px) for breathing room
- Add color-scheme: light/dark to tokens.css for native scrollbars
- Remove custom webkit scrollbar overrides (native is better)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): widen BEFORE/AFTER columns for more breathing room (80→88px)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): gear icon to status bar, native table headers, persistent bottom bar
- Move gear icon from absolute top-right to persistent status bar (bottom-left)
- Status bar now renders on both EmptyState and FileTable views
- Lift file stats and elapsed timer from FileTable to App for status bar
- Table column headers: 11px uppercase with letter-spacing (native feel)
- Status bar height 36px, compact layout with gear/stats/clean-more
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(06): add beforeMetadata/afterMetadata to buildFileEntry in DropZone
Fixes TypeScript type contract — FileEntry interface requires these fields
after Plan 06-02 extended it with full metadata capture.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(07-01): domain logic + IPC infrastructure for folder operations
- middleTruncatePath with 8 unit tests for long folder path display
- IPC channels: folder:classify and folder:expand with Zod schemas
- folder_handlers.ts: classify paths as files/folders, expand via ExpandFolderCommand
- Preload bridge: FolderApi with classify and expand methods
- Wired setupFolderHandlers into init.ts alongside existing handlers
* feat(07-02): language names module, i18n override, IPC, menu submenus
- Add LANGUAGE_NAMES domain module with 25 entries sorted by nativeName
- Add language_names.test.ts with 5 tests (count, sort, uniqueness, coverage, non-empty)
- Add language/languageSystem i18n strings for all 24 languages
- Modify i18n.ts locale() to check settings language override
- Add rebuildMenusForLanguageChange and handleLanguageChange
- Add Language submenu to View menu and dock menu with radio items
- Add LANGUAGE_CHANGED IPC channel and onLanguageChanged preload listener
- Wire language change handlers in init.ts for menu-driven changes
* feat(07-01): renderer folder pipeline with DropZone, AppContext, FolderRow, FileTable
- AppContext: FolderDiscoveryStatus type, FolderState interface, folderStates Map
- Three new reducer actions: ADD_FOLDER_SCANNING, UPDATE_FOLDER_STATE, COLLAPSE_FOLDER
- DropZone: classify paths via IPC, expand folders, mixed-drop ordering (loose first)
- FolderRow: discoveryStatus prop, middleTruncatePath display, aria-live count, pulse animation
- folder_row.css: 36px height, 14px font, scanning pulse, prefers-reduced-motion gate
- FileTable: passes discoveryStatus from folderStates, includes scanning folders with no files
- Empty folders auto-collapse after 1.5s, skip toast for unreadable folders
* feat(07-02): LanguageDropdown component, I18nContext hot-swap, SettingsDrawer integration
- Create LanguageDropdown with 25 languages + System option in native script
- Full keyboard navigation (Arrow, Enter, Escape, Home, End)
- ARIA listbox with role=option, aria-selected, aria-expanded
- Create language_dropdown.css with 280px max-height, animation, prefers-reduced-motion
- Update I18nContext to listen for onLanguageChanged and hot-swap locale
- Add Language section to SettingsDrawer between Appearance and toggles
* feat(07-03): file reveal in system file manager + StatusBar Clear button
- Add file:reveal and file:reveal-context-menu IPC channels with Zod validation
- Create reveal_handlers.ts with shell.showItemInFolder and existsSync checks
- Add RevealApi to preload with showInFolder and showContextMenu methods
- Add reveal icon (fade-on-hover) to FileRow for completed files
- Context menu for save-as-copy mode with Reveal Cleaned Copy / Reveal Original
- Rename StatusBar button from "Clean more" to "Clear" with aria-label
- Wire saveAsCopy setting into FileTable for reveal behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(07-03): restore file list scrollbar and align folder/file chevrons
- Fix scrollbar by changing drop-zone min-height from 100vh to 0, allowing
flex children to shrink and file-table__body overflow-y: auto to activate
- Align folder row chevron with file row chevron by reusing shared ChevronIcon
component (12px) instead of custom 16px SVG, and using flex-start alignment
in the toggle button instead of center
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(07-03): add min-height:0 to .file-table to restore scrollbar
* fix(07-03): folder icon, subfolder collapse, reveal position, statusbar i18n, Clear crash
- Add folder icon SVG to FolderRow for visual distinction from file rows
- Fix subfolder rows showing when parent folder is collapsed
- Move reveal icon from name cell to after cell (rightmost, always visible)
- Translate status bar strings (interpolated i18n keys with French)
- Add missing French translations for all untranslated keys
- Fix Clear button crash: dispatch was not destructured in AppContent
* feat(08-01): install Playwright and create E2E test infrastructure
- Install @playwright/test with browser download skipped
- Add playwright.config.ts (workers:1, retries:0, 10s timeout)
- Create 4 shared helpers: app_launcher, fixture_copier, metadata_assertions, wait_for_processing
- Create fixture generation script and 9 fixture files (JPEG/PNG/PDF/MP4/WebP + error fixtures)
- Add test:e2e and test:all scripts to package.json
* feat(08-01): add app-launch E2E spec with 4 passing tests
- Window visibility, title, empty state content, DevTools closed
- Console.error monitoring with ExifTool noise filtering
- App launcher uses development env for correct resource paths
- Waits for React mount before returning window handle
* chore(08-01): add Playwright output dirs to gitignore
- playwright-report/ and test-results/ are generated artifacts
* feat(08-02): add file-processing and error-handling E2E specs
- file-processing.spec.ts: 6 tests covering single file, batch, type pills,
status bar, Clean more cycle, and drag-over CSS class
- error-handling.spec.ts: 4 tests covering corrupted file, zero-byte file,
unsupported format filtering, and recovery after problematic files
- Updated wait_for_processing helper to poll for spinner absence (more reliable)
- Updated metadata_assertions with PNG/video/PDF structural tag names
* feat(08-02): add settings, folder-recursion, and metadata-inspection E2E specs
- settings.spec.ts: 6 tests covering drawer open/close, 3 toggle switches
(orientation, timestamps, xattr with macOS skip), orientation pipeline,
and save-as-copy setting propagation
- folder-recursion.spec.ts: 2 tests covering nested folder expansion via
folder:expand IPC and flat path verification
- metadata-inspection.spec.ts: 3 tests covering expansion panel visibility,
metadata group fields with known tags, and removed/preserved indicators
* feat(08-03): add dark-mode, i18n, security, and accessibility E2E specs
- dark-mode.spec.ts: 4 tests (activate, render check, switch back, system theme)
- i18n.spec.ts: 3 tests (English default, French switch, Japanese switch)
- security.spec.ts: 2 tests (CSP meta tag, navigation blocking without sleeps)
- accessibility.spec.ts: 4 tests (tab navigation, focus visible, ARIA roles, focus trap)
* feat(08-03): add E2E README, optimize suite for 30s budget, validate stability
- README.md with run/add/fixture/helper/spec documentation
- Optimized new specs to use beforeAll/afterAll (shared app instance)
- Reduced waitForTimeout values for faster execution
- Full 10-spec suite: 38 tests in ~27s (under 30s budget)
- 5 consecutive runs: all green (zero flaky tests)
- 199 Vitest unit tests unaffected
* chore(09-01): commit ExifTool v13.50 binaries, update gitignore, delete Travis CI
- Track ExifTool nix/win binaries in git (required for CI, Windows lacks Perl)
- Update .gitignore to allow .resources binaries and .github/ directory
- Remove .travis.yml (replaced by GitHub Actions in Plan 03)
- Remove !.travis.yml gitignore negation, add !.github/ negation
* chore(09-02): upgrade electron-builder from v22 to v26
- electron-builder ^22.8 -> ^26 (installed 26.8.1)
- Local macOS build verified: 119MB .dmg produced successfully
- All 199 tests pass, typecheck clean
- No build config changes required
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci(09-03): add GitHub Actions CI workflow with cross-platform builds
* feat(10-01): configure macOS code signing, notarization, and universal binary
- Create entitlements.mac.plist with JIT, unsigned-executable-memory, and user-selected file access
- Create entitlements.mac.inherit.plist with same entitlements for child processes
- Update build.mac config: universal arch, hardenedRuntime, entitlements paths, notarize
- Simplify build.dmg by removing custom contents array (D-04)
- Retain packmac identity=null for local unsigned builds (D-19)
* docs(10-01): add SIGNING-SETUP.md maintainer guide for code signing
- Apple Developer enrollment and Developer ID Application certificate steps
- App Store Connect API key generation for notarization
- GitHub Secrets configuration (5 signing + 1 optional Homebrew)
- Release triggering instructions (workflow_dispatch)
- Signed build verification commands (codesign, spctl, stapler)
- Windows signing deferral documented per D-07
* feat(10-02): create release.yml workflow with signing, notarization, checksums, draft release
- Manual workflow_dispatch trigger only (CI-09)
- Test gate re-runs full suite before building (D-15)
- macOS build imports cert to temp keychain, signs + notarizes via electron-builder
- Windows and Linux builds produce unsigned artifacts
- Release job generates SHASUMS256.txt (CI-08), creates git tag, draft release
- Concurrency group prevents parallel release runs
* feat(10-02): create homebrew-cask.yml workflow for auto Homebrew PR on release
- Triggers on release:published event (decoupled from release workflow)
- Uses macauley/action-homebrew-bump-cask@v1 to auto-submit PR to homebrew-cask
- Requires HOMEBREW_TOKEN secret (GitHub PAT with public_repo scope)
* feat(11-02): add Playwright screenshot generation script
- Create scripts/generate-screenshots.ts that captures 5 app states
- Screenshots: light-processed, dark-processed, settings-open, metadata-diff, language-switch
- Outputs to exifcleaner-website/static/images/screenshots/ (no window chrome -- added via CSS in Plan 03)
- Add 'screenshots' script to package.json
* fix(11-02): improve screenshot script — more files, smaller window, English locale
- Increase fixture files from 4 to 12 with realistic filenames to fill the table
- Reduce window size from 1200x800 to 960x700 for better marketing appearance
- Force English language via settings API before taking screenshots
- Use Japanese (not system locale) for language-switch screenshot only
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 line
35 B
Text
1 line
35 B
Text
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|