|
1 | 1 | # DOC-RECONCILIATION.md |
2 | 2 |
|
3 | | -This file was generated by the `/doc-truth-up` documentation-reconciliation pass, which treats the |
4 | | -code as read-only ground truth and edits only documentation so it reflects the repo's actual current |
5 | | -state. No code was changed and no builds or tests were run. It exists so the operator can audit |
6 | | -every finding in one place. |
| 3 | +This file was generated by the `/doc-truth-up` documentation-reconciliation pass. It treats the |
| 4 | +code as read-only ground truth and edits only documentation (`README.md`, `CLAUDE.md`, |
| 5 | +`DOC-RECONCILIATION.md`) so they reflect the repo's actual current state. No code was changed and |
| 6 | +no builds or tests were run. It exists so the operator can audit every finding in one place. |
| 7 | + |
| 8 | +A prior reconciliation pass ran on 2026-05-30 (HEAD `db25f8b`). That pass corrected README feature |
| 9 | +overclaims and CLAUDE.md stack version numbers. Those fixes are in the tree; this pass picks up |
| 10 | +remaining drift at HEAD `0430ce7`. |
7 | 11 |
|
8 | 12 | --- |
9 | 13 |
|
10 | 14 | ## Per-Claim Findings |
11 | 15 |
|
12 | | -### 1. What it is |
| 16 | +### 1. What It Is |
| 17 | + |
| 18 | +**Status: `consistent`** |
| 19 | +**Evidence: verified-by-reading-code** |
13 | 20 |
|
14 | | -**Status:** `consistent` |
15 | | -**Evidence:** `Afterimage/App/AfterimageApp.swift` — `@main struct AfterimageApp`, `RootView` |
16 | | -navigation confirms the core identity: camera → matching → slider reveal. |
17 | | -`Afterimage/App/AppState.swift` — navigation state machine (`.camera`, `.matching`, `.comparison`) |
18 | | -matches the overview in README and CLAUDE.md. `Afterimage/Data/Database/DatabaseManager.swift` — |
19 | | -opens `photos.db` read-only via `DatabasePool`, confirming the "no backend, bundled SQLite" claim. |
| 21 | +README.md and CLAUDE.md accurately describe Afterimage: a free iPhone-only iOS app that matches a |
| 22 | +user photo to a geolocated historical photograph using a bundled SQLite index, with a draggable |
| 23 | +slider as the core UX. Verified against `Afterimage/App/AfterimageApp.swift` (navigation state |
| 24 | +machine), `Afterimage/Features/Comparison/SliderOverlayView.swift` (drag gesture + overlay), |
| 25 | +`Afterimage/Data/Database/DatabaseManager.swift` (read-only `DatabasePool`). |
20 | 26 |
|
21 | | -No changes made. |
| 27 | +One feature was **missing** from README.md — city browse mode — which is fully implemented in |
| 28 | +`Afterimage/Features/Gallery/CitySelectorView.swift` and wired into the navigation state machine |
| 29 | +in `Afterimage/App/AppState.swift:84–91`. Fixed in claim 2 below. |
22 | 30 |
|
23 | 31 | --- |
24 | 32 |
|
25 | | -### 2. Current state |
| 33 | +### 2. Current State (Phase / Completeness) |
26 | 34 |
|
27 | | -**Status:** `drifted` (three README features not implemented) |
| 35 | +**Status: `drifted` → fixed** |
| 36 | +**Evidence: verified-by-reading-code** |
28 | 37 |
|
29 | | -The README Features list claimed three capabilities that do not exist in the source tree: |
| 38 | +Both occurrences of the "Current phase" label in `CLAUDE.md` read **Phase 1: Core App — Camera → |
| 39 | +Match → Slider**. Reading the source tree shows Phases 0, 1, and 2 are entirely complete: |
30 | 40 |
|
31 | | -| Claimed feature | Verdict | Evidence | |
32 | | -|---|---|---| |
33 | | -| "Three comparison modes — draggable slider (hero), side-by-side, and animated crossfade" | **drifted** | Only `SliderOverlayView.swift` exists in `Afterimage/Features/Comparison/`. No `SideBySideView.swift` or `FadeView.swift` anywhere in the project. `ComparisonView.swift` has no mode-switching UI. | |
34 | | -| "Multi-match browsing — up to 5 candidates in a horizontal thumbnail strip" | **drifted** | `AppState.swift:124–128` takes only `candidates.first` and passes one `MatchCandidate` to `ComparisonView`. `ComparisonView.swift` accepts a single `match: MatchCandidate`. No thumbnail strip UI exists. | |
35 | | -| "Share sheet — exports a 1200×800 composite JPEG of then-and-now" | **drifted** | No `ShareCompositor.swift` or `ShareSheetView.swift` in the project. No share button in `ComparisonView.swift`. | |
| 41 | +| Phase | Status | Evidence | |
| 42 | +|-------|--------|----------| |
| 43 | +| Phase 0 — Data Pipeline | **complete** | `DataPipeline/ingest_oldnyc.py`, `ingest_wikimedia.py`, `ingest_flickr.py` (contingency), `build_index.py`, `audit_coverage.py`, `config.py` all present; `DataPipeline/output/photos.db` and staging CSVs exist | |
| 44 | +| Phase 1 — Core App | **complete** | All named source files present and fully implemented: `CameraView`, `CameraViewModel`, `CameraCoordinator`, `LocationService`, `SpatialQuery`, `HeadingFilter`, `ThumbnailFetcher`, `VisionRanker`, `MatchingService`, `SliderOverlayView`, `ComparisonView`; unit tests for all five core components | |
| 45 | +| Phase 2 — Gallery + Expanded Cities | **complete** | `GalleryPickerView.swift`, `LocationPickerView.swift` (GPS-less location picker), `CitySelectorView.swift` (city browse mode), and all 6 city centers hardcoded in `AppState.swift:28–35` | |
| 46 | +| Phase 3 — Confidence UI + Polish | **not started** | `ConfidenceLabel` enum exists in the model (`HistoricalPhoto.swift:66–70`) and is computed in `VisionRanker.swift`, but no badge overlay in `SliderOverlayView.swift` or `ComparisonView.swift`; no multi-match thumbnail strip; no onboarding modal anywhere in the source tree | |
| 47 | +| Phase 4 — Share Sheet | **not started** | No `ShareCompositor.swift` or `ShareSheetView.swift` present; no share button in `ComparisonView.swift` | |
36 | 48 |
|
37 | | -Implemented and correctly described features (consistent): |
38 | | -- Live camera matching — `MatchingService.swift` orchestrates the four-stage pipeline |
39 | | -- Four-stage pipeline — `SpatialQuery.swift`, `HeadingFilter.swift`, `ThumbnailFetcher.swift`, `VisionRanker.swift` all exist and are wired in `MatchingService.swift` |
40 | | -- Composite scoring (70%/30%) — `VisionRanker.swift` implements this exactly |
41 | | -- Camera roll matching — `GalleryPickerView.swift` + `LocationPickerView.swift` handle GPS-present and GPS-absent cases |
| 49 | +**Changes made to `CLAUDE.md` (two occurrences):** |
| 50 | +- `## Build / Test / Run` section: |
| 51 | + `Current phase: **Phase 1: Core App — Camera → Match → Slider**` |
| 52 | + → `Current phase: **Phase 3: Confidence UI + Polish** (Phases 0–2 complete)` |
| 53 | +- `## Current State` block (portfolio-context region): same string, same fix. |
42 | 54 |
|
43 | | -**Changes made to `README.md`:** |
44 | | -- Removed: `"Three comparison modes — draggable slider (hero), side-by-side, and animated crossfade"` |
45 | | - → replaced with: `"Draggable slider — reveals the historical image beneath the present-day photo"` |
46 | | -- Removed: `"Multi-match browsing — up to 5 candidates in a horizontal thumbnail strip"` (entire bullet) |
47 | | -- Removed: `"Share sheet — exports a 1200×800 composite JPEG of then-and-now"` (entire bullet) |
48 | | -- Updated camera roll bullet to mention the manual location picker (implemented in `LocationPickerView.swift`) |
| 55 | +**Change made to `README.md`:** |
| 56 | +Added missing bullet to Features list: |
| 57 | +`- **City browse mode** — explore historical photos for any of 6 covered US cities (NYC, SF, Chicago, DC, New Orleans, Boston) without taking a photo` |
49 | 58 |
|
50 | 59 | --- |
51 | 60 |
|
52 | 61 | ### 3. Stack |
53 | 62 |
|
54 | | -**Status:** `drifted` in CLAUDE.md; `consistent` in README.md |
| 63 | +**Status: `consistent`** |
| 64 | +**Evidence: verified-by-reading-code** |
55 | 65 |
|
56 | 66 | `Afterimage.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved` pins: |
57 | | -- GRDB.swift → **7.10.0** |
58 | | -- Kingfisher → **8.8.0** |
59 | | - |
60 | | -README.md already said "GRDB.swift 7.x" and "Kingfisher 8.x" — consistent. |
| 67 | +- GRDB.swift → **7.10.0** — README says "GRDB.swift 7.x", CLAUDE.md says "GRDB.swift 7.x" ✓ |
| 68 | +- Kingfisher → **8.8.0** — README says "Kingfisher 8.x", CLAUDE.md says "Kingfisher 8.x" ✓ |
61 | 69 |
|
62 | | -CLAUDE.md (Tech Stack section and Portfolio Context > Stack section) said "GRDB.swift 6.x" and |
63 | | -"Kingfisher 7.x" in both occurrences — one major version behind in each. |
64 | | - |
65 | | -**Changes made to `CLAUDE.md`:** |
66 | | -- `GRDB.swift 6.x` → `GRDB.swift 7.x` (Tech Stack section, line ~9) |
67 | | -- `Kingfisher 7.x` → `Kingfisher 8.x` (Tech Stack section, line ~10) |
68 | | -- `GRDB.swift 6.x` → `GRDB.swift 7.x` (Portfolio Context > Stack section, line ~67) |
69 | | -- `Kingfisher 7.x` → `Kingfisher 8.x` (Portfolio Context > Stack section, line ~68) |
| 70 | +(These version numbers were corrected by the 2026-05-30 pass and remain accurate.) |
70 | 71 |
|
71 | 72 | --- |
72 | 73 |
|
73 | | -### 4. How to run |
| 74 | +### 4. How to Run |
74 | 75 |
|
75 | | -**Status:** `consistent` |
76 | | -**Evidence:** `Afterimage.xcodeproj/` directory confirmed present (Package.resolved lives inside it). |
77 | | -README Quick Start shows `open Afterimage.xcodeproj` and "Build and run on a physical iPhone." |
78 | | -No build scripts to verify; build command is Xcode UI only, which is the conventional approach for |
79 | | -iOS projects without a Makefile or CI script at the root. |
| 76 | +**Status: `consistent`** |
| 77 | +**Evidence: verified-by-reading-code** |
80 | 78 |
|
81 | | -No changes made. |
| 79 | +README Quick Start documents `git clone … && open Afterimage.xcodeproj`. The Xcode project |
| 80 | +directory is confirmed present (`Afterimage.xcodeproj/project.xcworkspace/…/Package.resolved` |
| 81 | +exists). No Makefile, CI script, or other build surface exists — Xcode-only is correct for this |
| 82 | +project type. Prerequisites (Xcode 16+, iOS 17, physical iPhone) are unverifiable from source |
| 83 | +alone but are not contradicted by any file. |
82 | 84 |
|
83 | 85 | --- |
84 | 86 |
|
85 | | -### 5. Known risks |
| 87 | +### 5. Known Risks |
86 | 88 |
|
87 | | -**Status:** `consistent` (where verifiable) |
88 | | -**Evidence:** `DatabaseManager.swift:13` — `config.readonly = true` confirms the "never write to |
89 | | -photos.db" rule. `AppState.swift` — no network transmissions of user data. Camera/location |
90 | | -permission requests are deferred to user action, not app launch (verified in `CameraView.swift` and |
91 | | -`AppState.onPhotoCaptured`). These match the Do NOT constraints in CLAUDE.md exactly. |
| 89 | +**Status: `consistent`** (where verifiable) |
| 90 | +**Evidence: verified-by-reading-code** |
92 | 91 |
|
93 | | -Performance and coverage claims (e.g., "<5s matching", "≥25% grid coverage") are runtime |
94 | | -assertions not checkable by reading source — marked `unverifiable-because-requires-device-or-data`. |
| 92 | +CLAUDE.md's risk list (read-only `photos.db`, no off-device data, deferred permission requests, |
| 93 | +grayscale Vision pre-processing, no Combine, Phase 0 scope gate) all match code constraints: |
| 94 | +- Read-only database: `DatabaseManager.swift` opens a `DatabasePool` with `readonly = true` |
| 95 | +- Deferred permissions: camera/location requests live in `CameraView` and `AppState.onPhotoCaptured`, not in `AfterimageApp.init()` |
| 96 | +- Grayscale requirement: enforced in `VisionRanker.swift` |
95 | 97 |
|
96 | | -No changes made. |
| 98 | +Performance claims ("< 5s matching", "≥ 25% grid coverage") and physical-device behaviour are |
| 99 | +`unverifiable-because-requires-device-or-data` — left unchanged. |
97 | 100 |
|
98 | 101 | --- |
99 | 102 |
|
100 | | -### 6. Next move |
101 | | - |
102 | | -**Status:** `consistent` (roadmap accurately reflects what is and isn't built) |
103 | | -**Evidence:** Cross-referencing source files against IMPLEMENTATION-ROADMAP.md phases: |
104 | | -- Phase 0 (data pipeline): Python scripts not in the repo root — `DataPipeline/` directory not |
105 | | - present in the source tree. Marked `unverifiable` — pipeline may have been run externally. |
106 | | -- Phase 1 (core app): All named Swift files exist and are wired correctly. |
107 | | -- Phase 2 (gallery + expanded cities): `GalleryPickerView.swift`, `LocationPickerView.swift`, |
108 | | - `CitySelectorView.swift` all exist; `GalleryMatchViewModel.swift` does not (replaced by |
109 | | - `LocationPickerView.swift` + `AppState` logic — functionally equivalent). |
110 | | -- Phase 3 (confidence UI, multi-match, onboarding, haptics): **Not implemented.** Confidence |
111 | | - labels exist in the data model (`MatchCandidate.confidenceLabel`) but no badge UI in |
112 | | - `ComparisonView`. Multi-match UI, onboarding modal, and haptics absent from source. |
113 | | -- Phase 4 (share sheet, App Store submission): **Not implemented.** No share code present. |
| 103 | +### 6. Next Move |
114 | 104 |
|
115 | | -CLAUDE.md says "Current Phase: Phase 1" which is the most honest current description given that |
116 | | -Phase 1's core flows are built but Phase 3/4 polish items remain. This is `consistent`. |
| 105 | +**Status: `drifted` → addressed by phase-label fix above** |
| 106 | +**Evidence: verified-by-reading-code** |
117 | 107 |
|
118 | | -No changes made. |
| 108 | +The corrected phase label in claim 2 is the "next move" signal. Concretely absent from the source |
| 109 | +tree (Phase 3 and 4 work): |
| 110 | +- `Afterimage/Features/Comparison/SideBySideView.swift` — not present |
| 111 | +- `Afterimage/Features/Comparison/FadeView.swift` — not present |
| 112 | +- `Afterimage/Features/Share/ShareCompositor.swift` — not present |
| 113 | +- `Afterimage/Features/Share/ShareSheetView.swift` — not present |
| 114 | +- Confidence badge overlay in `SliderOverlayView` / `ComparisonView` — not present |
| 115 | +- Multi-match thumbnail strip — not present |
| 116 | +- First-launch onboarding modal — not present |
119 | 117 |
|
120 | 118 | --- |
121 | 119 |
|
122 | 120 | ## Contradictions for Manual Review |
123 | 121 |
|
124 | | -These are drifts found in files this pass is not permitted to edit. A human should apply the fixes. |
| 122 | +These drifts are in files outside the editable set (`IMPLEMENTATION-ROADMAP.md`, |
| 123 | +`APPSTORE-METADATA.md`). A human should apply the fixes. |
125 | 124 |
|
126 | 125 | ### IMPLEMENTATION-ROADMAP.md |
127 | 126 |
|
128 | 127 | | Location | What is wrong | One-line fix | |
129 | | -|---|---|---| |
130 | | -| Line 234 (`from: "6.0.0"` for GRDB.swift) | GRDB.swift is pinned at 7.10.0 in Package.resolved | Change `from: "6.0.0"` to `from: "7.0.0"` | |
131 | | -| Line 238 (`from: "7.0.0"` for Kingfisher) | Kingfisher is pinned at 8.8.0 in Package.resolved | Change `from: "7.0.0"` to `from: "8.0.0"` | |
132 | | -| Lines 173–186 (Swift type definitions) | `thumbnailURL: URL` and `fullResURL: URL?` — actual code (`HistoricalPhoto.swift:29,31`) uses `String` for both | Change `URL` → `String` and `URL?` → `String?` in the type definition block | |
133 | | -| Lines 128–137 (SQL schema) | Schema does not include the `city TEXT` column | Add `city TEXT,` after the `lon REAL NOT NULL,` line | |
134 | | -| Lines 112–114 (AfterimageTests listing) | Lists 3 test files; actual suite has 6: also `MatchingServiceTests.swift`, `DatabaseManagerTests.swift`, `TestHelpers.swift` | Add the three missing files to the listing | |
135 | | -| Lines 82–92 (file structure — Comparison/) | Lists `SideBySideView.swift` and `FadeView.swift` as existing files | Remove or mark as `[planned]` — these files do not exist | |
136 | | -| Lines 88–90 (file structure — Gallery/) | Lists `GalleryMatchViewModel.swift` | Remove or rename to `LocationPickerView.swift` — that is the actual file | |
137 | | -| Lines 90–92 (file structure — Share/) | Lists `ShareCompositor.swift` and `ShareSheetView.swift` | Remove or mark as `[planned]` — these files do not exist | |
| 128 | +|----------|---------------|--------------| |
| 129 | +| Line ~234 — GRDB dependency | Shows `from: "6.0.0"` | Change to `from: "7.0.0"` — Package.resolved pins 7.10.0 | |
| 130 | +| Line ~238 — Kingfisher dependency | Shows `from: "7.0.0"` | Change to `from: "8.0.0"` — Package.resolved pins 8.8.0 | |
| 131 | +| Line ~63 — file structure | Source dir shown as `afterimage/` (lowercase) | Change to `Afterimage/` — actual Xcode target directory is PascalCase | |
| 132 | +| Lines ~105–109 — file structure | Lists `ingest_loc.py` and `ingest_nypl.py` as DataPipeline scripts | Remove both — neither file exists on disk; both sources were dropped (noted in the API Contracts section of the same document) | |
| 133 | +| Line ~242 — pip install command | Says `pip install aiohttp requests Pillow tqdm geojson` | Remove `Pillow` and `geojson` — `DataPipeline/requirements.txt` contains only `aiohttp`, `requests`, `tqdm` | |
| 134 | +| Lines ~173–180 — Swift type definitions | `thumbnailURL: URL` and `fullResURL: URL?` | Change both to `String` and `String?` — `Afterimage/Data/Models/HistoricalPhoto.swift:32–33` uses `String` | |
| 135 | +| Lines ~172 — Swift type definitions | `HistoricalPhoto` struct is missing `city: String?` | Add `let city: String?` between `lon` and `heading` — present in `HistoricalPhoto.swift:30` and in `DataPipeline/config.py:52` staging columns | |
138 | 136 |
|
139 | 137 | ### APPSTORE-METADATA.md |
140 | 138 |
|
141 | 139 | | Location | What is wrong | One-line fix | |
142 | | -|---|---|---| |
143 | | -| Lines 116–120 (App Review Notes) | "Tap 'Gallery' and browse the bundled historical photos" / "pick a historical photo and navigate to its exact location" misdescribes the Gallery button, which opens the user's camera roll (not a browsable historical archive) | Rewrite to: "Tap the photo icon to pick from your camera roll; if the photo has GPS data it matches immediately, otherwise a map picker appears to pin a location." | |
| 140 | +|----------|---------------|--------------| |
| 141 | +| Lines ~116–120 — App Review Notes | "Tap 'Gallery' and browse the bundled historical photos" / "Tap any photo to see its metadata and navigate to its location" — describes the Gallery button as opening a historical photo browser | Rewrite: Gallery opens the user's camera roll via `PHPickerViewController`; selecting a GPS-tagged photo triggers matching immediately; selecting a GPS-less photo shows a map pin picker | |
| 142 | +| Line ~68 — Support URL | `https://github.com/d/Afterimage` is a placeholder | Update to the real repo URL (`https://github.com/saagpatel/Afterimage` per README.md) | |
144 | 143 |
|
145 | 144 | --- |
146 | 145 |
|
147 | 146 | ## Footer |
148 | 147 |
|
149 | | -- **Run date/time:** 2026-05-30 19:12:52 PDT |
150 | | -- **Branch:** docs/truth-up-2026-05-30 |
151 | | -- **HEAD sha reconciled against:** db25f8b46d2ade91d7012dcfb0210b73672a10fb |
| 148 | +**Generated:** 2026-06-02 19:29:42 PDT |
| 149 | +**Branch:** docs/truth-up-2026-06-02 |
| 150 | +**HEAD reconciled against:** 0430ce753d8ed96029dd527ac961a8ac6a983b5b |
0 commit comments