Building Renala

18 articles on building a macOS disk analyzer in Swift and SwiftUI. From the first wireframe to the App Store. Design decisions, performance tuning, treemap rendering, and what it takes to ship a native Mac app as a solo developer.

Part 1: Foundation

01

Why I Built a Disk Analyzer

On picking a project with a bounded scope, interesting algorithms, and a visible result. How frustration with existing tools led to building Renala, a macOS disk space analyzer.

02

Hello, Xcode

I expected a compiler. I found a religion. XcodeGen, dual build systems, code signing, entitlements, and the Makefile as escape hatch.

03

The Treemap Problem

It's a rectangle. Inside that rectangle, there are smaller rectangles. Implementing squarified treemaps from the 1999 Bruls/van Wijk paper, and why flat colors are not enough.

Part 2: The Platform

04

Swift 6 and the Concurrency Cliff

The compiler told me my code was wrong. It was right. @MainActor, Sendable, actor isolation, and the bugs Swift 6 strict concurrency caught before I shipped them.

05

SwiftUI Is Not React

I kept looking for useState. There is no useState. The @Observable model, MVVM in practice, and the Canvas API for immediate-mode rendering inside a declarative framework.

06

Painting Pixels: Lambert Shading

I spent three days on pixels. Worth it. Per-pixel Lambert shading, why SwiftUI Canvas cannot do this natively, and the background Task pipeline that makes it smooth.

Part 3: Performance

07

The Scanner Bottleneck

44 seconds. Then 26 seconds. One syscall changed everything. FileManager's hidden costs, getattrlistbulk, and raw pointer arithmetic on macOS.

08

A Heartbeat for the Main Thread

If you can't measure it, you can't fix it. PerfLogWriter, MAIN_THREAD_BLOCKED detection, and the 250ms batch flush pattern that keeps the UI responsive.

09

From One Gigabyte to Four Hundred Megabytes

I had 5.2 million FileNode objects. I did not need them. UUID to UInt32, paths reconstructed from parent + name, and the flat NodeStore that replaced a tree.

10

Finding Duplicates at Scale

Hashing 300 GB of files on a laptop without killing it. The multi-phase pipeline, 6-worker TaskGroup, and the throughput gains from batching UI updates.

11

Testing a Visual App

526 tests. Zero of them look at a pixel. Algorithm tests, real-file scanner tests, O(n) performance gates, and why SwiftUI views are deliberately untested.

Part 4: Ship It

12

Eleven Languages, One Toggle

Nobody ships 11 languages on day one. I did it anyway. Live language switching, the Python translation pipeline, RTL support, and Arabic plural categories.

13

The App Sandbox

You cannot open a file without asking permission. Every time. Security-scoped bookmarks, NSHomeDirectory() returning the wrong path, and why read-write matters.

14

The App Store Gauntlet

I thought building the app was the hard part. Privacy Manifest, StoreKit 2, screenshots at 2880x1800, and the CI/CD pipeline that handles signing, notarization, and submission.

15

Rejected

Two rejection reasons. One was my fault. One was almost my fault. A crash on macOS Sequoia from an @MainActor closure on a background queue, and a tip jar that appeared empty during review.

16

Submitted

The app is in the queue. Six reviews, four versions, three weeks. What the gap between 'done' and 'shipped' actually costs.

Appendices

20

Where Does Nothing Run?

When you pass an empty closure to an API, who decides which thread it runs on? A cross-language comparison of JavaScript, C#, Java, Kotlin, and Swift 6.

21

How SwiftUI Decides What to Redraw

SwiftUI tracks which properties each view reads and skips work when inputs have not changed. Closures break that model. Stable ViewModel references fix it.