···11+# CLAUDE.md
22+33+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
44+55+## Project Overview
66+77+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.
88+99+## Building and Running
1010+1111+```bash
1212+# Build the project
1313+xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge build
1414+1515+# Run the app (after building in Xcode)
1616+open ZipMerge.xcodeproj
1717+# Then press Cmd+R in Xcode to run
1818+```
1919+2020+The project uses Swift 5.0 and requires macOS with SwiftUI support.
2121+2222+## Code Signing and Installation
2323+2424+The project is configured with:
2525+- **Bundle Identifier**: com.singlefeather.ZipMerge
2626+- **Development Team**: M67B42LX8D
2727+- **Code Sign Style**: Automatic
2828+- **Code Sign Identity**: Currently ad-hoc (for local development)
2929+3030+### Building for Local Development
3131+3232+```bash
3333+# Build the project
3434+xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge -configuration Release clean build
3535+3636+# The built app will be in:
3737+# ./build/Release/ZipMerge.app
3838+3939+# Copy to Applications folder (optional)
4040+cp -r build/Release/ZipMerge.app /Applications/
4141+```
4242+4343+### Automated Release Pipeline
4444+4545+**See RELEASING.md for complete setup instructions.**
4646+4747+The project uses GitHub Actions for automated releases. When you push a version tag, it automatically:
4848+1. Builds with Developer ID signing
4949+2. Notarizes with Apple
5050+3. Creates GitHub release with signed .zip
5151+4. Outputs SHA256 for Homebrew cask
5252+5353+**Quick release process:**
5454+```bash
5555+git tag v1.0.0
5656+git push origin v1.0.0
5757+```
5858+5959+Then update the SHA256 in `/Users/jsp/dev/projects/homebrew-tap/Casks/zipmerge.rb` from the release notes.
6060+6161+**Required GitHub Secrets** (one-time setup - see RELEASING.md):
6262+- `CERTIFICATE_BASE64` - Developer ID certificate
6363+- `CERTIFICATE_PASSWORD` - Certificate password
6464+- `APPLE_ID` - Apple ID email
6565+- `APPLE_ID_PASSWORD` - App-specific password for notarization
6666+6767+### Distributing via Homebrew
6868+6969+The Homebrew cask is located at `/Users/jsp/dev/projects/homebrew-tap/Casks/zipmerge.rb`.
7070+7171+After each release, update the SHA256 hash in the cask file with the value from the release notes.
7272+7373+**Users can install via:**
7474+```bash
7575+brew tap jaspermayone/tap
7676+brew install --cask zipmerge
7777+```
7878+7979+### Manual Distribution (Without CI)
8080+8181+If you need to build manually:
8282+8383+```bash
8484+# Build with Developer ID signing
8585+xcodebuild -project ZipMerge.xcodeproj -scheme ZipMerge -configuration Release \
8686+ CODE_SIGN_IDENTITY="Developer ID Application" clean build
8787+8888+# Notarize
8989+xcrun notarytool submit build/Release/ZipMerge.app.zip \
9090+ --apple-id your-email@example.com \
9191+ --team-id M67B42LX8D \
9292+ --password app-specific-password \
9393+ --wait
9494+9595+# Staple and package
9696+xcrun stapler staple build/Release/ZipMerge.app
9797+cd build/Release && zip -r ZipMerge.zip ZipMerge.app
9898+```
9999+100100+## Architecture
101101+102102+### Core Components
103103+104104+**Models.swift** - Defines the data model:
105105+- `FileChangeType`: Enum for tracking file states (added, modified, deleted, unchanged)
106106+- `MergeDecision`: User decision per file (keepMine, takeTheirs, pending)
107107+- `ComparedFile`: Represents a single file comparison with diff hunks
108108+- `DiffHunk` and `DiffLine`: Structures for granular diff display (not yet fully implemented)
109109+- `ComparisonResult`: Contains all compared files and directories
110110+111111+**FileComparer.swift** - File operations and comparison logic:
112112+- `extractZip()`: Uses system `unzip` command to extract archives
113113+- `findRootDirectory()`: Handles zips with wrapper folders
114114+- `compare()`: Main comparison engine that walks both directory trees and categorizes changes
115115+- `applyChanges()`: Applies user merge decisions to the local directory
116116+- `computeHunks()`: Placeholder for future hunk-level merging (currently returns empty array)
117117+118118+**ContentView.swift** - Main UI orchestration:
119119+- Left panel: Directory picker, zip drop zone, and file list
120120+- Right panel: Diff view for selected file
121121+- State management for comparison results and merge decisions
122122+- Git integration: Detects git repos and offers commit creation after merge
123123+- File cleanup: Removes temp directories and zip files after successful merge
124124+125125+**DiffView.swift** - File diff visualization:
126126+- Side-by-side view for modified files
127127+- Single pane for added/deleted files with color-coded backgrounds
128128+- Line-by-line display with monospaced font
129129+130130+### Data Flow
131131+132132+1. User selects project directory and drops teacher's zip file
133133+2. `FileComparer.extractZip()` extracts to temp directory
134134+3. `FileComparer.compare()` walks both trees and generates `ComparisonResult`
135135+4. UI displays changed files with color-coded icons
136136+5. User reviews diffs and makes keepMine/takeTheirs decisions per file
137137+6. `FileComparer.applyChanges()` applies decisions
138138+7. If git repo detected, optionally creates commit
139139+8. Cleanup removes temp files and zip
140140+141141+### Important Implementation Details
142142+143143+- Zip extraction uses system `/usr/bin/unzip` command via Process
144144+- Comparison is byte-level for binaries, line-level diffs for text (when viewing)
145145+- Temp directories are created in system temp folder with UUID names
146146+- Git commits use `/usr/bin/git` directly (not libgit2 or similar)
147147+- All file operations happen on background queue, UI updates on main thread
148148+149149+## Known Limitations
150150+151151+- Hunk-level merging (`computeHunks()` and `applySelectedHunks()`) is stubbed but not implemented
152152+- Currently only supports whole-file merge decisions
153153+- No conflict resolution UI for line-level merging
154154+- Binary files show as "(binary or unreadable)" in diff view
+120
RELEASING.md
···11+# Release Process
22+33+This document describes the automated release pipeline for ZipMerge.
44+55+## Automated Release Pipeline
66+77+Releases are automated via GitHub Actions. When you push a version tag, the workflow will:
88+99+1. Build the app with Developer ID signing
1010+2. Notarize the app with Apple
1111+3. Create a GitHub release with the signed .zip
1212+4. Output the SHA256 hash for the Homebrew cask
1313+1414+## Required GitHub Secrets
1515+1616+Before creating your first release, set up these secrets in GitHub repository settings (Settings → Secrets and variables → Actions):
1717+1818+### 1. CERTIFICATE_BASE64
1919+2020+Your Developer ID Application certificate exported as base64.
2121+2222+**How to create:**
2323+2424+```bash
2525+# Export your Developer ID certificate from Keychain Access
2626+# 1. Open Keychain Access
2727+# 2. Find "Developer ID Application: <Your Name> (M67B42LX8D)"
2828+# 3. Right-click → Export "Developer ID Application..."
2929+# 4. Save as certificate.p12 with a password
3030+3131+# Convert to base64
3232+base64 -i certificate.p12 | pbcopy
3333+3434+# Paste the output as CERTIFICATE_BASE64 secret in GitHub
3535+# Then delete the certificate.p12 file
3636+rm certificate.p12
3737+```
3838+3939+### 2. CERTIFICATE_PASSWORD
4040+4141+The password you used when exporting the certificate.p12 file.
4242+4343+### 3. APPLE_ID
4444+4545+Your Apple ID email address (the one associated with your developer account).
4646+4747+### 4. APPLE_ID_PASSWORD
4848+4949+An app-specific password for notarization (NOT your Apple ID password).
5050+5151+**How to create:**
5252+5353+1. Go to https://appleid.apple.com/account/manage
5454+2. Sign in with your Apple ID
5555+3. In the Security section, under "App-Specific Passwords", click "Generate Password"
5656+4. Enter "GitHub Actions Notarization" as the name
5757+5. Copy the generated password and save it as APPLE_ID_PASSWORD in GitHub
5858+5959+## Creating a Release
6060+6161+Once secrets are configured:
6262+6363+```bash
6464+# Ensure you're on main branch with latest changes
6565+git checkout main
6666+git pull
6767+6868+# Create and push a version tag
6969+git tag v1.0.0
7070+git push origin v1.0.0
7171+```
7272+7373+The GitHub Actions workflow will automatically:
7474+- Build and sign the app
7575+- Notarize with Apple (takes 5-10 minutes)
7676+- Create a GitHub release with ZipMerge.zip
7777+- Include SHA256 hash in release notes
7878+7979+## Updating the Homebrew Cask
8080+8181+After the release completes:
8282+8383+1. Copy the SHA256 from the release notes
8484+2. Update `/Users/jsp/dev/projects/homebrew-tap/Casks/zipmerge.rb`:
8585+ ```ruby
8686+ sha256 "THE_SHA256_FROM_RELEASE_NOTES"
8787+ ```
8888+3. Update version if needed
8989+4. Commit and push to homebrew-tap:
9090+ ```bash
9191+ cd /Users/jsp/dev/projects/homebrew-tap
9292+ git add Casks/zipmerge.rb
9393+ git commit -m "Update zipmerge to v1.0.0"
9494+ git push
9595+ ```
9696+9797+Users can then install with:
9898+```bash
9999+brew tap jaspermayone/tap
100100+brew install --cask zipmerge
101101+```
102102+103103+## Troubleshooting
104104+105105+### Notarization fails
106106+107107+- Verify APPLE_ID and APPLE_ID_PASSWORD are correct
108108+- Ensure app-specific password hasn't expired
109109+- Check notarization logs in the GitHub Actions output
110110+111111+### Code signing fails
112112+113113+- Verify CERTIFICATE_BASE64 is correctly encoded
114114+- Ensure CERTIFICATE_PASSWORD matches the export password
115115+- Certificate must be "Developer ID Application" (not "Apple Development")
116116+117117+### Build fails
118118+119119+- Check that Xcode version on GitHub Actions supports your Swift version
120120+- Verify project builds locally first with `xcodebuild`