← Keeper Design principles

Twelve rules for deleting things people can’t get back.

Most cleanup tools are safe because their subject regenerates. Photos don’t. Designing Keeper meant re-deriving every default, every confirmation and every piece of copy from that one fact — and the rules that came out of it turn out to apply to anything with an undo button that doesn’t really undo.

What made this hard

Six constraints that were all true at once. Any one of them is manageable; together they rule out almost every pattern a cleanup app would normally reach for.

The principles that came out of it

Each one is a decision we made differently than we would have on a lower-stakes app, and what it turned into on screen.

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.

What each rule costs

None of these are free. Worth being explicit about the trade, because on a different product several of them would be the wrong call.

RuleWhat it costsWhy it’s worth it here
Show the survivor Fewer items visible per screen; more scrolling for power users Turns a minute-long decision into a one-second one
Declare ties Some sets need manual input that a bolder app would have auto-resolved The confident cases become believable
Words, not scores Looks less sophisticated in a screenshot Users can actually check the claim against the photo
“Untouched”, not “unopened” A weaker headline than every competitor’s It survives contact with a sceptical user
No network entitlement No crash reports, no analytics, no remote kill switch — bugs are found the slow way The core promise stops needing to be believed
Itemised confirmation One more screen between intent and result It is the only screen standing between a user and a permanent mistake

The shortest version

  1. Put the keep in the hero and the rejects in the margin.
  2. Default toward what survives, and derive the deletion from it.
  3. Explain in words the user can check against what’s on screen.
  4. Their past actions outrank your model — and say when that happened.
  5. Admit a tie rather than manufacture a winner.
  6. Rename the feature to what you can measure instead of guessing the rest.
  7. Remove the capability rather than promise restraint.
  8. Ask what the worst output is before asking how accurate it can get.
  9. Report the space honestly, including the part you haven’t freed yet.
  10. Make the confirmation refusable by itemising it.
  11. Design the keyboard path first when the work comes in thousands.
  12. Let the cheap pass answer first so the first screen isn’t a progress bar.

Where these came from

The full Keeper proposal — the nine clutter categories, the scoring model, ten screens, the on-device pipeline, and an honest list of what might not work.

Read the proposal