In 1998 I needed to convert PDF files to text for a search engine called Alkaline, without requiring separately installed libraries. So I wrote a C++ parser. Alkaline is another story. I recovered the source from a backup dated December 26, 1998. It reads PDF 1.0 through 1.2, follows object references and page trees, decompresses content with bundled zlib, and prints metadata, page text and bookmark titles. There are my own string, vector and hash-table libraries in there too. The encryption code is a stub. This was a work in progress. I wasn’t reverse engineering an undocumented format.…
In 1997 I wrote UcsLink.Fusion for the University of Geneva. We had hundreds of PCs across the city, Sun and Windows NT servers, and Windows 95 and Linux workstations. Getting a user’s software and settings onto whichever PC they sat down at was a job for login scripts. I wrote a Delphi 2.0 application to do it instead. UCS stood for Universal Copy System. Fusion read a script that mapped network drives, copied shared software, created shortcuts, connected printers, set environment variables and synchronized the clock. It could include other scripts, test conditions and clean up files left by…
In the 1990s I wrote a document manager for a small business in Geneva with about 20 workstations. Most of its staff were new to computers, and folders were a challenging idea to grasp. They saved Word documents wherever Word happened to put them, and the network supervisor spent his days looking for lost files by panicked coworkers. File & Folder solved that problem. My 1999 product page said it was for “a small business which is sick and tired of seeing it’s workers putting documents a bit everywhere in the system.” The first version, File & Folder 1.0, was a Visual Basic app for Windows…
In January 1998, a classmate and I wrote a game. It was called “Bolongas zBalls”, and it was a Java applet. You click to start, then move the mouse, without clicking. Touch the dark ball to blow it up, and the next ball turns dark. Clear them all and the next level doubles the number of balls. Touch a plain ball and it spawns another one, so if you’re sloppy, the balls multiply until you lose. Each level has a time limit. The intro music is the theme song from Capitaine Flam, the French version of the Japanese cartoon Captain Future, which aired on TF1 in 1981. I honestly don’t remember why…
In July 1998, the Internet Society held INET ‘98, “The Internet Summit”, at Palexpo in Geneva. The University of Geneva provided about 250 computers for the conference, and I was a student there. I wrote much of the conference network’s website, www.inet98.ch, and the launcher that ran on the public Windows 95 PCs instead of Explorer. The Inet 98 Launcher was a full-height bar on the left of the screen with a button for each installed application: Internet Explorer, Netscape, PC Pine, Telnet, WS-FTP and Office 95 and 97. I wrote it in Delphi 3. It showed a screen saver when a machine sat…
Before GNU Autoconf generated configure scripts, there was a DOS program called Autoconf. It let you keep one CONFIG.SYS and one AUTOEXEC.BAT, then choose among boot configurations by pressing a key. You did not have to wait for a menu. The PC BIOS kept early keystrokes in its keyboard queue, so you could press a configuration letter before AUTOCONF.SYS had even loaded. When the driver finally ran, it found the key and continued immediately. I did not write the original. I copied its x86 assembly source by hand from a French computer magazine. It was the first assembly program I had ever seen…
In his Rails World 2026 keynote DHH talks about one-shot apps built almost instantly with an agent as part of Omarchy. His canonical example is Omacalc, a dead simple calculator. It’s a good demo and not a hard problem. Let’s take it further and rewrite a more advanced calculator in Rust that we can also run as an MCP server, so an AI coding agent can outsource arithmetic to it instead of hallucinating a square root. In 1994 I wrote an expression evaluator for a lab assignment. It grew into Expression Calculator, a shareware Windows app I sold through a company I co-founded, Vestris Inc. In…
I write a lot, and I use AI a lot. But AI does not write my blog posts. Mostly. AI is useful for making some of my writing less tedious. Recently, I asked GitHub Copilot CLI to read a few dozen posts from this blog and derive a VOICE.md to assist my writing. The result is now checked into this repo and linked from AGENTS.md, so any AI agent working here gets the same reminder: this blog is authored by me, not by AI and the agent’s job is to help me rather than pretend to be me. The useful and interesting part of VOICE.md was how it captured my weirdly specific patterns. Start with a real…
Back in January I wrote Serving Markdown for AI Agents: for every page on this blog, there’s also a .md version at the same URL, discoverable via a tag, so AI agents can fetch clean Markdown instead of parsing HTML. It also made me $360, via referral conversions an AI agent apparently generated after reading the clean Markdown version of a post. That worked, but it lived in this repo as a one-off script, _scripts/render_markdown.rb, wired into the GitHub Actions deploy workflow (needed since GitHub Pages’ native build only allows a small allowlist of plugins, which jekyll-md isn’t on), with…
I spent five and a half years at AWS before moving (back) to Microsoft, where I work today. At Amazon, many meetings began with silent reading. Instead of presenting a document, the author gave everyone time to read it before the discussion started. There was a small facilitation trick that made this work surprisingly well: when reading began, everyone raised a hand. When someone finished, they lowered it. The meeting started with all hands up and counted down to zero. At Microsoft, I found the inverse practice: everyone began with their hands down and raised one when they finished. At the…
In 2023 I ported slack-strava to Discord by hand, evening by evening over about three weeks, and it took most of that stretch to get a working bot. This week I ported the same app to Microsoft Teams as teams-strava (“Strata”), and this time I wrote almost none of the code myself. GitHub Copilot CLI did. The 2023 Baseline discord-strava’s git history is a fair record of how long a manual port takes. The first commit, “Initial fork from slack-strava,” landed July 30, 2023. “Most interactions working” was three days later. Getting install/help/error-handling polish took through August 20:…
While reviewing dozens of PRs from ericproulx optimizing Grape’s internals recently, I noticed that the bare raise ArgumentError, "..." calls scattered across the codebase were inconsistent: some messages were capitalized, some ended in a period, most were not. Longtime readers know where this is going. Ruby’s own core and standard library exceptions don’t do this - TypeError: no implicit conversion from nil to integer, ArgumentError: wrong number of arguments, and so on all read lowercase and unpunctuated, because the message is meant to be read after the exception class name and a colon,…
distance_of_time_in_words is a small Ruby gem that turns two Time objects into a human-readable string like “3 days and 4 hours”. Two separate bug reports against it this year turned out to be variations on the same theme: computing a duration between two timestamps is not the trivial subtraction it looks like, the moment time zones are involved. Both fixes shipped in dotiw 5.6.0. Bug 1: dst? Lies When You Least Expect It #63 reported that a duration of one minute was rendered as “less than 1 second” for users in Europe/Dublin. The gem’s TimeHash had a DST correction that looked reasonable: d…
My previous post walked through four bugs in ruby-enum, a gem I maintain, all stemming from the fact that class-level instance variables aren’t inherited by subclasses. The third fix, #59, made keys, key?, value?, key, value, to_h, parse and each walk up superclass and merge in a parent’s enums, so a subclass would see everything its ancestors defined. It was correct, fully tested, and shipped. It also made every one of those methods roughly 5x slower on any subclass. def _enum_hash if superclass Ruby::Enum superclass.send(:_enum_hash).merge(_own_enum_hash) else _own_enum_hash end end This…
The ruby-enum gem is a small library I maintain that adds enum-like behavior to a class via include Ruby::Enum and define :KEY, value. Four pull requests landed against it recently, each fixing a different symptom, and all four turned out to be the same underlying bug: class-level instance variables set in a module’s included hook are not inherited by subclasses the way you might expect. All of these fixes shipped in ruby-enum 1.2.0. Ruby::Enum stores its keys and values in instance variables on the class itself, set up when the module is included. def self.included(base) base.extend…
The slack-ruby-client library, an open source Ruby gem I maintain, runs a scheduled GitHub Actions workflow that regenerates code from Slack’s API definitions and opens a pull request with the diff. The commit message and CHANGELOG entry used to be a generic “Update API (2026-08-11)”, which told a reviewer nothing about what actually changed. Here’s how we taught the workflow to describe its own diffs, using GitHub Copilot CLI, which open source maintainers can get for free. The Idea The workflow already computes a diff before opening the pull request. Instead of a boilerplate commit message,…
AI-first Engineering is now the operating default in many organizations, including mine. Developers have kept up. Vibe coding became agentic engineering, and the race towards truly autonomous engineering isn’t slowing. But what are we, managers, to do? A manager job continues to include recruiting and retaining people, setting clear goals and expectations, fostering communication, owning team workflows, and coaching daily behaviors. To this list we must now add driving AI transformation. Being a passive advocate is not enough. Unlike other subjects, no amount of AI training will help you keep…
In April 2025 I wrote Apologizing for My Obsessiveness Over Punctuation, a post about my various organizational compulsions. Near the end, almost as an aside, I mentioned that I obsessively categorize expenses in Monarch, a personal finance app, linking it with my referral code. That post earned me exactly one referral in the nine months that followed. Then, on January 15th, 2026, I wrote Serving Markdown for AI Agents. The idea is simple: for every post on this blog, there’s now a .md version at the same URL. AI agents can discover and fetch clean markdown instead of parsing HTML. I didn’t…
Before AI coding assistants, a typical engineering team built expertise with the years: new team members joined, contributed small bug fixes, then were given more ambitious tasks over time as they became more comfortable in a codebase, to ultimately become experts. This process took years. In contrast, today, almost every engineer, and many non-engineers, have access to tools that can gather context from any codebase, and produce a large volume of high-quality code in a day. This lowers the bar for contributing meaningful changes to the software we build and enables many more individuals…
Yesterday I described AI-generated code as “plausible-looking, locally coherent, globally wrong.” Here’s a concrete example from my own codebase. I needed a cleanup job to close old Slack group DM conversations in my slack-sup2 app. The AI-generated solution looked perfectly reasonable: def close_old_sups! return 0 unless sup_close old_sups = sups.where(conditions) old_sups.each(&:close!) old_sups.count end def close! return unless conversation_id return if closed_at logger.info "Closing DM channel #{conversation_id}..." slack_client.conversations_close(channel: conversation_id)…
Read at the source
Your visit, your choice.
Optional Google Analytics helps us understand visits. Microsoft Clarity records masked interactions to improve the site. Optional tools stay off unless you choose them. Privacy details.