Monday, February 16, 2026

AI-Driven Development: Insights from Day One

Hello from February 2026, where the way we as software developers get our jobs done on a daily basis is in the process of changing rapidly, with the advent of AI code-writing tools! 

Today being a company holiday (Presidents' Day here in the U.S.), I decided to spend the day getting in some self-guided hands-on practice with using some of this new AI tooling. Specifically, I decided to implement a user-requested feature for TimeSnapper for MacOS: Zoom-in / zoom-out functionality on images displayed in the playback view. I'd do this not by coding the feature myself -- as I'd always have needed to do in the past -- but by having AI write the code for me. 

For context: TimeSnapper is a utility app that runs in the background on your Mac or Windows computer, and captures screenshots every few seconds, making it possible for you to go back and look at what you were seeing earlier in the day (or even on previous days). Here's what the playback window looks like in the Mac version:

TimeSnapper for MacOS playback window

I didn't spec out a whole lot of requirements up front. I generally just wanted the zoom functionality to work "as a user would expect" -- i.e., having more or less the same familiar user experience as when using the Preview (image viewing) or Apple Maps apps on a Mac.

For the AI tooling, I used Claude Code v2.1.42 with the Claude Sonnet 4.5 model, running in a terminal window in my repository's directory. For the IDE, I used the standard XCode 16.4 atop my MacOS Sequoia.

Hey little sister, what have you done? 

Here's what I went with for my initial Claude prompt:

TimeSnapper (this repository) is an application that runs in the background and periodically takes screenshots of what the user is working on, for the user to review later. The PlayYourDay* project files implement a window where the user can review screenshots. The PlayYourDayImageView is where the screenshots are actually displayed to the user. Change or replace that view to allow the user to pan and zoom the image being displayed. Some specific requirements:

  • The user should be able to zoom in with the keyboard shortcut cmd+=, zoom out with cmd+-, and restore the zoom to the default level with cmd+0
  • The user should be able to zoom in and out by holding the cmd key and scrolling the mouse wheel up and down.
  • When the image is larger than the window, the window should render scroll bars to allow the user to pan the visible portion of the image.
  • When the image is larger than the window, the user should be able to pan the image by holding down the left mouse button and moving the mouse. 

The model "thought" for several minutes, and then made some changes to my project's files.  

So, I went ahead and shipped the changes to the App Store!

...In actually, no. I did not do that. (Although I imagine that there now are folks out there shipping apps this way: What's stopping them? Read on for why it's probably a good thing that I didn't just vibe-and-ship!)

Instead, I took at pass at reviewing what the AI had done, by:

  1. Reading through the output in the Claude window;
  2. Reading through the changes to the source files (using GitHub desktop);
  3. Testing out the changes in my local running TimeSnapper app. 

It turns out that, for me, today, only that 3rd item had much value. Claude's explanation of what it was doing showed that it seemed to be on the right track, and didn't include anything that looked "off." Likewise, the code changes all seemed plausible; I didn't spot anything egregious. 

So, I proceeded with testing the changes. In retrospect, I basically acted as a QA engineer counterpart to Claude as the software developer. (On top of the "product manager" and "UX designer" hats I was already wearing to tell Claude what to build in the first place.)

Initially, I was impressed by the changes! I could indeed zoom the image in and out by pressing cmd+-  / cmd+=. I could also zoom the image using cmd+mousewheel. I could pan the image with a left-click drag. This was a pretty striking improvement over "no zoom or pan functionality at all", as was previously the case!

Once I got past the surface level of the new functionality, however, I pretty quickly noticed some issues with the behavior. I decided to go ahead and have Claude iterate on what it had built. So, without clearing Claude's context, I told it:

It works pretty well, but there are some bugs:

  1. When panning the image by holding down the left mouse button and moving the mouse, moving the image left/right works correctly, but moving the mouse up/down moves the image in the OPPOSITE direction that the mouse is moving. The image should move in the same direction.
  2. When the image is zoomed in, after pressing Cmd+0 to restore the default zoom level, the image is still partially vertically panned out of the viewable area of the window. The image should automatically be moved to be within the viewable area of the window, if possible.
  3. Trying to zoom in the image past 100% doesn't work. Upon a Cmd+= key press, the image moves up and to the right, but it doesn't "zoom in" (visibly become any larger). The "canvas" (the scrollable area of the window) _does_ become larger, but the image itself doesn't correspondingly change size to fit.

Claude again "thought" for a couple of minutes, then made another round of code changes. Ok! I jumped in to QA the changes again -- and found that the drag-to-pan functionality was no longer working. I also noticed an additional bug: The right-click context menu for the displayed image no longer appeared adjacent to the mouse cursor, but (for some reason) below the playback window. 

Once again, I told Claude the problems; a couple of minutes elapsed; Claude made some code changes; I QA'ed; and I noticed additional bugs/problems and/or regressions. A sampling of these additional problems, each encountered after a round of having Claude make changes, and me manually testing the results:

  • When pressing Cmd+0 to restore the default zoom level, the image is vertically scrolled down past the bottom of the viewable area of the window.
  • When beginning to pan the image using left-click-drag, as soon as the mouse is dragged a little bit, the image "snaps down" and partially moves below the bottom of the viewable area of the window.  
  • When panning around the image, during the mouse drag, the position of the image "stutters" a bit: It abruptly jumps back and forth by about 20 pixels. 
  • When zooming the image using cmd + mouse wheel (in either direction -- either zoom in or zoom out), the vertical position of the image changes, such that there is now empty space between the top edge of the image, and the top edge of the window.
  • Regression: The mouse wheel zoom should be "centered" on the mouse cursor position, but it is instead centered on the lower-left corner of the image. 

During this whole process, I was jotting down my observations and insights (during those 2-3 minute periods while Claude was working on each set of changes). A couple of insights I had after the first ~75 minutes or so of this:

  • From my notes: "I feel like a QA engineer who is dealing with a kind of careless developer who repeatedly keeps 'throwing changes over the wall to QA' without testing them." 
  • As Claude repeatedly struggled to successfully fix a couple of specific bugs: "At this point I'm getting concerned that these rounds of changes could be making the codebase worse instead of better. Should I be setting 'save points,' e.g. via git branches? Or should I be doing a commit after each incremental change, and squashing them all when things look ready to publish?"

As I played with the work-in-progress changes, I also realized that there were a few behaviors that I hadn't specified up front to Claude, but that became apparent through the process of playing with the app. These included both minor nuances of behavior, and more significant features. An example of each:

  • The zoom "increment" when using the mouse wheel feels best when it is small. When using the keyboard, it feels better to have a bigger increment. 
  • Additional feature: Cmd+9 should perform a "Zoom to fit." That is, to set the zoom level as large as possible, with the constraint that the image still fits within the bounds of the window. (Same functionality and keyboard shortcut as the MacOS Preview app.)  

This aspect of software development is nothing new. AI or not, it has always been that case that certain behaviors and requirements are "discovered" during the process of the software being built, rather than having been specified up front. (Y'all have heard of agile, right?) A takeaway that I have from this:

  • Having AI write code doesn't solve the bottleneck that occurs when coordination is needed between roles (technical, UX designer, product) as questions arise. The more that the software developer (or whomever is "piloting" the AI) themselves understands the multiple considerations/aspects of the software being written, the lower the cost will be in time and context switching. 

At about the 3-hour mark, I had reached a point where there were still a couple of non-trivial bugs that Claude was having trouble solving. Despite a few rounds of me describing the problematic behavior, and Claude making a set of changes and cheerfully reporting that the problem should now be solved, the bugs persisted. Insight I had at this point, from my notes:

  • "I feel like I'm at a point of diminishing returns of the AI. A skilled developer would have initially built the implementation such that all of these remaining persistent issues would not be happening in the first place." 

In other words, I had seemed to have reached a point where the AI's attempts to fix problems by tweaking its implementation just wasn't working. The underlying structure/framework that the AI had built initially at the beginning of the day atop my human-authored master branch was such that it just didn't seem to be able to fix certain behaviors -- sort of akin to a house that had been built with a creaky frame. 

It's a nice day to start again 

With this in mind -- and remembering how quickly Claude had built its initial implementation (a few minutes) compared to how long I'd gone back and forth with Claude to get the software to be high enough quality that I wouldn't be embarrassed to ship it (multiple hours at this point), I decided to try "starting fresh": I saved what I had so far to a git branch; I went back to the master branch; I cleared Claude's context; and I gave Claude a fresh set of instructions, with more complete requirements, incorporating what I'd learned over the past few hours:

TimeSnapper (this repository) is an application that runs in the background and periodically takes screenshots of what the user is working on, for the user to review later. The PlayYourDay* project files implement a window where the user can review screenshots. The PlayYourDayImageView is where the screenshots are actually displayed to the user. Change or replace that view to allow the user to pan and zoom the image being displayed. Some specific requirements:

  • The user should be able to zoom in with the keyboard shortcut cmd+=, zoom out with cmd+-, and restore the zoom to the default level with cmd+0. 
  • Upon a cmd+9 press, a "Zoom to fit" operation should happen, with the zoom level being set to a multiple of 0.1 such that the image fits entirely within the window. 
  • The default zoom level when the "Play Your Day" window is opened should be 1.0. 
  • The zoom increment when zooming with the keyboard should be 0.1.  
  • The minimum zoom level should be 0.1. The maximum zoom level should be 5.0. 
  • The user should be able to zoom in and out by holding the cmd key and scrolling the mouse wheel up and down. The zoom increment for mouse wheel zooming should be 0.05. 
  • When the image is larger than the window, the window should render scroll bars to allow the user to pan the visible portion of the image.
  • When the image is larger than the window, the user should be able to pan the image by holding down the left mouse button and moving the mouse. 
  • The "View Image File in Finder" context menu should no longer display on left-click -- it should display only on right-click. (Left-click will now be used for the image panning functionality.) 
  • The "View Image File in Finder" context menu should always display adjacent to the mouse cursor position, even when the image zoom level is something other than 1.0.
  • Whenever the image fits within the window horizontally, the image should be anchored to the left edge of the window.
  • Whenever the image fits within the window vertically, the image should be anchored to the top edge of the window (NOT to the bottom edge).

This approach ended up working well (for me, today, for this particular project!). While the resulting implementation still had some problems, Claude seemed to build a more solid "foundation," given the fresh context, and the more complete set of requirements up front. 

Within about an hour of additional rounds of QA and back-and-forth with Claude, I had the zoom feature working to a point where I was happy enough to be able to ship it to the App Store.

Insights from that last hour of work:

  • Human attention to detail still matters. I was having Claude fix such minor/nuanced behaviors that I noticed as: After zooming one (small) increment via the mouse wheel, it was not possible to get back to the "default" (100%) zoom level, or to the minimum or maximum zoom levels, via the keyboard (large increments) -- because Claude had implemented keyboard zoom to just add/subtract 0.1 from the zoom factor, rather than having the zoom factor "snap" to the nearest multiple of 0.1. 
  • AI does not produce perfect results. The build I ended up shipping still had a minor bug where sometimes, after zooming in or doing a "restore to 100% zoom", the image would be vertically scrolled down slightly (instead of the image being "anchored" to the top-left corner). Despite a couple of attempted rounds of having Claude try to fix this, Claude was unable to do so. I decided that it wasn't (for now) worth my time to manually track this bug down in the Claude-authored code -- which I don't have a deep understanding of, because I didn't write it! -- and fix it.

Take me back home, yeah 

Ultimately, the day was a success! I was able to ship a new feature for TimeSnapper; and I got in some valuable early reps in using AI tooling to write code. 

Wrapping up: A few big obvious caveats apply to this entire post!

  • As stated up front, today was the first day where I was able to devote an entire day to playing with AI-driven development. Ask me again how things are going after I've had more chances to do this in a few weeks; or at this time next year. Not to mention the fact that the available tools themselves are undergoing such rapid changes. 
  • This project was building on a standalone Objective-C Mac desktop app, with changes focused around new GUI behaviors. Would things have gone differently (better; worse) in some ways if I were instead working on an app with, say, a Python back-end, a React front-end, B2B API integrations, and changes focused around CRUD forms? I bet they would have!

Finally: For what it's worth, this post (as with all posts on this blog) is 100% human-authored, by me. (Although there's currently little stopping this same claim from being made on prose that is AI-generated! Interesting times!)

Sunday, December 28, 2025

The Jon Schneider Game of the Year awards: 2025

As has become my annual tradition, here's the list of my favorite computer and console video games that I played for the first time during 2025 (along with the platform(s) that I played them on)!

1. Hades 2 (Windows)

I really don't know how Supergiant Games does it. The original Hades was my personal 2020 Game of the Year, with its combination of tight top-down-view action combat, great voice-acted characters and character development, and excellent music. Hades 2 delivers more of the same, except with even more interesting combat and varied gameplay. I put in the time to 100% the Hades 2 Steam achievements, and enjoyed every run. 

There's certainly something special about a game where -- despite there being no gameplay benefit or purpose to it -- you can opt to spend some time having your character join in and beautifully harmonize with a song that another character visiting your home base is singing.

2. StarVaders (Windows, Steam Deck)

Like it says on the tin, StarVaders is a roguelike deckbuilder that takes place on a Space-Invaders-like battle map. Cards are used to make your upward-facing spaceship move and fire up at the alien invaders moving down to attack you. Game balance is solid; and lots of different character classes, character variants, difficulty levels, and missions provide lots of replayability. Recommended for fans of Slay the Spire and similar games! 

3. Metaphor ReFantazio (Windows, Steam Deck)

Although I'm not a big "AAA" game player, Metaphor ReFantazio hit a sweet spot for me with its "Persona, but in a unique swords and sorcery fantasy setting" gameplay. I played all the way through the ending during the first few months of 2025.

4. Mario Kart World (Switch 2)

I particularly like the new Knockout Tour mode (in single-player) -- it really takes advantage of Mario Kart World's interconnected world to delivery a fun continuous 15 minutes or so of racing. Mario Kart World is also 2025's Schneider Family Game of the Year, managing the rare feat of getting us playing a video game together in 4-player split-screen. 

5. Monster Train 2 (Windows, Steam Deck)

Just as with Hades 2: Monster Train 2 is like the original, but more and better! I had a good time discovering Monster Train 2's various synergies, both within the various factions, and in the intra-faction combination pairings. And I particularly enjoyed the major content unlock that occurs midway through the game. 

6. 8-Bit Adventures 2 (Steam Deck, Windows)

My favorite classic-style turn-based RPG of 2025! A wholesome save-the-world adventure with likeable characters. I haven't played the original (but plan to circle back to it soon, as the developer, Critical Games, added Steam Cloud saving to the Steam version, at my request!!) I played all the way through to the end. Recommended for fans of classic-style JRPG-like games

7. Donkey Kong Bananza (Switch 2)

Here begins the portion of this year's list of games that I was impressed by and enjoyed, but didn't yet make it all the way through to completion. Bananza certainly meets the "impressed me" criterion, with its preponderance of destructible terrain combined with solid 3D platforming level design. I like how powerful DK feels in this game: In Mario games, Mario generally needs to be pretty cautious of enemies; by contrast, DK can readily clobber just about anything he comes across! 

8. Tactical Breach Wizards (Windows)

First off: Tactical Breach Wizards features the wittiest writing in a video game that I've come across in my long history as a gamer, so it's got that going for it! Beyond that, the world imagined by Tactical Breach Wizards -- a modern world, but where magic is real, and thus is the primary tool employed by special-forces agents -- is a great playground for the turn-based tactical combat gameplay. I'm currently enjoying making my way through this game in short sessions of a mission or three each morning before work, mouse in my right hand, coffee in left.

9. Clair Obscur: Expedition 33 (Windows)

I probably needn't explain much about the game that has already featured so prominently on so many 2025 awards and Game of the Year lists. I feel like I should have liked the game's turn-based combat (but with dodges requiring real-time reflexes) more than I did -- Paper Mario (N64) and Thousand-Year Door are both on my all-time favorites list -- but I never was quite able to really get the hang of dodging terribly reliably, and 33 seems to want you to be pretty good at dodging in able to be successful in combat.  I made it a fair ways into the game, but didn't end up completing it. 

10. Rift of the Necrodancer (Windows, Steam Deck) 

I'm a bit of a fan of Cadence (including her Hyrule appearance), and so was looking forward to Rift! I spent a fair bit of time with it when it came out, and did play all the way through the story. I'm (evidently) not super talented at this game, though, and topped out at Medium difficulty on most songs, and Hard on the easier ones. 

(However -- and since this is my blog, I'll just go right ahead and dad-brag a bit -- my 20-year-old son is the current #1 world record holder of multiple Rift songs in "Impossible" difficulty on the Switch platform, and also has a bunch of top-percentile scores on the Steam platform -- despite carrying an otherwise-full load as a Chemical Engineering major and marching band member at university!) 

(Aside: New game developers, consider resisting the temptation to include the word "Rift" in the name of your new game. There are quite a few such games out there already! ðŸ˜…) 

Appendix: NOT-new-in-2025 Multiplayer GOTY

My continuing-favorite game of 2025 was once again Super Smash Bros. Ultimate on the Switch! The aforementioned 20-year-old son and I enjoy playing the online 2v2 mode and taking on all comers -- and, per the in-game stats, managing to win matches at a ~85% clip! I'm decent at Smash and generally hold up my end of the team with my Bowser Jr. and Samus; but "20" (who can skillfully play the entire roster(!), but probably likes best Lucina, Mii Brawler, and Pyra/Mythra) definitely does most of the heavy lifting! 

Appendix: Games in Progress

Fields of Mistria (Windows, Steam Deck)

Field of Mistria is essentially Stardew Valley, but different! I spent a solid few weeks with this game midway through the year, and enjoyed some of the changes and improvements it makes on Stardew Valley. I decided to put the game back on the shelf for now, though, pending the rest of the planned content (including the full implementation of PC-NPC relationships) being added to the game. Hopefully that'll be later on in 2026!

CrossCode (Windows, Steam Deck)

Although CrossCode is a somewhat older game -- having come out in 2018 -- I just discovered it in December 2025, it having been one of a few inexpensive games that I picked up in the Steam Winter Sale. (I think I may have tried but bounced off of a CrossCode demo back around the original release date.) Wow, there's a lot I'm liking here so far. A twin-stick action-adventure game (like my 2024 GOTY, Minishoot' Adventures)! A simulated MMORPG game, bringing back some fond memories of when I casually played World of Warcraft in its early days? Pixel art? Firm but fair puzzles? An intriguing story? There's a lot here so far that falls pretty squarely in my personal wheelhouse!

At times, CrossCode has felt like it may wind up earning a place among my favorite games of all time! At other times, it has been a little frustrating or obscure. I've managed to play through to what feels like it might be about the halfway mark without resorting to any hint guides, though, so far. I'm looking forward to continuing to play in 2026, forming a fuller opinion --  and maybe featuring CrossCode in my 2026 GOTY list post!

Tuesday, September 23, 2025

N+1 query problem? More like 1+N query problem!

Some years back, when I first learned about the N+1 query problem, I had a little bit of difficulty wrapping my mind around it -- or at least the naming of it. Inadvertently making lots of round trips to the database is bad, sure. And the problem gets a lot worse when you have record counts in the thousands (or more) instead of just a handful, sure. But why are we calling this "N+1"?

I eventually realized that the problem is that the nomenclature is backwards, chronologically! The order of operations happening when this problem gets triggered is:

  1. The database is queried for a list of records. (1 query)
  2. For each of those records, a follow-up query is made. (N queries)

I started thinking of the problem instead as the "1+N query problem." And for me, at least, the naming immediately became much more intuitive to grasp!

 


A quick perusal of search results makes it clear that this is a pretty #unpopularopinion. Almost all of the discussion of this problem out there exclusively uses the "N+1" terminology. 

I'm going to keep saying "1+N" in conversations with peers, though. I don't mind being one man standing against the tide on this specific issue! 🌊👨‍💻

Thursday, July 10, 2025

TimeSnapper for Mac: Custom Directory Selection Now Available!

TimeSnapper for Mac has been updated to allow a custom directory to be selected for your captured screen images!

If you're not already familiar with TimeSnapper, it's a utility that runs in the background on your Mac or PC and saves an image of your screen(s) (or just the active application window) every few seconds. This can be highly useful when, to take a few examples: 

  • A piece of text you were composing got lost because your browser crashed, or because a web form submit failed. (Retrieve the text out of your screenshot history!) 
  • You want to search up information about a specific error message that you encountered a few minutes ago, for which you don't remember the exact wording. (Again, it'll be waiting for you in your screenshot history!) 
  • You want to remember what all you were working on the afternoon of two days ago. (Click and drag across the TimeSnapper timeline UI to pan through your screenshot history!)  

Previously, the Mac version of TimeSnapper saved your screen images to subfolders of a particular "sandboxed" directory that the TimeSnapper app was by default permitted to access. This worked, but the target folder was in a fairly deep location on the MacOS filesystem, and thus was inconvenient to access by any means other than the "View in Finder" button in TimeSnapper's Preferences window.

Now, starting in version 1.1.0, TimeSnapper for Mac has been enhanced to use security-scoped bookmarks to allow you to choose any folder on your local computer for TimeSnapper to use to save its screen images. 

To use the new feature, after updating your copy of TimeSnapper to the latest version via the Mac App Store, click on the TimeSnapper icon in your Mac's top menu bar, then select Preferences, then the Auto Cleanup tab in the Preferences window. You'll see a "Change folder..." button in that window. 

TimeSnapper Preferences window > Auto Cleanup tab 
If you aren't already using TimeSnapper, take a look at the reviews and ratings (4.8 out of 5 stars as of today!) of TimeSnapper on the Mac App Store, and treat your personal productivity to a lifetime license for a quite reasonably low price!

Tuesday, May 27, 2025

Fix: Large file upload tests in a Rails app start failing after bumping rack to 3.0.16

Problem: After bumping the Rack gem in a Rails app from 3.0.14 to 3.0.16, a couple of tests which tested an HTTP POST of a large-ish (~30 MB) PDF file to a controller started failing. Other similar tests, which did not upload a large file, continued passing.

The failure symptom was the POST returning an HTTP 400 Bad Request error. The error was returned before the request made it to any of our application code (as evidenced by none of our breakpoints being hit, or debug statements appearing in the output log).  

For our particular test, the specific error message was: Minitest::Assertion: Expected response to be a <413: payload_too_large>, but was a <400: Bad Request>

Fortunately, there were no other gem dependencies that came with the bump of Rack to 3.0.16. So we knew the problem must be caused by some change in Rack 3.0.15 or 3.0.16. 

Solution: The single item in the rack 3.0.16 release notes did turn out to be the pertinent change:

Security: CVE-2025-46727 Unbounded parameter parsing in Rack::QueryParser can lead to memory exhaustion.

As that linked advisory mentions, a new check is added in rack 3.0.16 that turns away "extremely large" application/x-www-form-urlencoded bodies. From this nist.gov page on CVE-2025-46727, links were available to the fix's specific code changes. From the first linked change, we can see that a "BYTESIZE_LIMIT" of 4194304 (a little over 4 MB) is now imposed on application/x-www-form-urlencoded request bodies. 

Per the mozilla.org page on HTTP POSTapplication/x-www-form-urlencoded is only one possible form Content-Type HTTP header value -- and not the one that is supposed to be used with binary data, such as PDF file uploads! Instead, multipart/form-data should be used.

Our original unit test code was actually not specifying any Content-Type HTTP header value in our test POST request. Changing the test's POST request to additionally send the HTTP header Content-Type: multipart/form-data fixed the problem, and got our test passing once again.

Aside: When I spent a minute during the investigation to ask ChatGPT about this problem -- a POST request failure introduced as of Rack 3.0.16, possibly due to a large request payload -- ChatGPT obligingly hallucinated up a plausible-sounding (but 100% fictional) response about Rack introducing a default limit in POST request payload size, and a corresponding plausible-sounding (but 100% fictional) response about a configuration line to be added to config/applicaiton.rb to override the supposed new behavior. 😂🙄