Last night I closed the laptop with a checklist for the morning.
Item two on it: build the project, and let there be no errors and no warnings.
The checklist said "for the morning". I opened it at half past one at night.
Cmd+B.
It didn't build.
Item three never happened.
Yesterday I said I suspected I wouldn't like it.
I got that wrong. This is exactly what I was expecting.
🎨 Blotches first
There is zero disappointment in any of this.
This is what I planned.
Let me explain by analogy. And let me say up front: I am not a painter.
But if somebody handed me the job of painting a picture, I would probably start like this. A general layout first — where the horizon goes, where the figure goes. Then broad strokes, in different colours, with no care taken at all: not objects, but blotches and silhouettes that will later become the background. A composition, so it becomes visible where everything is going to stand.
And only over the top of that — detail. Volume. Texture.
A blotch at this stage is not a mistake. A blotch is the method.
I needed to see the picture in order to paint it.
Quite possibly a real painter will read that and say it isn't how it's done. That it's inefficient and starts from the wrong end.
Fine by me.
Make no attempts and you will certainly never learn how it should have been attempted.
And this next part isn't about painting at all.
The people launching the first rockets understood perfectly well that they were going to flop. The first three Falcon 1s didn't make it. The fourth reached orbit.
Nobody was counting on the first attempt succeeding. They were counting on it happening.
(Obviously I am not comparing a snippet app to a rocket. My point is that the first attempt is not there for the result.)
So "everything working like a broken toy" was in the plan from the start. What interested me was something else, and it came as four questions.
How broken it would turn out to be.
Whether it would launch at all.
Once it launched — what fixing it would cost.
And whether the AI could carry that.
I got the first answer before I reached item three: it doesn't build at all. Good. Next on the list.
🧭 Nobody knows how to do it right
I never finished a book of Branson's — Screw It, Let's Do It. But the main thing in it has stayed with me.
Nobody knows how to do it right.
Not "few people do". Nobody. What doesn't exist yet has never been done by anyone, and there is no one to ask.
And once you understand that, you get harder to stop. You stop waiting for the competence to arrive. You just try. Then again. Then again after that.
Although the way I work is the exact opposite.
I work with people who are cleverer and better read than I am. Who do what they know how to do, and don't reach into what they don't. That is right, it is efficient, and it is how I have worked for years.
This project is built inside out. I am doing what I cannot do. Cannot do yet. And I am deliberately refusing to give my own ignorance a vote.
Because ordinary actions do not produce extraordinary results.
And I want more than that.
I look at my life and I understand that I am halfway along. If the fruits of being a good employee are enough for me — fine, I can simply work, and that is an honest and decent life.
But if I want something more, it only gets made by steps into the unknown.
Here is one of them.
Right now it doesn't build.
🔨 Three reasons
And not one of them is about Swift.
The first is the one I shrugged at yesterday and went to bed.
The commit was titled "Start iOs development", and I decided it was a slip. Fingers remember ten years of front-end, Apple means iOS, it happens.
It was not a slip.
The project really had been set up as an iOS one. Target platform: iphoneos. Device family: phone and tablet. Bundle identifier: koskei.com.jpaste — the reverse domain, written backwards.
And the code inside it is AppKit. The very NSPasteboard whose two lines this whole thing was started for.
AppKit does not exist on iOS.
We spent the whole night building a Mac app inside a project for a phone. And neither of us noticed: me, because I don't know where the handles are in Xcode; it, because nobody asked.
The second reason: the test files sit in the app target, so import Testing simply doesn't resolve there.
The third: the views and view models sit in the test target, so the main window can't see them.
And those two have one cause between them, which explains last night's whole mess at once. In a modern Xcode project, what decides which target a file belongs to is the folder it sits in. Not a list in the settings — the folder. Files with their path inside their name landed where they landed, and the folder disposed of them itself.
Yesterday I looked at those fused-together names and saw untidiness.
It wasn't untidiness. It was a build scheme.
Plus a couple of files that existed in two copies at once.
The total: thirty-seven files, two and a half thousand lines, and not one new capability.
It looks like tidying up. Like straightening a stack of paper before you start writing.
Except it isn't tidying. It's the difference between an app and a folder: yesterday I had twenty-two Swift files and no way at all to run them.
But here's what I noticed while I was straightening.
🪞 The wrong target
This morning I asked for two things, and I asked them in this order.
The first: make it work. Whatever else is true of it, it has to be an app you can put a snippet into.
The second I asked while we still hadn't picked up any speed, and it wasn't an insight. It was a small question: why aren't we using the system's own glass? From where I'm standing it looks like a relatively cheap trick.
The answer came back as a rewritten specification.
There is a document at the start of this project that I wrote myself, and it has the word parity in it: take the prototype's feature set as the starting scope instead of rediscovering it from nothing.
That sentence is about what the app does.
Somewhere between that document and the plan I'm building from, parity quietly turned into how the app looks. Nobody decided that. It got read that way, and I read it that way too, without once stopping on it.
Because the prototype lived in a browser.
A flat dark background, rectangles, shadows I drew by hand. For two months I thought of that as design. It isn't design — it's what a tab could manage.
A browser can't blur what's lying underneath the window. It doesn't know whether your system is dark or light until it asks. It has no access to the materials macOS builds its own windows out of. Everything I made in there was a way around those limits.
And a native app doesn't have those limits.
So what I was carrying over wasn't a solution. It was a compromise — and a compromise with a condition that no longer exists.
The prototype answered the question of what should happen. It never answered the question of how it should look.
So the rule is rewritten: the prototype stays the source of truth for layout and behaviour — what sits where, what happens on a click, where a block travels. And it stops being the source of truth for appearance.
Entirely. Completely.
🧊 Glass you can switch off
Instead of a flat background — the system's own materials.
A frosted window you can see through. Surfaces that take their colour from the wallpaper. A transparency control, so everybody can set it to their own taste.
And two decisions that had to be made this morning.
The first: the minimum system version stays where it was. Not everybody got a Mac with the new materials, and raising the bar for the sake of looks means throwing away a share of people in exchange for something they wouldn't even see. On older systems the app substitutes the familiar material and works in exactly the same way. One build, one store listing, one purchase.
The second I like better.
On new systems, where the real glass exists, there is still a switch in the settings that turns it off.
Because glass is a matter of taste. Some people find it harder to read. Some just don't like it.
I'm not convinced anybody will ever touch that switch. But the material version is already written — it has to exist for the older systems, it's already in the same binary, it's already tested. Handing it to people costs nothing.
And it sits far enough away in the settings to be nobody's problem. Giving people a switch is cheaper than being right.
🔌 Engineer honesty moment
Now two things that came to light today. This is the answer to the first of my questions — how broken it would turn out to be.
The first.
I sat there looking at a canvas with one block on it. I wasn't hunting for a bug. I was just looking.
And I caught myself on a question that sounds ridiculous asked about an app I wrote yesterday: how is this actually supposed to work?
There was no input field in the app.
Yesterday I promised myself something raw, buggy and not working — but existing. Here is how raw, and it isn't at the edges.
The field you type the text of a new snippet into did not exist in the app. Editing by double-click was broken. You can drop a block onto the canvas, but you cannot put text into it.
A snippet app you cannot put a snippet into.
That was scheduled for later, because what the plan said was look good — and how the text gets in there, we'll work out on the way. It had to be pulled to the very front and done before everything else. Before a single line about appearance.
The second is worse. And I'm not the one who found it.
There was a vault in the app. A separate privacy level for boards, a field for a passphrase, a little padlock, all as designed.
I looked at it exactly once. I thought: what do I want with it right now, when there's nothing to put in a block anyway. I'll test it later.
Later never came. The vault was wired into the app this evening — and it turned out there was nothing to wire.
The cryptography had been written. Its own file, with tests, and the tests passed. The vault service knew how to encrypt a block's text and decrypt it back — both functions there, both working.
They were simply never called by the one thing that saves a block.
And in the function that derives the key from the passphrase, the comment says: PBKDF2, one hundred thousand iterations. The body calls HKDF. HKDF has no iterations at all, and the number being passed in had nowhere to go.
The lock was hanging on a door that didn't close. And the lock had a security rating engraved on it.
Today it got connected up: the key is derived from the passphrase with two hundred thousand iterations, every board gets its own salt, and existing blocks are encrypted the first time a vault is set up. And what has already been decrypted and is sitting in the memory of an open board is now honestly searchable — and disappears when it closes.
And this is the one thing today that wasn't in the plan. Broken I was expecting.
What I wasn't expecting is broken that looks finished enough that I put off checking it myself.
For a day a vault that locked nothing sat in the repository. The only thing that stopped me putting something into it is that there was nothing to put.
💬 A question
When did you last copy your own old solution without asking why it was that way?
My answer is today. And the galling part is that the limitation I invented it for was lifted long ago, while I went on carrying the solution around as if it were an asset.
Tell me how it goes for you 👇
Next, everything is green: the build, the tests, fourteen tasks.
Which is exactly what worries me 😉