Show the survivor, not the casualties
The obvious layout for a duplicate finder is a grid of things to delete with checkboxes. It reads as an accusation and it puts the user’s attention on fifteen photos they don’t care about.
Inverting it — one large keeper, the rejects as a small strip underneath — changes the question from “which of these do I sacrifice” to “is this the right one”. The second question takes about a second to answer. The first one takes a minute and produces regret.
On screen
The review screen is 60% keeper. The fifteen rejects are thumbnails in a single row.
When the mistake is permanent, pre-select the keep
Pre-selection is a real gift when the stakes are low and a trap when they aren’t. The resolution isn’t to abandon defaults — with 1,842 sets, no defaults means no product — it’s to choose which side of the decision the default sits on.
Keeper pre-selects the frame that stays and derives the deletions from it. Same end state, but the thing the user has to actively confirm is the survival, not the destruction.
On screen
Two rows: “Keep 1 frame” ticked green, “Delete 15 frames” ticked red beneath it, both visible before you commit.
Never show a score where a sentence will do
The engine produces a sharpness value of 0.84. Putting that on screen looks rigorous and communicates nothing — nobody knows whether 0.84 is good, and it invites arguments about the number instead of the photo.
The same information as “Sharpest of 16 · All 4 faces · Eyes open” is instantly checkable against the picture in front of you. The score belongs in the compare view, where you are explicitly asking for the working.
On screen
Four word-chips under the keeper. Raw numbers appear only in the side-by-side compare.
Rank the user’s own signals above your cleverest model
A frame the user favourited two years ago beats whatever the aesthetics model thinks today, every time. This is not humility for its own sake — the user’s past action is simply better evidence about the user’s preference than any general-purpose scorer.
The corollary is that the app has to say when this happened. “You favourited this one” is a completely different explanation from “this one scored highest”, and conflating them teaches people the model is smarter than it is.
On screen
Tier 1 signals win outright and label themselves. Nothing favourited, edited, shared or filed is ever suggested for deletion.
When two answers are close, say so and pick neither
Every ranking system produces near-ties, and the usual behaviour is to return the winner anyway because a UI needs one. That converts a coin flip into a confident-looking recommendation, which is the fastest way to burn trust once the user notices.
Declaring the tie costs one screen state and buys the right to be believed on every set where the app is confident.
On screen
“These two are close.” Neither is pre-ticked; the set advances only when the user chooses.
Don’t invent the data the platform won’t give you
The single most requested feature for a photo cleaner is “show me what I haven’t looked at in years”. The OS keeps no such record. The tempting move is to approximate it with creation date and keep the appealing label.
We measure what is actually knowable — never favourited, never edited, never filed, never shared — and rename the feature to match. Untouched is a smaller claim than unopened, and it has the advantage of being true.
On screen
The word “untouched”, with the five conditions listed in the row’s subtitle rather than hidden in a help page.
Make the privacy claim falsifiable, not sincere
“We never upload your photos” is unverifiable by the person who most needs to verify it, which makes it worth roughly nothing regardless of whether it’s true. Sincerity is not a security property.
Removing the network entitlement converts a promise into a fact anyone can check with one command. Design-wise this is the same move as declaring a tie: give up a little rhetorical room in exchange for being believed.
On screen
The first screen states the claim; Settings prints the entitlement list; the docs give the command to verify it.
A feature can be technically excellent and still wrong to ship
“You haven’t photographed this person in four years — clear 3 GB?” is a genuinely strong space-saving feature. It is also, some fraction of the time, addressed to someone about a person who died.
The app cannot tell those cases apart, so the design question stops being “how accurate can we make this” and becomes “what is the worst sentence this can produce, and can we live with it”. The answer was to strip the feature down until its worst output was harmless: no absence framing, no pre-selection, and the only action is thinning duplicates.
On screen
Off by default. One action — “thin this chapter” — and a permanent “never suggest anything here”.
Tell people where the space actually went
Deleting 84 GB of photos frees 0 GB. The bytes sit in Recently Deleted for thirty days, and the storage screen the user was trying to escape doesn’t move. An app that celebrates at that moment has just lied by omission.
The same applies to iCloud: on an optimised library, local and cloud numbers differ, and only one of them is the one the user is upset about.
On screen
The confirm sheet states the thirty days and the iCloud effect before you press the button, not after.
The confirmation is the product, so itemise it
“Delete 6,821 items?” is not a confirmation, it’s a formality — nobody can evaluate that number, so everyone taps through. A dialog that can’t be meaningfully refused isn’t protecting anyone.
Broken into four named rows with their own sizes, the same total becomes checkable. The user can spot that screenshots are higher than expected and back out of one line rather than all of it.
On screen
Four rows, four counts, four sizes, and the keeper count restated: “1,842 keepers kept”.
For volume work, design the keyboard path first
A thousand sets is an evening with a mouse and twenty minutes with the keyboard. That is not an accessibility footnote, it is the difference between a tool people finish and a tool people abandon at set 60.
Designing the shortcuts first also forces the interaction model to be simple enough to have shortcuts — if accept-and-advance can’t be one key, the screen is doing too much.
On screen
Space accepts and advances, arrows promote a different frame, ⌘Z reverses. The buttons carry their shortcuts as labels.
Let the cheap pass answer first
The expensive analysis takes fifteen minutes. The naive build makes people wait for all of it before showing anything, which means the first impression of the app is a progress bar.
Ordering the pipeline cheapest-first isn’t only a performance decision — it’s a UX one. Metadata alone resolves screen recordings, screenshots and Recently Deleted in twelve seconds, so there is something worth doing on screen almost immediately, and the slow part finishes in the background.
On screen
“Review what’s ready (12.4 GB)” appears during indexing, with named passes and honest counts above it.