Jim's Depository

this code is not yet written

When writing Cassandra's Deal I had in the plan a "settings" page and a little gear icon to get to it, as one does. In the end I ended up not having any settings. Sure, there were a lot of visual representation decisions which could have had settings. And there are levels of trivial move automation that could have a setting.

But I realized that an app without settings is liberating for a user. There is no implied obligation to go to the settings, understand which ones are applicable to you and how they work. Freedom!

There are still visual customizations, but I let them be driven by the system preferences the user has already made. Dark mode and light mode is automatic. It's not in the current release, but the drawing of the card face to make the rank and suit more visible will be driven by the "text size" preference of the user.

Many apps will require settings or some data input from the user to make sense, but for simple apps, its worth asking yourself if the user discomfort of having settings is worth the gain.

I'm pretty good at Klondike type solitaire, but I do not always win winnable hands. (No backtracking, all hands are winnable with unlimited backtracking.) After not feeling I was getting any better at winning winnable hands I decided what I needed was an indicator to show when I had "stepped off the path", when I had converted a winnable spread of cards to an unwinnable one. Not immediately finding such a program, I spent a day with an AI agent and wished one into existence.

Lessons learned:

  • There are solitaire card states where stepping off the golden path is just baffling. You can make what looks like the most obviously benign move and render it unsolvable. More disturbing there are places where you have a choice of two cards to move to a destination and one makes it unsolvable with no clear indication how you might choose one or the other. I fear that a human operable algorithm for perfect solvability is not achievable. But, hey, give it a shot! I have an app for that.
  • The AI wrote an entire polished solitaire app with real time solvability analysis just from my instructions and feedback. I was ready for a 1.0 release without ever having seen any of the code.
  • Having a knowledgable computer scientist is still useful, in talking to it about solvability I sped up its algorithms by about a factor of 1000.
  • Having a programmer with in depth knowledge of a platform and its capabilities is also an asset. The AI frequently chose ways of implementing which didn't take advantage of capabilites, for instance, when dealing a deck of cards it would use the "Task" system to delay and then deal the next card. This gives a deal subject to timing jerkiness. Encouraging it to use the animation system with delays built in to the animation gets a smooth, synchronized experience. But if you didn't have a programmer who had watched an unreasonable number of WWDC videos that approach would not have been explored.
  • I used Codex for most of the work. I let Claude help when Codex was on its marketing break. Claude seems a bit slower, though most of that feeling might be that it is less chatty while working. They both seemed to get the job done. The low end Claude gets a lot less work done before going on break compared to ChatGPT. Codex works better with Xcode 26.5, but I imagine they both do better with the Xcode beta, I just can't use that because I'm releasing to the store. Still, it might be worthwhile to develop mostly with the Xcode beta, then switch to the released Xcode when it is time to publish.

In any event, your can go to the iOS or macos app store and download Cassandra's Deal and try for yourself.

Image of solitaire game

Attachments

cassandrasdeal.png 535506 bytes

Let's say your desktop machine has been sitting in place for 7 years without moving. Apple refuses to let your machine know its location without Wifi active. But you are using your glorious ethernet connection and don't want the chaos of a secondary connection that will sometimes get used and throw everything into a tizzy.

It is possible to work around this. Your Wifi radio has to be on and watching beacons, but it doesn't have to be connected to a network. Go through any network you know how to join and turn off "Auto join". Cycle your wifi off and back on. If you connect to a network, go set that one to not "Auto join" as well.

Once you have no networks to auto join your Wifi rainbow in the menu bar will be grayed, but your location services still work because the radio is looking at beacon messages from the nearby access points.

Maybe someday Apple will give us "On/Off/Location Only" for our Wifi state, but until then, this works. You just have to manually ask to join your network when lightning strikes your ethernet switch.

While testing SMTP message reception and DKIM validation I ran into an evil action from Postfix.

In my basically default Debian Postfix/Dovecot system, if you send a message to Postfix, it DKIM signs it, then sends it off to the destination. But if your message needed 8BITMIME and your destination doesn't support that, then Postfix quietly re-encodes your message and body to be quoted-printable.

Now your DKIM signature is invalid!

It sounds like you can configure Postfix to bounce that instead, but that's not what I got for a simple installation.

That was about a day and a half of me debugging my DKIM verification code because my body hash calculation kept not matching the one in the DKIM header.

Morals:

  • Just go ahead and support 8BITMIME and SMTPUTF8 if you can. No need to poke the bear.
  • If your body header checksum matches for some messages, but not others, its probably something upstream corrupting the message bodies after the signature.

Lost half a day today working to a strange Cloudflare DNS resolver anomaly.

When you make a UDP request, it may answer correctly, or it may indicate truncation by setting the TC (TrunCation) bit and not give you an answer. The answer easily fits, but it just decides, "nope, not this time". If you reissue the query over TCP you will get the result every time (in my testing). But UDP gives an erroneous TC non-result about 25% of the time on the name I was testing.

So, I guess be alert for that. If your DNS code can flip over to TCP you may never realize this is happening other than some of your queries are oddly much delayed compared to the others.

Just the simple host command can show the behavior, though you won't realize it happened because it falls back to TCP. If you are watching packets with tcpdump you will see it.

I don't see the behavior using Google's 8.8.8.8.

Anyway, here's a little tcpdump capture if you want to look at it. That's a bunch of requests for the same TXT record (the SPF for lunarware.com, which is ultimately DNS hosted at Cloudflare.)

I don't have a resolution unfortunately, I just banned 1.1.1.1 until I add TCP lookup to the affected code.

Attachments

I needed to annotate regions of a bunch of images to let Create ML train up an object recognition model. I found a program for that, but it decided to nag me every few images and I didn't like the UI much.

So… I decided to see how writing a new app by just exhorting Codex to apply itself works, the answer is: beautifully! I spent about a day feeling out how I want the app to work, with a couple of false starts for trying to make it too simple.

But now the world can enjoy Image ML Annotator for all your image labeling needs, as long as you need exactly what I needed.

screenshot

I built it to train a sudoku puzzle locator for images, but the example data for playing with the app is a bunch of motivational pictures of puppies, kittens, and ducklings that I had an LLM and an image generator cough out. So, lots of AI in this project. The web site was AI generated, the help is AI generated (and is surprisingly good), all of the code is AI generated. I may have edited a small line here and there, but it is essentially 100% written by Codex with me standing over its shoulder and annoying it constantly.

macos store download

Attachments

screenshot1.png 1260379 bytes

Femtoblogger gained support for Passkeys and Atom.

You can create an account with passkey authentication. If you have an existing account, you can add another passkey. The username/password authentication is still supported, but not encourage. Notably missing is the ability to set or change a password once you have an account, I'll get to that.

Atom support is back for all you feed readers. Another Codex win. I asked it to put atom support back, told it which endpoint I wanted to use. It looked around at my models and how I do things and wrote it all in one whack. It really is a bit like wishing features into existence.

Less visible, some sorts of pages are now cached internally so they don't have to be regenerated all the time. For instance, the Atom feed page caches so if your reader uses If-None-Match or If-Modified-Since you might get a 304 Not Modified and save a download. But even if you don't use those headers you will probably get a pregenerated cached copy, so that saves time.

I had Codex help me with the parts of femtoblogger which annoyed me.

As I said earlier, I was ambivalent about Lighter. I decided to ditch Lighter and just go straight to llibsqlite3. Codex and I stomped through that table by table in my model layer with me asking it to convert a table and it doing all the work. 100% success and much faster than I would have done it by hand.

The I decided to address the HTML generation. I'd been using Stencil and template files. I asked Codex to make me a HTML DSL so I could write my HTML in Swift and use Swift for the flow of control and computed values instead of trying to map that on to the Stencil capabilities. 100% success and much faster than I would have done by hand. Codex was reading the Stencil templates and generating the HTML DSL with interspersed Swift control flow and values which also extending the DSL every time it found an unimplemented HTML element. I had to do a little steering on the shape of the DSL API to make it come out the way I wanted, so not an unattended change, but easily done 10 times faster than by hand and without all the tiny errors I would have introduced.

I brought up email on a domain which hasn't had it for 15 years or so. I am instantly being barraged with spammers who actually remember the names of the people on that domain!

But I have to wonder, are these real messages and just a very persistent delivery agent? 15 years of trying to deliver the message? I'm tempted to add the users just to see.

hello, im trying to find drivers for my lacie ethernet disk mini and found you hacked it. do you have the drivers some where? thanks

If you want to look up countries for an IP address, because say all your comment spam in your blog comes from the same country, then the nice folks at MaxMind will hook you up with their GeoLite2 databases for no cost, though there is a license to pay attention to.

These files are in MMDB format which excels at encoding partitions of natural numbers. The format is documented and there are a number of libraries to access the files, but I wanted native Swift to keep my cross platform building nightmares in check.

You can check out swift-mmdb which I keep on github. I haven't made a change in 4 years, but that's just because it keeps working.

The performance cost is about 9MB for the data and 150µS per lookup.

more articles