CLAUDE.md#
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview#
ZipMerge is a macOS SwiftUI application that compares a local project directory with a teacher's submitted zip file. It's designed to help students merge changes from assignments when version control isn't used by instructors.
Building and Running#
# Build the project
xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge build
# Run the app (after building in Xcode)
open ZipMerge.xcodeproj
# Then press Cmd+R in Xcode to run
The project uses Swift 5.0 and requires macOS with SwiftUI support.
Code Signing and Installation#
The project is configured with:
- Bundle Identifier: com.singlefeather.ZipMerge
- Development Team: M67B42LX8D
- Code Sign Style: Automatic
- Code Sign Identity: Currently ad-hoc (for local development)
Building for Local Development#
# Build the project
xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge -configuration Release clean build
# The built app will be in:
# ./build/Release/ZipMerge.app
# Copy to Applications folder (optional)
cp -r build/Release/ZipMerge.app /Applications/
Release Process#
Use the local release script for simplicity:
./release.sh v1.0.0
This automated script handles everything:
- Builds with Developer ID signing
- Notarizes with Apple
- Creates GitHub release with signed .zip
- Calculates SHA256 hash
- Updates Homebrew cask automatically
- Commits and pushes cask update
Prerequisites:
ghCLI installed and authenticated- Developer ID Application certificate in Keychain
- Apple ID app-specific password (prompted during script)
The script is fully automated and requires no manual steps. See RELEASING.md for details.
Distributing via Homebrew#
The Homebrew cask is located at /Users/jsp/dev/projects/homebrew-tap/Casks/zipmerge.rb.
After each release, update the SHA256 hash in the cask file with the value from the release notes.
Users can install via:
brew tap jaspermayone/tap
brew install --cask zipmerge
Manual Distribution (Without CI)#
If you need to build manually:
# Build with Developer ID signing
xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge -configuration Release \
CODE_SIGN_IDENTITY="Developer ID Application" clean build
# Notarize
xcrun notarytool submit build/Release/ZipMerge.app.zip \
--apple-id your-email@example.com \
--team-id M67B42LX8D \
--password app-specific-password \
--wait
# Staple and package
xcrun stapler staple build/Release/ZipMerge.app
cd build/Release && zip -r ZipMerge.zip ZipMerge.app
Architecture#
Core Components#
Models.swift - Defines the data model:
FileChangeType: Enum for tracking file states (added, modified, deleted, unchanged)MergeDecision: User decision per file (keepMine, takeTheirs, pending)ComparedFile: Represents a single file comparison with diff hunksDiffHunkandDiffLine: Structures for granular diff display (not yet fully implemented)ComparisonResult: Contains all compared files and directories
FileComparer.swift - File operations and comparison logic:
extractZip(): Uses systemunzipcommand to extract archivesfindRootDirectory(): Handles zips with wrapper folderscompare(): Main comparison engine that walks both directory trees and categorizes changesapplyChanges(): Applies user merge decisions to the local directorycomputeHunks(): Placeholder for future hunk-level merging (currently returns empty array)
ContentView.swift - Main UI orchestration:
- Left panel: Directory picker, zip drop zone, and file list
- Right panel: Diff view for selected file
- State management for comparison results and merge decisions
- Git integration: Detects git repos and offers commit creation after merge
- File cleanup: Removes temp directories and zip files after successful merge
DiffView.swift - File diff visualization:
- Side-by-side view for modified files
- Single pane for added/deleted files with color-coded backgrounds
- Line-by-line display with monospaced font
Data Flow#
- User selects project directory and drops teacher's zip file
FileComparer.extractZip()extracts to temp directoryFileComparer.compare()walks both trees and generatesComparisonResult- UI displays changed files with color-coded icons
- User reviews diffs and makes keepMine/takeTheirs decisions per file
FileComparer.applyChanges()applies decisions- If git repo detected, optionally creates commit
- Cleanup removes temp files and zip
Important Implementation Details#
- Zip extraction uses system
/usr/bin/unzipcommand via Process - Comparison is byte-level for binaries, line-level diffs for text (when viewing)
- Temp directories are created in system temp folder with UUID names
- Git commits use
/usr/bin/gitdirectly (not libgit2 or similar) - All file operations happen on background queue, UI updates on main thread
Known Limitations#
- Hunk-level merging (
computeHunks()andapplySelectedHunks()) is stubbed but not implemented - Currently only supports whole-file merge decisions
- No conflict resolution UI for line-level merging
- Binary files show as "(binary or unreadable)" in diff view