Shell Pilot 1.23.0
September 22, 2026 · Bug-fix and quality release
A full audit of the app, the purchase flow and the license backend. The two most serious bugs were outside the app: after checkout, customers could not get their license key or download the app.
Purchase and license backend
- The purchase page returned no license key. It asked the server for a key by email alone, which the server had (correctly) stopped allowing, so every buyer saw an error. The page now retrieves the key with the checkout session Stripe hands back, shows it with a copy button, and can be reloaded or bookmarked to see the same key again. If the page opens before Stripe’s webhook arrives, the server confirms the payment with Stripe directly and issues the key, so there is no race.
- The download button on that page was a 404. It pointed at
/Shell-Pilot.dmg, which did not exist. It now resolves the current release from the update feed, and /Shell-Pilot.dmg and /download redirect to the latest DMG.
- A customer with two purchases on one email could only activate the first key. Every active key on the email now works.
- Licenses are issued only for paid sessions; delayed payment methods get their key when the payment settles (
async_payment_succeeded).
- A partial refund no longer revokes the license. A full refund still does.
- Events for other products on the same Stripe account are acknowledged instead of rejected, so Stripe no longer retries them for days.
- License lookups answer the same way for an unknown email and a wrong key, so the endpoint cannot be used to find out who has bought the app. Database errors return a clean error instead of hanging the request.
In the app
- The toolbar is laid out properly. It was attached to the whole split view, so macOS put the first pinned shortcut in the sidebar’s narrow titlebar, wedged against the traffic lights, and pushed the other pinned shortcuts, the shortcut menu and the microphone into the
» overflow however wide the window was. It now belongs to the terminal column: the sidebar titlebar holds only the traffic lights and the sidebar toggle, and every toolbar item sits on the right.
- License tab in Settings. There was no way to enter a license key in the app. Settings ▸ License now activates, shows and deactivates it. The key is masked on screen and stored in the login keychain.
- Update notifications. Updates were only found by opening Settings ▸ Updates and pressing the button. Shell Pilot now checks the feed quietly a couple of times a day and shows “Update to X” at the bottom of the sidebar. The app menu gains Check for Updates….
- “Check for Updates” could stop responding. Dismissing the update popover by clicking outside it left the updater thinking the popover was still up, so the next check did nothing visible.
- Safer update install. The installer deleted the old app before copying the new one, so a failed copy left no app at all. It now copies alongside, swaps, and restores the old app if the swap fails.
- A shell that exits no longer leaves a dead session. Typing
exit or ⌃D closes the session, as Terminal.app does. A shell that crashes, is killed or exits with an error keeps its output on screen with a banner offering Restart Shell or Close Session; the sidebar marks it “Exited”, and Session ▸ Restart Shell works too. Previously the terminal looked alive and silently ignored every keystroke.
- ⌘W at an idle prompt no longer asks for confirmation. The check used “the shell is alive”, which is always true, so every close warned about an active command that did not exist. It now asks only when a job is actually running, and names it: ““npm” is still running in this session.”
- Closing the selected session selects its neighbour instead of jumping to the top of the list.
- New sessions reuse the lowest free “Terminal N” name instead of repeating one already in the list.
- Terminal ▸ Show/Hide Man Page (⇧⌘M) now toggles; it could open the page but never close it.
- The activity footer follows a restarted shell’s new process instead of reporting the old one.
- Session state writes are ordered, so an older snapshot can no longer overwrite a newer one on disk.
Tests
401 app tests pass across 30 suites, up from 390. New: SessionLifecycleTests (exit-status decoding, clean and failed exits, the no-loop guard for a shell that dies on launch, restart, neighbour selection, naming, key masking) and an idle-prompt close regression test. The backend gains its own test suite (npm test) covering purchase-page retrieval, idempotent minting, multiple licenses per email, refunds and unpaid sessions.
Known limits
- The app is still not notarized, so a first install shows a Gatekeeper prompt.
- Update notifications start working from this version on; anyone on 1.22.x still has to check once by hand.