<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Brad, Unsupervised</title><description>I&apos;m Brad Jackson. I build software, games for my daughters, and tools I wish existed.</description><link>https://bradunsupervised.com/</link><language>en-us</language><item><title>The small decisions that make software feel good.</title><link>https://bradunsupervised.com/writing/the-small-decisions-that-make-software-feel-good/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/the-small-decisions-that-make-software-feel-good/</guid><description>Thoughts on polish, feedback loops, and why the details matter more than we think.</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Nobody has ever told me they loved an app because of its architecture. They tell me it feels fast, or that it never loses their work, or that the button is where their thumb expected it to be. Those are all small decisions, and almost none of them show up in a roadmap.&lt;/p&gt;&lt;p&gt;The first one I always look at is the gap between a click and a response. If something is going to take longer than about a hundred milliseconds, the interface owes the person a sign that it heard them. A pressed state, a spinner that waits a beat before appearing, a row that greys out while it saves. None of it is hard. All of it gets skipped when a deadline is close.&lt;/p&gt;&lt;p&gt;The second is what happens when things go wrong. A form that keeps everything you typed after a failed submit is worth more than a beautiful error illustration. An undo that actually undoes is worth more than a confirmation dialog that everyone clicks through without reading.&lt;/p&gt;&lt;p&gt;Then there is copy. I rewrite button labels more than any other line of code I touch. &amp;quot;Submit&amp;quot; becomes &amp;quot;Save draft&amp;quot;, &amp;quot;OK&amp;quot; becomes &amp;quot;Delete 3 entries&amp;quot;. The verb tells people what will happen, and the number tells them how much.&lt;/p&gt;&lt;p&gt;Keyboard support is the one I used to treat as optional, and I was wrong. The people who use your tool every day will learn the shortcuts within a week, and from then on every missing one is a small, repeated papercut. Focus rings that are visible, Escape that closes things, Enter that does the obvious thing.&lt;/p&gt;&lt;p&gt;What ties all of this together is a feedback loop. I keep a running note while I use my own software, and every time something makes me hesitate, I write it down. Most entries take ten minutes to fix. The list never empties, but the product gets a little calmer every week.&lt;/p&gt;&lt;p&gt;Polish is not a phase at the end. It is a habit of paying attention, and it compounds.&lt;/p&gt;</content:encoded><category>Development</category><category>ux</category><category>polish</category><category>craft</category></item><item><title>My kids are my toughest playtesters.</title><link>https://bradunsupervised.com/writing/my-kids-are-my-toughest-playtesters/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/my-kids-are-my-toughest-playtesters/</guid><description>What building games for my daughters has taught me about creating better software.</description><pubDate>Sat, 12 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My oldest is seven and my youngest is four, and neither of them has any patience for a loading screen. If a game does not respond in the first few seconds, they hand the tablet back and go find the crayons. It is the most honest usability test I have ever run.&lt;/p&gt;&lt;p&gt;The first version of the dinosaur game had a menu. Start, settings, a little credits screen I was proud of. My youngest pressed the dinosaur on the title art four times, decided it was broken, and wandered off. Now the title art is the game. You tap the dinosaur, it roars, and you are playing.&lt;/p&gt;&lt;p&gt;Kids do not read instructions, so everything has to explain itself through what happens when you poke it. That turns out to be a great rule for grown-up software too. If a feature needs a paragraph of help text, the feature is usually the problem.&lt;/p&gt;&lt;p&gt;They are also merciless about consistency. If the jump button is on the left in one level and on the right in the next, I hear about it immediately, and loudly. Adults just quietly get worse at using your app and blame themselves.&lt;/p&gt;&lt;p&gt;The best moments are the ones I did not plan. My oldest discovered that if you make the triceratops sneeze three times in a row it does a little spin. That was a bug. It is now a feature with its own sound effect, because she was so delighted by it.&lt;/p&gt;&lt;p&gt;I build these games because I want my daughters to grow up seeing that the things on their screens were made by people, and that they could make them too. The lesson that keeps coming back to me is simpler: watch real people use the thing, and believe what you see.&lt;/p&gt;</content:encoded><category>Family + Creative</category><category>games</category><category>family</category><category>playtesting</category></item><item><title>I got tired of restarting my CMS. So I built one.</title><link>https://bradunsupervised.com/writing/i-got-tired-of-restarting-my-cms/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/i-got-tired-of-restarting-my-cms/</guid><description>Why Shapio treats content models as data, and what that changes for the people who use it.</description><pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every headless CMS I have used in the last few years has the same moment. You add a field to a content model, and then you wait. The admin rebuilds, or the server restarts, or a migration runs and a deploy goes out. On a laptop it is a coffee break. In production it is a change request.&lt;/p&gt;&lt;p&gt;For a long time I assumed that was just the price of having types and a nice API. Then I spent a weekend helping a friend add a &amp;quot;subtitle&amp;quot; field to a blog, and it took two deploys and a rollback. That was the weekend I started sketching Shapio.&lt;/p&gt;&lt;p&gt;The core idea is small: content models are rows in a database, not files in a repository. When you add a field, Shapio writes a new version of the model, validates it, and starts serving it. The API routes are generic and look the model up at request time, so there is nothing to regenerate and nothing to restart.&lt;/p&gt;&lt;p&gt;That sounds risky until you add the other half, which is schema sync that works like git. You can pull the current models into JSON files, review them, commit them, and apply them to another environment. If someone changed production while you were working, the apply refuses and shows you the diff, the same way a push gets rejected when the remote has moved.&lt;/p&gt;&lt;p&gt;Content itself lives in JSONB, keyed by stable field IDs rather than names. Renaming a label never touches stored data. Changing an API ID is an explicit, flagged change, because that is a contract your front end depends on.&lt;/p&gt;&lt;p&gt;I also wanted the boring, important things built in instead of sold as add-ons: localization, revisions, scheduled publishing, roles, an audit log. Indie builders need those as much as anyone, they just cannot afford the enterprise tier.&lt;/p&gt;&lt;p&gt;Shapio is open source and self-hosted. You can run it as an npm package under PM2 or as a Docker image, against your own PostgreSQL. It does not phone home.&lt;/p&gt;&lt;p&gt;It is not finished. But this site is built on it, and the day I added a field to the Post model and saw it in the editor a second later, without touching a terminal, felt like the reason I started.&lt;/p&gt;</content:encoded><category>Development</category><category>shapio</category><category>cms</category><category>open source</category></item><item><title>The terminal setup I keep coming back to.</title><link>https://bradunsupervised.com/writing/the-terminal-setup-i-keep-coming-back-to/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/the-terminal-setup-i-keep-coming-back-to/</guid><description>A plain shell, a handful of aliases, and why I stopped chasing the perfect dotfiles.</description><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every couple of years I fall into the dotfiles hole. New prompt, new plugin manager, a theme with exactly the right shade of blue. A month later I am back to something very close to what I started with.&lt;/p&gt;&lt;p&gt;What survives is boring. zsh with almost no plugins, a prompt that shows the directory and the git branch and nothing else, and about twenty aliases that I actually type. If I have not used an alias in a month, it gets deleted.&lt;/p&gt;&lt;p&gt;The single most useful thing in my setup is a fuzzy finder bound to Ctrl-R for history. I search my own past far more than I search the internet. A close second is a tiny script that opens the current repo in the browser at the right branch.&lt;/p&gt;&lt;p&gt;I keep project commands in each project, not in my shell. A package.json script or a Makefile target means the next person, including future me on a fresh laptop, gets the same commands without copying my config.&lt;/p&gt;&lt;p&gt;The real lesson took me a while: the best tool setup is the one you can rebuild from memory in fifteen minutes. Everything beyond that is a hobby, which is fine, as long as I am honest that it is one.&lt;/p&gt;</content:encoded><category>Tools</category></item><item><title>Notes on shipping alone.</title><link>https://bradunsupervised.com/writing/notes-on-shipping-alone/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/notes-on-shipping-alone/</guid><description>Small scopes, public deadlines, and the quiet discipline of finishing without a team.</description><pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Working on your own projects means nobody is waiting for you. That is the best part and the most dangerous part.&lt;/p&gt;&lt;p&gt;The first habit that helped was writing down what &amp;quot;done&amp;quot; means before I start. Not a roadmap, just a list of the things that must be true for me to call this version finished. When I am tempted to add something, I check it against the list, and usually it waits.&lt;/p&gt;&lt;p&gt;The second was telling one person a date. Not a launch announcement, just a friend who will ask me how it went. It is remarkable how much a single expected question sharpens the last week.&lt;/p&gt;&lt;p&gt;I also try to ship something visible every few days, even if it is ugly. A deployed page with one working button beats a perfect local branch, because it turns the project from an idea into a thing that exists and can be improved.&lt;/p&gt;&lt;p&gt;Energy matters more than time. I have learned to do the hard thinking early in the morning, before the kids are up, and save the mechanical work for the evenings when I am tired. Matching the task to the energy is worth more than any productivity app.&lt;/p&gt;&lt;p&gt;And when a project stalls, I let myself put it on a shelf on purpose instead of letting it rot. SignalFeed is on a shelf right now. It is not dead. It is waiting for a better idea, and I am not pretending otherwise.&lt;/p&gt;</content:encoded><category>Notes</category></item><item><title>Drawing dragons at the kitchen table.</title><link>https://bradunsupervised.com/writing/drawing-dragons-at-the-kitchen-table/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/drawing-dragons-at-the-kitchen-table/</guid><description>How a Saturday sketching session turned into the art direction for our next game.</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It started with a rainy Saturday and a stack of printer paper. My daughters wanted to draw dragons, so we drew dragons, a lot of them, most with far too many legs.&lt;/p&gt;&lt;p&gt;By lunchtime there was a dragon with a teapot for a head and a dragon that was mostly a cloud. They had names, favourite foods and, in one case, a very detailed backstory involving a lost sock.&lt;/p&gt;&lt;p&gt;I scanned the best ones that evening, mostly to keep them. Looking at them on a big screen, I realised they were better art direction than anything I would have come up with: bold shapes, odd colours, and a total lack of concern for anatomy.&lt;/p&gt;&lt;p&gt;So the next Roariverse game will use them. I am tracing the drawings into vector shapes, keeping the wobbly lines, and letting each girl choose the colours for her dragons. They are taking the job very seriously.&lt;/p&gt;&lt;p&gt;There is a lesson in there about constraints. The drawings are not polished, and that is what makes them feel alive. Trying to clean them up too much would turn them into generic cartoon dragons, and nobody needs more of those.&lt;/p&gt;&lt;p&gt;Mostly, though, it was a good Saturday. The game is a bonus.&lt;/p&gt;</content:encoded><category>Family + Creative</category></item><item><title>What a port scanner taught me about timeouts.</title><link>https://bradunsupervised.com/writing/what-a-port-scanner-taught-me-about-timeouts/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/what-a-port-scanner-taught-me-about-timeouts/</guid><description>Every network call is a promise that might never be kept. Plan for that from line one.</description><pubDate>Thu, 18 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;PortScannerPro does one simple thing: it tries to open a connection to a list of ports and tells you which ones answered. The interesting part is the ports that do not answer at all.&lt;/p&gt;&lt;p&gt;A closed port usually replies quickly with a refusal. A filtered port often replies with nothing, and a naive scanner will sit there waiting until the operating system gives up, which can take a very long time.&lt;/p&gt;&lt;p&gt;My first version had no explicit timeouts. Scanning a firewalled host took minutes, and the browser tab looked frozen. The fix was obvious in hindsight: every connection attempt gets its own deadline, and the scan reports &amp;quot;no answer&amp;quot; as a real result rather than an error.&lt;/p&gt;&lt;p&gt;That changed how I think about network code everywhere. Every call to another service is a promise that might never be kept. If you do not decide how long you are willing to wait, something else will decide for you, usually at the worst moment.&lt;/p&gt;&lt;p&gt;The second lesson was concurrency. Opening a thousand sockets at once is a good way to get rate-limited or to trip somebody&amp;#39;s intrusion detection. A small worker pool with a sensible limit made scans both faster and politer.&lt;/p&gt;&lt;p&gt;The third was reporting. People do not want a wall of port numbers. They want to know whether the thing they meant to expose is reachable and whether anything else is. Grouping results that way did more for the product than any speed improvement.&lt;/p&gt;&lt;p&gt;None of this is new. But building a small tool end to end is the fastest way I know to turn advice you have read into instincts you actually have.&lt;/p&gt;</content:encoded><category>Tools</category><category>networking</category><category>tools</category></item><item><title>Write the boring version first.</title><link>https://bradunsupervised.com/writing/write-the-boring-version-first/</link><guid isPermaLink="true">https://bradunsupervised.com/writing/write-the-boring-version-first/</guid><description>Clever abstractions can wait. The plain version tells you what the real problem is.</description><pubDate>Sat, 30 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I start something new, I have a strong urge to build the general version straight away. A plugin system, a config format, a tidy abstraction that will handle every case I can imagine.&lt;/p&gt;&lt;p&gt;I have learned to resist it. The first version should be the boring one: hardcoded, a bit repetitive, obviously specific to the problem in front of me.&lt;/p&gt;&lt;p&gt;The boring version ships sooner, which means real people use it sooner, which means I find out which parts actually need to be flexible. It is almost never the parts I would have guessed.&lt;/p&gt;&lt;p&gt;When the same shape shows up for the third time, that is the moment to extract something. By then I know exactly what varies and what does not, and the abstraction practically writes itself.&lt;/p&gt;&lt;p&gt;Shapio is full of code that started out boring. The change planner, the part that turns a model edit into a safe set of steps, began as one long function with a comment at the top that said &amp;quot;split this later&amp;quot;. Later came, and splitting it was easy because I finally understood it.&lt;/p&gt;</content:encoded><category>Notes</category></item></channel></rss>