← Keeper Product proposal · August 2026

What Keeper is, and how it gets built.

Nine kinds of photo clutter and what each is worth. How the app decides which frame out of sixteen is the one to keep. Ten screens. The five-pass pipeline that makes a 48,000-photo library tractable on a phone. And a straight list of the parts that might not work.

iOS 18 iPadOS 18 macOS 15 SwiftUI · PhotoKit · Vision No network entitlement

Photographs are the one kind of clutter that never regenerates

Most things that eat a disk are easy to reason about because they come back. A cache refills, a build folder rebuilds, a download can be downloaded again. Photos invert every part of that: they are simultaneously the largest thing on the device and the only thing on it that is genuinely irreplaceable. One wrong tap costs a person something no amount of freed space buys back.

So the product cannot be a bulk deleter. It has to be a culling assistant — what photographers have always done with a loupe and a grease pencil, done automatically for a library nobody has the evening to sit with. The core move is not “delete these forty photos”. It is “of these forty, this one is the keeper, and here is why”. The space is a consequence of the judgement, not the pitch.

The thesis in one line: the app never asks you to choose what to lose. It shows you what it would keep, and asks you to disagree.

Nine kinds of clutter, ranked by payoff

Biggest reclaim first — not alphabetically, and not by how clever the detector is. Figures are modelled on a 48,000-asset, 214 GB library: a heavy but ordinary six-year iPhone history.

CategoryHow it’s found, on deviceReclaimConfidence
Near-duplicate setsBursts, retries, “one more with everyone looking” Perceptual hash buckets by time and place, then Vision feature-print distance inside each bucket 61 GB
High — the maths is unambiguous
Screen recordingsThe worst bytes-per-item in any library A media subtype flag on the asset, not a guess 23 GB
Certain
Long-tail videoThe 22-minute school concert nobody replays Duration × bitrate ranked by bytes, flagged when never favourited, edited or filed 18 GB
Medium — needs your eyes
Expired screenshotsReceipts, codes, maps, boarding passes, memes Screenshot subtype, then Vision’s utility flag plus on-device text recognition to sort receipt from meme from keepsake 9 GB
High
Technically failed framesPocket shots, motion blur, lens-cap black Laplacian sharpness variance, luminance clipping, face capture quality when a face is present 6 GB
High for the extremes only
Recently DeletedAlready “deleted”, still on disk for 30 days The system’s own Recently Deleted collection, surfaced on day one 5 GB
Certain
Forwarded mediaSaved out of chat apps Non-camera source, absent EXIF camera fields, text-dense with a low aesthetic score 4 GB
Medium
Exact duplicatesRe-downloads, AirDrop round-trips, re-saves Byte length, then a hash of the first and last 64 KB, then a full hash only on collision 3 GB
Certain
Motionless Live PhotosA still with three wasted seconds welded on Frame-to-frame difference across the motion component below a threshold 2 GB
Medium — offered as “flatten”, not delete

Reclaim figures overlap slightly — a blurry frame inside a burst is counted once, in the set that claims it first. The app reconciles overlaps before it shows a total, because a headline number you can’t actually reach is the fastest way to lose someone’s trust.

What makes one frame the keeper

Grouping similar photos is solved arithmetic. Picking the best one is a taste judgement, and taste is where the app either earns its place or gets deleted in week two.

Tier 1 · your signals

What you already told Photos

Favourited. Edited. Cropped. Placed in an album. Set as a Memory key photo. Shared. These outrank every algorithm in the app — if you touched a frame, it wins its group by default, and Keeper says so rather than pretending it scored well.

Tier 2 · technical

Whether it’s a good photograph

Sharpness by Laplacian variance on the subject region, not the whole frame. Exposure clipping at both ends. Resolution and format, so a ProRAW original beats its shared JPEG. Vision’s aesthetics score. Face capture quality, eyes-open and expression from landmarks, weighted by face size.

Tier 3 · set context

Whether it’s the best of these

Most faces present, because the group shot with everyone in it is usually the reason the burst exists. Widest framing when the rest are crops of it. Latest in the burst, because the last frame is usually the one you waited for.

Ties do not get resolved silently. When the top two frames land within a few points of each other, Keeper says “these two are close” and pre-selects neither. A confident wrong answer costs more trust than an honest shrug.

The flow, eight screens deep

A capacity headline, rows sorted by payoff, a pre-selection you can argue with, and one confirmation that itemises exactly what leaves.

9:41▮▮▮ ⌁
Keeper reads your library. It cannot send it anywhere.
No network entitlementThe app has no permission to open a connection. Check it in Settings → Entitlements.
Nothing leaves the deviceEvery comparison runs on this iPhone’s Neural Engine.
Deletes are reversibleEverything goes to Recently Deleted. You keep 30 days.
Allow full library access
Keeper can’t spot a duplicate it isn’t allowed to see.
1 · The promise, up frontThe privacy claim is the first screen and it’s falsifiable. No carousel, no “Get Started”.
9:41▮▮▮ ⌁
PASS 2 OF 3 · FEATURE PRINTS
31,204 / 48,116
~6 MIN LEFTON DEVICE
Metadata pass48,116 assets read
Done
Feature printsGrouping similar frames
65%
·
Quality scoringOnly on grouped frames
Queued
Keeps indexing in the background while plugged in. You can close the app.
Review what’s ready (12.4 GB)
2 · Indexing is honest workThree named passes with real counts, resumable, and results usable before the scan finishes.
9:41▮▮▮ ⌁
RECOVERABLE FROM 214 GB
131 GB
SUGGESTED YOUR CALL KEEPING
Similar sets1,842 sets · 8,904 frames
61.4 GB
Screen recordingsOldest 3 years ago
22.8 GB
Long videosNever favourited
18.2 GB
Screenshots4,106 · mostly receipts
9.1 GB
Blurred & darkHigh confidence only
6.3 GB
🗑
Recently DeletedOn disk for 30 days
5.0 GB
REVIEW
HISTORY
SETTINGS
3 · Home, sorted by payoffThe three-part meter separates what Keeper suggests from what it refuses to decide for you.
9:41▮▮▮ ⌁
KEEPER
SHARPEST OF 16ALL 4 FACESEYES OPENWIDEST FRAMING
Tap any frame to promote it. Long-press to compare.
Keep 1 frameKaanapali · 14 Jul 2024 · 4:31pm
Delete 15 framesFrees 198 MB
Keep all
Accept & next
4 · The screen the app lives or dies onThe keeper is the hero, the rivals are a strip beneath it, and the reasoning is four readable chips — never a score.
9:41▮▮▮ ⌁
A · KEEPERB · RIVAL
Sharpness
0.84
0.61
Faces
4
3
Eyes open
4 / 4
2 / 3
Resolution
12 MP
12 MP
Your signals
Promote B to keeper
5 · Show the workingPinch-zoom is synced across both frames, so pixel-peeping two near-identical shots actually settles it.
9:41▮▮▮ ⌁
RECEIPTS 1,204CHATS 880MAPS 410
Text recognised on device to sort these. Nothing was read off the device.
Two held back — they’re in an album, so Keeper left them alone.
Delete 2,940 · frees 6.8 GB
6 · Sweep mode for the disposableScreenshots need good buckets and a fast grid, not one-by-one judgement. Anything you filed is excluded and says why.
9:41▮▮▮ ⌁
ABOUT TO REMOVE
6,821 items · 84.2 GB
Similar sets5,102 frames · 1,842 keepers kept
48.1 GB
Screen recordings61 files
22.8 GB
Screenshots1,658 files
6.8 GB
Blurred & darkConfidence above 0.9
6.5 GB
Space returns in 30 days, or soonerThese move to Recently Deleted. To reclaim 84.2 GB now, empty it there.
iCloud Photos is onThis frees 84.2 GB of your storage plan too.
Move 6,821 items to Recently Deleted
7 · The one irreversible-ish momentItemised, never a bare “Delete 6,821 items?”, and it corrects the two things people always get wrong.
9:41▮▮▮ ⌁
RECLAIMED TODAY
84.2 GB
6,821 REMOVED41,295 KEPT
Every keeper is still here1,842 sets kept their best frame. Nothing favourited, edited or filed was touched.
Undo this sessionRestores all 6,821
🗑
Empty Recently DeletedFrees the space now
Session logEvery decision, on device
Next: 47 GB still recoverable
8 · A receipt, and a way backUndo is a first-class row, not a toast that vanishes. Emptying Recently Deleted is offered but never automatic.

The big screen is where culling actually gets done

Phones are for the sweep categories. A 27-inch display is where you can see sixteen frames at once and settle a set in two seconds. Same engine, same index, a genuinely different posture.

KEEPER — 48,116 ASSETS · 131 GB RECOVERABLE
SET 41 · KAANAPALI · 14 JUL 2024 · 16 FRAMES198 MB RECOVERABLE
KEEP
SHARPEST OF 16ALL 4 FACESEYES OPENWIDEST FRAMING15 FRAMES STRUCK
Keep all (⌘K)
Accept set & advance (␣)
KEEPERIMG_4417
Sharpness0.84
Aesthetics0.71
Faces4 · all sharp
Resolution12 MP HEIC
Your signalsnone
Second place scores 0.79. Close, but sharper across all four faces.
Mac · the light tableThree panes: categories by payoff, the whole set as a contact sheet, and an inspector showing the score breakdown for the current keeper. The space key accepts and advances, arrow keys promote a different frame, ⌘Z reverses. A thousand sets is an evening of work with a mouse and twenty minutes with the keyboard — so the keyboard path gets designed first.

iPad: the same window, thumb-first

The sidebar collapses to a popover and the contact sheet gets the room. Apple Pencil strikes through a frame to reject it and circles one to promote it — the grease-pencil gesture the whole interface is built around, and the one input where the metaphor is literal.

Mac: where the library actually lives

On a Mac the photo library is a single package you can size, back up and copy before touching. That makes the desktop the safest place to run a large first session, and the natural home for the escrow export that writes originals to an external disk.

Five passes, cheapest first

Numbered because they genuinely are a sequence: each pass is an order of magnitude more expensive than the last and only runs on what survived the one before. That funnel is the entire reason a 48,000-asset library is tractable on a phone.

Enumerate and read metadata

One fetch pass reads creation date, modification date, media subtype, burst identifier, favourite flag, location, dimensions and resource byte sizes. No pixels are decoded. This alone resolves screen recordings, screenshots, Recently Deleted, exact-duplicate candidates and everything size-ranked.

≈ 12 seconds for 48,000 assets · no ML

Bucket into candidate groups

Comparing every photo to every other photo is 1.1 billion comparisons and a non-starter. Instead: a 64-bit difference hash from a 32×32 thumbnail, bucketed by time window and coarse geohash. Two photos only become candidates if they were taken near each other in time or place and their hashes are close. Typical libraries collapse to a few thousand groups.

≈ 3 minutes · thumbnail decode only

Feature-print inside groups only

Vision feature prints on a 360px thumbnail, then pairwise distance within each bucket to split a “sunset” bucket from a “sunset with the dog in it” bucket. Running this across the whole library would need roughly 400 MB of resident float vectors; running it inside buckets needs a few tens of megabytes, and the prints are discarded after clustering.

≈ 8 minutes · the memory trick that makes this shippable

Score only the survivors

Sharpness, exposure, aesthetics, face detection, capture quality and text recognition run only on frames that landed in a group of two or more, plus the blur candidates. That is typically 15–20% of the library rather than all of it.

≈ 6 minutes · Neural Engine · Metal for the Laplacian

Persist, then stay incremental forever

Groups, scores and decisions go into a SwiftData store in the app container. A library change observer keeps it live: new photos are scored in minutes, not by a full rescan. On iOS the full pass runs as a background processing task while charging, so it costs no foreground time at all.

Full rescan available, never required

Shared core, thin shells

A KeeperKit Swift package holds the models, pipeline and scoring engine; the three platform targets are SwiftUI on top of it. XcodeGen from a project manifest, SwiftFormat and SwiftLint as pre-build phases with warnings as errors, and no third-party runtime dependencies.

Testable without a library

The pipeline takes a photo-library protocol, so tests run against a synthetic fixture library with known duplicate groups and known correct keepers. Scoring gets a regression suite so a weighting change can’t silently start picking different frames.

Sizes match System Settings

Decimal GB, same as the Storage screen. Where iCloud optimisation means local bytes and library bytes differ, both are shown — a headline number you can’t verify against Settings is a bug report waiting to happen.

Not “we don’t upload your photos” — the app cannot

Every photo app makes the promise, and the promise is worth nothing because the user can’t check it. Keeper’s version is structural, and a curious person can verify it in about ninety seconds.

No outgoing network client

The app ships without the network client entitlement. There is no socket to open. Settings prints the entitlement list verbatim, and the docs show the codesign command to confirm it against the shipped binary.

Nothing third-party at runtime

No analytics SDK, no crash reporter, no remote config, no A/B framework — the four things that quietly turn a private app into a telemetry pipe. Vision, Core ML, PhotoKit, Foundation.

Models bundled and offline

Feature prints, aesthetics scoring, face detection and text recognition all run through the Neural Engine on hardware the user owns. No model downloads, no server-side inference, no “improve the product” opt-out buried in onboarding.

Derived data stays in the container

Hashes, feature prints and thumbnails live in the app container under complete-unless-open data protection — readable only while the device is unlocked. Never a shared group container, never iCloud, never a temp directory. One button wipes and re-derives.

Read-only until the button

Keeper holds read access for its entire working life. Write access is exercised exactly once per session — the deletion you confirmed — through the system’s own dialog, which the app cannot suppress or auto-dismiss.

Data Not Collected

All fourteen App Store privacy categories answered “no”. Sold once, no subscription, no account, no sign-in — nothing to have an account with.

One honest asterisk. Keeper needs full library access to work; Limited Photos Access can’t, because you cannot find duplicates among photos you aren’t allowed to see. The app explains that in one sentence at the permission prompt instead of nagging past a partial grant.

Five rules the app is not allowed to break

What I’m not certain about

Written down now rather than discovered in month three.

“Not opened in three years” is a promise the OS won’t let us keep
PhotoKit exposes no view count, no last-opened date, no read timestamp. Nothing in the framework knows you scrolled past a photo yesterday, and any app claiming otherwise is guessing — usually with creation date wearing a costume. Keeper measures neglect instead: a frame is untouched when it was never favourited, edited, filed, shared or used as a Memory key photo, and has no recognised face. It words it that way in the UI too.
Face clustering is the biggest technical risk
Vision gives us face detection and capture quality publicly; it does not give us a public face-identity embedding, and the system’s own People albums aren’t reachable through PhotoKit. Clustering from feature prints on face crops works, but it is meaningfully worse than Apple’s and it degrades across years and haircuts. This is why the people feature is opt-in and why nothing in the main flow depends on it.
“People you no longer photograph” is the one feature that can hurt someone
“You haven’t photographed this person in four years — clear 3 GB?” is technically an excellent space-saving feature and, some fraction of the time, the cruellest sentence a phone will say. That gap is a former partner, an estranged parent, a friend who died. If it ships, it ships as Chapters: off by default, never phrasing a cluster as absence, never pre-selecting a person’s photos, offering only thin this chapter — the ordinary duplicate cull scoped to those photos, deleting none of the singles. Every cluster carries a permanent “never suggest anything here”. It is also the first thing I’d cut.
The aesthetics score needs iOS 18 and macOS 15
The image aesthetics request — which also carries the utility flag separating a receipt from a photograph — is new API. Supporting iOS 17 means hand-rolling that classifier and shipping a Core ML model, which adds bundle weight and a whole evaluation problem. Recommendation: require iOS 18 and take the free, Apple-tuned score.
Battery and thermals during the first index
Fifteen-plus minutes of sustained Neural Engine work will warm a phone and drain it. Mitigation is to default the full pass to charging-and-idle, make a foreground scan explicitly opt-in with the tradeoff stated, and always let partial results be reviewed. It still needs measuring on an older device before we believe any of it.
Video is where the bytes are and where the tooling is thinnest
Similarity detection across videos means decoding keyframes and is roughly an order of magnitude more expensive than stills. V1 treats video by metadata only — subtype, duration, size, your signals — which already reclaims 41 GB in the model library. Near-duplicate video is a v2 problem.
“Best” is subjective and the app will be wrong sometimes
There is no way around this, only a way to survive it. Every disagreement is one tap, the reasoning is always visible, and the app tracks how often you override it. If you overrule the sharpness weighting fifty times, that is a signal worth learning from locally — a candidate v2 feature, kept on device like everything else.

What to build, and in what order

Ordered so each phase ships something useful on its own. If we stop after phase two, there is still a real app on the store.

Five calls to make before writing code

  1. The name. Keeper frames the app around what survives rather than what dies. Negative Space is the alternative — a photography term that also means exactly what the app does.
  2. iOS 18 floor? It buys the aesthetics score and the utility flag for free and cuts real work. It also cuts off iPhone XR and older.
  3. Ship Chapters at all? Designed defensively, but it’s the one feature that can hurt someone, and the app is complete without it.
  4. Mac first or iPhone first? Mac is faster to build and better at the actual culling; iPhone is where the storage pain is felt and where the app gets bought.
  5. Paid up front? No account and no subscription is the only pricing consistent with a no-network app, and it’s worth saying so on the store page rather than doing it quietly.

Phase one is self-contained and needs no UI. It either validates the funnel timings in an afternoon, or tells us the whole approach needs rethinking before a single screen gets built.

The principles behind the drawings

Designing a cleanup app for something irreplaceable forced a handful of rules that generalise well beyond photos. They’re written up separately.

Read the design principles