work · macos
incinerator
incinerator is a macos app for reclaiming disk space, dressed as a fire. a small animated oven floats above your desktop; you scan the folders where junk actually accumulates, get a ranked queue of what to clear, and you delete a file by dragging it into the flames. the theme is playful on purpose. the part i care about is underneath it: how do you build something whose whole job is destruction, and make it feel safe?
rank what's worth clearing, don't just list it
a dumb cleaner shows you your biggest files and leaves the judgment to you. incinerator scores every file on three axes, then sorts. size matters, but so does age, and so does whether you've actually opened the thing.
let composite = min(sS * 0.35 + aS * 0.25 + uS * 0.30 + catBonus, 1.0)
guard composite > 0.2 else { return nil } // not worth recommending
each sub-score is a step function over buckets, and a category bonus nudges installers and archives up (a .dmg is almost always safe to delete after you've installed). the reason string each card shows, "installer, safe to remove after install," "last opened two years ago," is generated from those same signals, so the recommendation can always explain itself. the goal isn't a longer list. it's a shorter, defensible one.
the part nobody opens is the "last used" date
macos doesn't hand you a reliable "when did you last open this" through the normal file attributes. so incinerator reaches into the spotlight metadata store for it, the same index that powers search.
if let mdItem = MDItemCreateWithURL(nil, fileURL as CFURL) {
lastUsed = MDItemCopyAttribute(mdItem, kMDItemLastUsedDate) as? Date
}
that single attribute is what separates "a big file" from "a big file you forgot exists." when spotlight has no record, the score falls back to the creation date, so the absence of data never crashes the judgment, it just softens it.
finding duplicates without reading every byte
the obvious way to find duplicate files is to hash all of them and compare. that's also the slowest possible way. incinerator runs a three-pass cascade designed so the expensive work only happens where it could possibly matter: first group by exact byte size, then hash only the first eight kilobytes of each survivor, and only then do a full hash on what still collides.
// pass 2: partial hash, first 8kb only
for file in group {
if let hash = hashFile(at: file.url, maxBytes: 8192) {
partialBuckets[hash, default: []].append(file)
}
}
two files can't be identical if their sizes differ, so most candidates die in pass one for free. the hasher itself streams through an InputStream in sixty-four-kilobyte chunks and exits early at the byte limit, so it never pulls a whole movie into memory to discover it's unique. this is the same instinct as a good interview: cheap disqualifying questions first, the expensive deep dive only for the finalists.
trash, then burn, and never lose the file in between
here's the safety design. "delete" is really two stages. the first moves a file to the trash, and crucially it captures where the file landed inside the trash.
var resultingURL: NSURL?
try FileManager.default.trashItem(at: url, resultingItemURL: &resultingURL)
trashedFileURL = resultingURL as URL? // now we can find it again
most apps lose the handle the moment a file is trashed. by keeping the relocated url, the irreversible step, the actual removeItem, always operates on a known, stable path, and only ever after the file is already safely in the trash. so the dramatic drag-into-the-fire is permanent, but everything leading up to it is recoverable. the violence is real but it's fenced.
an oven that floats above everything
the oven isn't a normal window. it's an NSPanel subclass at the floating level, transparent, joining all spaces, non-activating, so it hovers over your work without stealing focus, like a wood stove in the corner of the room. it shows an embers gif when idle and swaps to flames when a file is dragged over it, with the flame variant chosen at random but never repeating the last one, so the fire never looks looped. small touches, but they're what make a utility feel alive instead of clinical.
i'll be honest about the limits: the scan folders are hardcoded, it runs unsandboxed so it can read your home directory (which means it can't ship on the app store), and "incinerate" unlinks the file, it doesn't shred the bytes. but the spine, score before you list, disqualify cheaply before you hash, and make destruction the only irreversible step while everything before it forgives, is what i'd keep. the prophet ﷺ said الطّهور شطر الإيمان, cleanliness is half of faith. clearing the clutter, even the digital kind, is a small way of honoring that.