Skip to content →

Month: August 2026

Revisiting MLB Opening Day and Purchasing MLB Tickets

Revisiting MLB Opening Day

Pennant races are heating up for MLB but I want to step back to opening day for the 2026 season for a minute. For the last several years I’ve made last-minute purchases for tickets to the opening day game for the Brewers. Not so this year.

When I went to search for tickets, I discovered that the Brewers, like a lot of MLB teams as far as I can tell, made a fundamental change to their ticketing platform. The older system was never perfect but in the absence of good accessibility had at least one feature that I found helpful in trying to get tickets. There was an option to have the ticketing system choose the best available seats at the price you wanted to pay. That is now gone.

The accessibility of the new system was lacking in many ways beyond this on opening day. MLB has corrected some of these but in my opinion, the system is still fundamentally flawed for being able to make intelligent choices about ticket purchases.

Today, when you get to the actual ticket purchasing experience, MLB presents you with a list of sections in a baseball stadium. For American Family Field, this is strictly a list of numbers starting at 101 and going up to something around 442. No context about where in the stadium each section is or anything else that would help you to make an informed purchase.

Picking a section gives you a second list of seats in that section which indicate the section, row and seat. This is marginally better.

The Potential

MLB’s current system is much like a minor league ball player trying to make it to the show. There are flashes of brilliance but then so many shortcomings that you have to wonder if the major leagues are ever a possibility.

For example, with those sections I mentioned, there is no way to move more than one section at a time with the keyboard. Fine if you do not want to allow data entry but basic usability would allow for support of scrolling with keys such as page up and page down or home and end.

Also, when you first get into the ticketing platform, you get the all-too-common message about some accessibility system being in place because you are greeted with this message:

Please note: This website includes an accessibility system. Press Control-F11 to adjust the website to people with visual disabilities who are using a screen reader; Press Control-F10 to open an accessibility menu.

Exactly how this improves the rest of the experience is not something I’ve been able to determine so far. What I can tell you is what it does not do. It does not tell me where any of those sections are. It does not make page down work in that list of sections. It does not turn 101 through 442 into anything I can reason about. An overlay announces that accessibility has been considered, which is a different thing from the experience being usable, and the two get confused far too often. Handing me a keyboard shortcut for an accessibility menu and handing me a list that runs from 101 to 442 with no context are the same move: the appearance of information in place of the information.

MLB Ticketing Experience

An example of the ticketing experience MLB uses can be found by starting at www.brewers.com/tickets and then choosing Single Game Tickets.

A Sample Solution

Using a combination of various AI prompts to review visual seating charts, search stadium information and more, I put together an example of the sort of solution I’d like to see MLB and other stadiums and ticket sellers make available. You can find my solution at https://theideaplace.net/projects/mlb/.

The Basic Solution

The pitch is simple. A seating chart answers one question at a glance: where would I actually be sitting? My guide answers that same question in words.

Pick a ballpark and you get every section in it. American Family Field has 151. For each one you get the level it sits on, the ticket zone it belongs to, how many sections it is from home plate and in which direction, how many rows deep it is, which row you enter at, and which end of the row seat 1 is on. Section 116 stops being a number in a list and becomes something you can reason about: Field Level, Field Infield Box, one section from home plate toward first base, rows 1 through 21, entered at row 21 from the top, netting in front, very sunny early in a day game and shaded by roughly two in the afternoon in July.

That last item, the seat numbering, is the piece I most wanted and the piece nobody publishes plainly. Seat numbers run in a direction, and that direction reverses at home plate. At American Family Field, section 116 on the first-base side has seat 1 at the end of the row closest to home plate. Section 121 on the third-base side is the mirror image, so seat 1 there is the end farthest from the plate. Same ballpark, same tier, opposite answer. If you care whether you are on the aisle or buried in the middle, or whether you are drifting toward the infield or away from it, that is the difference between choosing a seat and guessing at one.

Rows are the other thing a chart shows and a list does not. Terrace Box 420, directly behind home plate, has rows 1 through 3, then row 5, then rows 8 through 25. Rows 4, 6 and 7 do not exist. That gap is where a wheelchair platform sits. There is no way to work that out from “Section 420, Row 8” on a ticketing page, and it matters quite a lot if row 5 is the row you were told to look for.

The page is built to be read rather than looked at. Every section is its own heading, so you move through the ballpark the way you move through any well-structured page. A search box takes a section number, a zone name or a landmark, so typing dugout or bullpen narrows the list to the sections near one. You can filter by level. There is a single table of every section for scanning, and the underlying data downloads as a CSV if you would rather sort and filter it in a spreadsheet.

One rule I set from the start is that where a source does not state something, the guide has to say so rather than quietly filling the gap. At Sutter Health Park, no source I could find publishes which side seat 1 is on, so that guide says exactly that instead of inventing an answer. Every section also carries a confidence rating, and each ballpark has a list of the questions I could not settle. A guide that admits what it does not know is more useful than one that reads smoothly and is wrong in places you cannot identify.

There are now thirty ballparks in the guide, 4,723 sections in total, covering all six divisions. None of this is technically hard. It is headings, tables with real column and row headers, and plain text. That is rather the point. The information already exists somewhere in every one of these ticketing systems. What is missing is the decision to present it in a form you can actually read.

A Note on Data

As I said, this content was all generated by AI. Obviously I’m not able to see the seating maps to validate this. I have tried to review the process I used and some of the information but am providing this guide as an example. Separate the concept from the data.

I’d also rather be told about an error than not. If you know a ballpark well and something in one of these guides is wrong, I want to hear about it. You can file an issue at https://github.com/kellylford/TheWorkBench/issues and I will take a look. But the argument I’m making here does not depend on my data being perfect. It depends on the fact that MLB and the ticket sellers have the real data, know it is correct, and still hand me a list of numbers from 101 to 442.

Leave a Comment

ClaudeSpeak: Having Claude Code Read Its Replies Through Your Screen Reader

I have been doing a lot of development work with Claude Code, and the thing that slowed me down most was not the AI. It was reading the output.

In my experience both the Claude desktop app and the terminal have challenges with screen readers. The desktop app often requires scrolling around in a screen reader’s view of the text, using scroll to bottom, and generally fishing around too much to find what was just said. The terminal, when a screen reader is set to echo back terminal output, rereads all sorts of terminal output, even while you are entering new commands.

ClaudeSpeak is aimed at improving on both situations. It makes Claude Code speak each reply once, cleanly, as soon as it finishes — and it can speak through your screen reader rather than around it.

Screen reading users of Claude’s terminal may also want to try a new screen reader mode setting as well. Details on what it does and how to enable it are at Use Claude Code CLI with a screen reader.

That setting and this are addressing different halves of the same problem — one changes what Claude Code renders, the other changes how the reply reaches you — so they are worth trying together rather than instead of each other.

The project lives here: ClaudeSpeak on GitHub.

Up front: I built this with Claude, and Claude wrote essentially all of the code and documentation. I directed it, tested it, and told it when something sounded wrong. I am flagging that partly for transparency and partly because it is relevant to how you read the technical claims further down — those are Claude’s findings and Claude’s understanding, not things I independently verified.

It costs nothing in Claude usage. The text is pulled out of the session transcript by a hook, so the model is not involved and no extra tokens are spent.

JAWS exposes a COM interface and NVDA has a controller client library. Going through either means Claude’s replies use the voice, rate and punctuation level you already configured, and your usual silence key interrupts them like any other speech. If you would rather have a separate Windows voice so Claude is distinguishable from everything else, that works too and is a config setting.

Installing

There is an install.bat in the repo. Run it from the ClaudeSpeak folder and it does four things: copies the PowerShell scripts into your .claude folder, installs the skill, writes a starter config set to pick whichever screen reader is running, and adds the hook to your Claude Code settings.json. Then restart Claude Code, because hooks and skills are both read at session start.

The settings.json part is the bit that could hurt, so it is written to be careful. It merges rather than replaces, so anything already in that file stays. It takes a backup first. If you already have a Stop hook — I do, for chat logging — it adds alongside rather than on top. Re-running it is safe: an existing config or hook is left alone. And if your settings.json is not valid JSON it refuses to touch the file at all and prints what to add by hand.

You can see what it would do without changing anything:

powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1 -WhatIf

Please read the installer before you run it. I would say that about anyone’s install script, but particularly one written by an AI, for a file you care about.

An important caveat: the installer has only been tested on my machine. Claude exercised it against throwaway targets covering a fresh install, merging into an existing settings file, adding alongside an existing Stop hook, running it twice, and a deliberately malformed settings.json — and that testing did find two real bugs before they shipped. But that is one machine, one Windows version, one Claude Code version, and one person’s settings file. It has never been run on anyone else’s setup. If it does something unexpected on yours I would genuinely like to know, and there is a manual setup path in the README that is maybe ten lines of copying and one edit.

Whichever way you install it, it applies to every Claude Code session on the machine, the desktop app as much as the terminal. It does not affect claude.ai in a browser, which is a different product with no hooks.

Controlling What Gets Spoken

This part I did want, and it works. Thinking, tool calls and tool output are never spoken — only Claude’s actual prose, and only the newest reply. On top of that you can choose what happens to code blocks (announced, skipped, or read out), tables, and URLs, and you can ask for only the first paragraph or cap the length.

You can set all of that by just saying so — “stop reading me code”, “just the gist”, “read the actual links” — via an included Claude Code skill. If you have not used skills before, they are simply a folder with a SKILL.md file in it, dropped into your .claude folder; there is no install step beyond copying it, and the repo explains the general mechanics.

Two Things Claude Had to Sort Out With NVDA

Both JAWS and NVDA work. Getting NVDA there took some digging, and the findings seem worth passing on. To repeat the caveat above: this is Claude’s understanding, arrived at while debugging on my machine. I do not independently know it to be true, and I would be glad to be corrected.

The first is that the NVDA controller client DLL does not appear to ship with NVDA. Claude could not find anything matching that name anywhere under the install directory on 2026.1.1. It came from NV Access’s controller client package, which is a separate download.

The second is architecture. The DLL apparently has to match the architecture of the process calling it, not of NVDA and not of Windows. I am on Windows on ARM, where the inbox PowerShell is ARM64 and the other one is x86, so an x64 build — the one you are most likely to already have — would not load at all. The download contains several builds and Claude picked the matching one. Claude also found that the filenames are not a reliable guide to what is inside, so the scripts read the file header instead.

There was also a debugging trap worth mentioning regardless of any of the above. The engine falls back to another speech route when the configured one fails, so speech happening does not prove your route worked. A failing NVDA config fell through to a Windows voice and sounded like success — twice — before we caught it. There is now a log recording which route actually spoke. If you test this, check the log rather than trusting your ears.

Where Help Would Be Welcome

  • NVDA is verified on Windows on ARM. Plain x64 should be simpler, but nobody has actually run it there.
  • Narrator, VoiceOver and Orca are not wired up.
  • The macOS and Linux side is much thinner than the Windows side.
  • The choices about what to strip before speaking are guesses. If they are wrong for how you work, I would like to hear it.

Suggestions, corrections and pull requests are all welcome. Open an issue or send a pull request, or leave a comment here.

Kelly Ford

Leave a Comment

Building the Live Captions App I Wish Windows Shipped

I’ve been sitting on this project for a couple weeks, and it’s time to write about it.

Windows has had a Live Captions feature for a couple of years now. Turn it on and a small bar appears at the bottom of the screen, showing whatever it’s currently hearing — your microphone, a meeting, a video, anything the system is playing. For a sighted person who just wants a quick visual backup while they half-listen to something, it does the job. For someone using a screen reader, it barely exists. Narrator, NVDA, and JAWS have no reliable way to get at what’s in that bar. There’s no history — once a line scrolls off, it’s gone. You can’t review it in a meanaingful way, you can’t copy it, and there’s no way to arrow up and re-read something you missed. It’s a caption feature built entirely around glancing, and glancing isn’t an option for me.

So I vibe-coded my own.

It’s called Accessible Live Captions, and the pitch is simple: same idea as Windows Live Captions — real-time, on-device transcription of your microphone or your system audio — but built from the start around a screen reader, not around a glance. Everything lands in a real transcript: a scrollable, keyboard-navigable list where every finished line is its own focusable item. Arrow up, arrow down, Home, End — you review it exactly like you’d review any other list in Windows. Each new line gets announced through Narrator, NVDA, or JAWS using the UI Automation notification event, the same mechanism a lot of accessible apps use to speak things without stealing your focus. And because I know how fast a screen reader gets buried under a flood of interim, still-changing text, only the finished lines get announced. The “still deciding” partial text shows up in a small area labeled Now hearing, but it stays quiet.

If you actually want the compact, single-line presentation Windows’ version uses, you can have that too — F7 switches to a panel that shows one caption at a time in large text. The difference is what’s happening underneath it. The panel isn’t a separate, lesser mode; it’s a different view onto the same kept history, still announcing through the same notification event, still lets you step backward through what you missed. Switch back to the transcript and every line is still there. That’s really the core argument of the whole project: the compact view and the accessible view were never actually in tension. Keeping the text and making it reviewable costs nothing. Windows could do this today.

Under the hood it can caption your microphone or any audio playing on the PC — a specific app, or everything at once — and it gives you a choice of four on-device speech engines: an accurate Whisper model, a true word-by-word streaming recognizer, Windows’ own on-device NPU recognizer on Copilot+ PCs, and Windows’ built-in instant speech recognition. All four run locally. No cloud, no API key, nothing leaving the machine. On a Snapdragon X Copilot+ PC, the Whisper engine runs comfortably in real time on the CPU alone, which is honestly the point I most want to make with this project: accurate, private, accessible live captioning of anything happening on the PC is already achievable on this hardware, right now.

There’s one detail I’m particularly glad I got right. When you’re captioning system audio as a screen reader user, your screen reader’s own speech is part of that system audio too — so without doing anything about it, you’d end up with your captions transcribing screen reader speech too. The app can exclude your screen reader’s process from the captured audio, on by default, so what you get is the meeting or the video, not a feedback loop of your own screen reader.

None of this required exotic engineering. It’s WPF, System colors instead of hardcoded brushes so High Contrast just works, focus that starts and stays on the transcript because Tab doesn’t need to do anything when there’s only one real control, and every command reachable from an actual menu bar instead of a maze of toolbar buttons. It’s the same set of habits I’d want from any accessible Windows app, applied to a feature Microsoft already ships.

I want to be fair here: the recognizer Windows uses for Live Captions is genuinely good, and running it on the NPU on Copilot+ hardware is a real engineering achievement — my app’s Windows-on-device engine option actually calls into that same recognizer through Microsoft’s own API, because there was no reason to reinvent it. What’s missing isn’t the speech recognition. It’s the twenty additional lines of UI work to make the output of that recognition something you can actually read with a screen reader after the fact. That’s the gap this project is trying to make visible.

Right now this is a demonstration, not a polished product — it’s ARM64 Windows 11 only, built and tested on my own Copilot+ PC, and I’m calling it that on purpose. The code, the releases, and the full write-up of how it’s built are all on GitHub if you want to look under the hood or try it yourself: github.com/kellylford/LiveCaptionsWithAccessibility/releases. If you’re a screen reader user who deals with captions regularly, I’d genuinely like to hear whether the keyboard model and the announcements feel right to you, because that’s the part no amount of my own testing can fully substitute for.

One Comment