Skip to content

Commit e2006ed

Browse files
authored
Merge pull request #4 from saagpatel/feat/runnable-proof-doc
feat(docs): add runnable proof path + portfolio-context
2 parents c29f1ef + 3cb38f1 commit e2006ed

2 files changed

Lines changed: 283 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,53 @@ See IMPLEMENTATION-ROADMAP.md for full phase details and verification checklist.
4747
- Do not use Combine or callback-based async — async/await only
4848
- Do not add UI in Phase 0 — Phase 0 is data pipeline and SQLite index only
4949
- Do not widen Phase 0 to more than 2 cities until density audit passes (≥25% of 100m grid cells covered)
50+
51+
<!-- portfolio-context:start -->
52+
# Portfolio Context
53+
54+
## What This Project Is
55+
56+
Afterimage is a free iOS app (iPhone-only) that matches a photo you take — or select from your camera roll — to a geolocated historical photograph from the same location. The core interaction is a draggable vertical slider revealing the historical image beneath the present-day photo. All matching happens on-device against a bundled SQLite index; no backend, no accounts.
57+
58+
## Current State
59+
60+
**Phase 1: Core App — Camera → Match → Slider**
61+
See IMPLEMENTATION-ROADMAP.md for full phase details and verification checklist.
62+
63+
## Stack
64+
65+
- Language: Swift 5.10+
66+
- UI: SwiftUI (iOS 17+ minimum — no UIKit views except AVFoundation camera wrapper)
67+
- Database: SQLite via GRDB.swift 6.x — typed Swift wrappers, fast spatial queries
68+
- Image loading: Kingfisher 7.x — async fetch + disk cache for thumbnails
69+
- Image ML: Vision framework (`VNGenerateImageFeaturePrintRequest`) — on-device feature print similarity
70+
- Location: CoreLocation (CLLocationManager + CLHeading)
71+
- Camera: AVFoundation (photo capture pipeline)
72+
- Data pipeline: Python 3.12 + aiohttp + sqlite3 (dev-time only, not shipped)
73+
74+
## How To Run
75+
76+
- Swift: no force-unwraps (`!`) outside of fatalError/precondition; use `guard let` or `try?` with explicit fallback
77+
- File naming: PascalCase for Swift types and files, camelCase for variables
78+
- Architecture: feature-based folder structure (Features/Camera/, Features/Matching/, etc.)
79+
- No third-party analytics or crash reporting SDKs in v1
80+
- All async work via Swift async/await — no Combine, no callbacks
81+
- GRDB: always open `photos.db` as read-only `DatabasePool`
82+
- Vision: always preprocess images to grayscale before `VNGenerateImageFeaturePrintRequest`
83+
84+
## Known Risks
85+
86+
- Do not add features not in the current phase of IMPLEMENTATION-ROADMAP.md
87+
- Do not open `photos.db` as writable — it is a read-only bundled asset; never write user data to it
88+
- Do not transmit user photos, location data, or any usage telemetry off-device
89+
- Do not request camera or location permissions on app launch — only when the user first taps camera/gallery
90+
- Do not run `VNGenerateImageFeaturePrintRequest` on color images — always convert to grayscale first
91+
- Do not use Combine or callback-based async — async/await only
92+
- Do not add UI in Phase 0 — Phase 0 is data pipeline and SQLite index only
93+
- Do not widen Phase 0 to more than 2 cities until density audit passes (≥25% of 100m grid cells covered)
94+
95+
## Next Recommended Move
96+
97+
Use this context plus the README and supporting docs to resume the next active task, then promote the repo beyond minimum-viable by capturing a dedicated handoff, roadmap, or discovery artifact.
98+
99+
<!-- portfolio-context:end -->

docs/RUNNABLE-PROOF.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Afterimage — Runnable Proof Path
2+
3+
A one-pass walkthrough from a clean checkout to a working in-simulator demo of
4+
the historical photo overlay. Each step has a command, an expected result, and
5+
a quick "if it fails" pointer.
6+
7+
> **Audience:** anyone resuming after a long pause, demoing the app to someone
8+
> else, or capturing a baseline before changes.
9+
10+
---
11+
12+
## 0. Prerequisites
13+
14+
- macOS (iOS toolchain required)
15+
- Xcode 26.3+ (matches `project.yml`)
16+
- Python 3.11+ (for `DataPipeline/`)
17+
- XcodeGen (`brew install xcodegen`) — `project.yml` is the source of truth
18+
- A real Apple developer account if you want to run on a device. Simulator
19+
works without one.
20+
21+
```bash
22+
xcodebuild -version
23+
xcodegen --version
24+
python3 --version
25+
```
26+
27+
---
28+
29+
## 1. Regenerate the Xcode project from `project.yml`
30+
31+
```bash
32+
cd /Users/d/Projects/Afterimage
33+
xcodegen generate
34+
```
35+
36+
**Expected:** `Afterimage.xcodeproj` is rebuilt against `project.yml`. No
37+
warnings.
38+
39+
**If it fails:** `xcodegen --quiet generate` to see clean errors; usually a
40+
missing folder or new file not declared in `project.yml`.
41+
42+
---
43+
44+
## 2. Build for simulator
45+
46+
```bash
47+
xcodebuild \
48+
-project Afterimage.xcodeproj \
49+
-scheme Afterimage \
50+
-configuration Debug \
51+
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=latest' \
52+
build
53+
```
54+
55+
**Expected:** `BUILD SUCCEEDED`. Warnings about deprecated APIs are OK if they
56+
are not new (compare against the last green commit `c29f1ef`).
57+
58+
**If it fails:**
59+
- Verify `DEVELOPMENT_TEAM` is set in `project.yml` (commit `95f2915`).
60+
- `xcodebuild -showsdks` to confirm an iOS 17+ SDK is available.
61+
- Clean the build with `xcodebuild clean` then retry.
62+
63+
---
64+
65+
## 3. Run the unit-test suite
66+
67+
```bash
68+
xcodebuild \
69+
-project Afterimage.xcodeproj \
70+
-scheme Afterimage \
71+
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=latest' \
72+
test
73+
```
74+
75+
**Expected:** `TEST SUCCEEDED`. Coverage includes:
76+
- `DatabaseManagerTests` — SQLite open + schema
77+
- `HeadingFilterTests` — heading window filter (45° default, skip when
78+
`headingAccuracy > 45°`)
79+
- `MatchingServiceTests` — composite score (70% geo + 30% vision)
80+
- `SpatialQueryTests` — bounding-box + Haversine ≤100m
81+
82+
**If it fails:** look at the test name; the test file is at
83+
`AfterimageTests/<TestName>.swift`. Most failures are fixture-data or
84+
GRDB-version-related.
85+
86+
---
87+
88+
## 4. Verify the data pipeline (optional but recommended)
89+
90+
The data pipeline builds the `photos.db` SQLite index that gets bundled into
91+
the app. It is **dev-time only** — not run on device.
92+
93+
```bash
94+
cd /Users/d/Projects/Afterimage/DataPipeline
95+
python3 -m venv .venv
96+
source .venv/bin/activate
97+
pip install -r requirements.txt
98+
99+
# Ingest one source as a smoke test (Wikimedia is the most stable)
100+
python3 ingest_wikimedia.py --city nyc
101+
102+
# Build the index from all ingested rows
103+
python3 build_index.py
104+
105+
# Coverage audit (gate that Phase 0 widens past 2 cities)
106+
python3 audit_coverage.py
107+
```
108+
109+
**Expected:**
110+
- `photos.db` produced under `DataPipeline/output/`
111+
- Coverage audit reports ≥25% of 100m grid cells covered for the cities you
112+
ingested
113+
- Commit `bfc2390` widened the pipeline to NYC, SF, Chicago — re-running with
114+
`--city all` should reproduce the 3-city dataset
115+
116+
**If it fails:**
117+
- Wikimedia/LoC APIs throttle — wait, retry. The ingest scripts log rate-limit
118+
hits.
119+
- Memory ballooning during index build → reduce batch size in
120+
`build_index.py`.
121+
122+
---
123+
124+
## 5. Replace the bundled `photos.db` (if you regenerated one in step 4)
125+
126+
```bash
127+
cp DataPipeline/output/photos.db Afterimage/Resources/photos.db
128+
```
129+
130+
Rebuild (step 2) so Xcode picks up the new bundle resource.
131+
132+
**Skip this step if you're demoing the as-shipped DB.** The bundle's existing
133+
`photos.db` is the one tied to the most recent commit.
134+
135+
---
136+
137+
## 6. Launch the app in simulator and walk the demo
138+
139+
```bash
140+
open -a Simulator
141+
# In the simulator: Features > Location > Custom Location...
142+
# NYC: 40.7128, -74.0060
143+
# SF: 37.7749, -122.4194
144+
# Chicago: 41.8781, -87.6298
145+
```
146+
147+
Then in Xcode: hit Run (Cmd-R) on the `Afterimage` scheme with the simulator
148+
selected.
149+
150+
### Demo flow
151+
152+
1. **Permission prompts** — Location, Camera. Accept both. Without Location,
153+
no spatial query; without Camera, only camera-roll mode works.
154+
2. **Capture or pick a photo**. For a quick simulator demo, use a known
155+
camera-roll photo set to the NYC location. (Simulator → Features > Photos
156+
> Add to Library, then set location via Features > Location.)
157+
3. **Wait for match**. The matching pipeline runs:
158+
- Spatial query (≤100m, ~20 candidates)
159+
- Heading filter (±45° if `headingAccuracy` is good)
160+
- Thumbnail fetch (Kingfisher, concurrent)
161+
- Vision feature-print ranking (composite score: 70% geo + 30% vision)
162+
4. **Slider reveal**. Drag the vertical slider on the comparison view. The
163+
historical image fades in beneath the present-day photo.
164+
5. **Share composite**. Tap Share → `UIActivityViewController` opens with the
165+
composite image rendered.
166+
167+
### What "works" looks like
168+
169+
- Match returns at least 1 candidate for the NYC test location
170+
- Slider drag is smooth (60 FPS)
171+
- Composite share renders both images
172+
- No crash, no permission loops
173+
174+
---
175+
176+
## 7. Verify the security/privacy posture
177+
178+
```bash
179+
# Privacy manifest present (commit 651a5f4)
180+
ls Afterimage/PrivacyInfo.xcprivacy
181+
182+
# DEVELOPMENT_TEAM set for App Store signing (commit 95f2915)
183+
grep DEVELOPMENT_TEAM project.yml
184+
185+
# No accounts / backend dependencies
186+
grep -r "https://api\." Afterimage --include='*.swift' | head -5
187+
# Expected: empty or only Wikimedia thumbnail fetches
188+
```
189+
190+
---
191+
192+
## 8. App Store metadata sanity (commit `c29f1ef`)
193+
194+
```bash
195+
cat APPSTORE-METADATA.md | head -20
196+
```
197+
198+
Verify subtitle, description, keywords, and privacy questions match the
199+
intended pitch. Screenshots are committed separately when ready.
200+
201+
---
202+
203+
## Build-proof source of truth
204+
205+
This checklist mirrors the build proof captured at commits:
206+
207+
- `bfc2390` — pipeline expanded to NYC, SF, Chicago
208+
- `78d9f1e` — vision: double continuation resume fix
209+
- `651a5f4` — privacy manifest
210+
- `95f2915` — DEVELOPMENT_TEAM for App Store signing
211+
- `c29f1ef` — App Store Connect metadata
212+
213+
If a step regresses, bisect against these commits.
214+
215+
---
216+
217+
## What "Phase 1" success means (per CLAUDE.md)
218+
219+
Phase 1 is "Core App — Camera → Match → Slider". Success criteria for the
220+
runnable proof:
221+
222+
| Capability | Verification step |
223+
|---|---|
224+
| Camera capture works | Step 6, action 2 |
225+
| Camera-roll picker works | Step 6, action 2 alternative |
226+
| MatchingService returns ≥1 candidate at known cities | Step 6, action 3 |
227+
| Slider overlay reveals historical image | Step 6, action 4 |
228+
| Share composite renders | Step 6, action 5 |
229+
| Privacy manifest present | Step 7 |
230+
| Tests pass | Step 3 |
231+
232+
Phase 1 widening (more cities, more sources, ML re-ranking refinements) is
233+
out-of-scope here; this doc only proves the current state runs.

0 commit comments

Comments
 (0)