Two weeks, twenty-three releases across three apps. The thread this time is narrower than last issue’s, and less comfortable. Last time the pattern was tools that stopped claiming more than they had checked. This time it is the checks themselves: in four unrelated projects, something that had been passing for months turned out to have been measuring nothing at all.
FinderGit
Open a commit like a folder (0.29.0). Reading a commit list, you want to know one small thing: which files did that one touch? Every commit now has a chevron, and opening it is exactly like opening a folder — the files, git’s status letter, the old name where something was renamed, and a read-only diff scoped to what that commit did to that file. Deleted files open too, because what a commit removed is often the part worth reading. Issues and pull requests open in place as well, with the whole conversation: the body a reviewer submitted with their verdict, and the notes pinned to individual lines. A pull request reviewed entirely inline — which is how most of them are — used to look as though nobody had said a word.
The same release fixed something I would rather tell you about than not. Discarding a file whose name contains *, ? or square brackets could revert a different file, because git reads those characters as a pattern: asking it to restore a[1].txt restored a1.txt instead, and discard has no undo. If you have never named a file that way, this never touched you. If you have, it did so without saying anything, which is the part that bothers me.
An empty cell is not a zero (0.30.0). GitLab and Bitbucket now fill in their own issue, pull-request and star counts with no account and no token, joining GitHub and Codeberg. Adding two forges was the easy half. The half that took the time was being honest about what each one cannot answer: Bitbucket retired its issue tracker, so its Issues cell is now empty while a GitHub row above it reads 0, and those are different facts that finally look different. The rule now runs through everything — a count that could not be obtained shows nothing rather than zero, whatever the reason.
The one that mattered most was forks. Only GitHub will say how far a fork trails its upstream; the other three answer with a 0 that means no permission to tell you, and FinderGit had been handing that zero straight through to a banner drawn as a green “Up to date with upstream”. A fork three hundred commits behind looked current.
Your tags and your releases, side by side (0.31.0 → 0.33.0). There is a quiet lie every Git tool tells: ask for your tags and you get the ones in your clone. A tag you cut this morning and never pushed looks exactly like one the whole team has had for a year. A new tab pairs every tag with the release cut from it and marks each row on both, local only or not fetched — and the buttons follow from that, so Push is drawn where there is something to push and nowhere else.
Two people then found the rest of the release. One opened it on a repository with 715 tags, selected three from four years ago, and found the bar carrying every action several hundred rows above, off the top of the panel — it is pinned now. The other asked why v1.0.2 sat at the bottom of a 207-tag list, under one six days older. The answer is that git has two kinds of tag: an annotated tag is a real object with its own date, while a lightweight tag is a name pointing at a commit, and git records nothing about when it was made — so the only date available is the commit’s. The sort was correct, and there was no order in which it read correctly. Rows now say which kind they are, and a commit subject is drawn behind its own mark rather than where a tag message would go: the two were written by different people at different times, and a commit subject printed as a tag message is a caption that lies.
Deleting a tag everywhere is one action now, and it runs the remote first, deliberately: if the remote refuses, your local tag is untouched and retrying costs nothing — the other order destroys the last reference naming that commit and only then discovers it could not reach origin.
You should not have to be watching (0.34.0). You start twenty tag deletions, look away, and never find out how it went. Every action now reports itself in a card in the corner carrying git’s own words — not a paraphrase, not “an error occurred” — and the card leaves on its own, because a stack you have to dismiss gets dismissed reflexively without being read. What stays is the bell: a failure lights the badge and keeps it lit, a success does not, so the badge means “something needs you” rather than “something happened”. Bulk runs show their progress, and a run you stop marks the tags it never reached as not attempted rather than guessing — the one you may have to check by hand is precisely the one a guess would hide.
A fetch that failed reporting itself in the corner with git’s own message, while the bell in the toolbar carries the unread badge
A position is not a name (0.35.0). Stash moved down to the bar beside Fetch, Pull and Push, where you actually reach for it — the old placement vanished at exactly the moment you wanted it, because pressing Stage All emptied the list whose header it lived in. But the fix worth reading about is underneath: every row in the stash list carried its position, and a position renumbers when something above it is dropped. A list read a moment earlier could point Pop at a different stash than the one under your cursor, and because the changes it applied looked perfectly plausible, nothing on screen would ever have told you. A stash is identified by its own commit now, resolved at the instant the action runs.
It took six rounds of review to land, five of them patching one window at a time while a sixth kept opening. What finally worked was changing the question: not “where is a check missing” but “which decision here is being made on data nobody validated”.
And the two that belong to this issue’s thread. A translation coverage report had been reading 100% while 189 user-facing strings had never entered the catalogue at all — it was right on its own terms, because a string that never became a key cannot be reported as missing. And a rule about how SwiftUI reports sizes, credited with fixing a clipped panel and then cited by every popover fix since, turned out not to be true: a measurement puts each shape into the real sizing path and reports the same height with the modifier and without it. The measurement is now in the repository, so next time the answer comes from running something rather than remembering a sentence.
Netfox
Is it my connection, or the internet? (0.18.0) A new Link panel reads the layer under the IP address: what the cable or the radio actually negotiated, the error counters, and how much of the delay is your own link against everything past your door. Every latency figure is a minimum rather than an average, and that was measured rather than assumed — across five bursts the minimum moved under 1 ms while the average moved over 100 ms, so an average would have blamed your link for the internet’s bad day.
The part I would point at, though, is what the panel refuses to say. A link nothing has crossed gets no clean bill of health: it has not been shown to be fine, it has been shown nothing. A radio that is switched off has no band and no channel, so those are left out rather than filled in with “Unknown” — an absence rendered as two facts is worse than a gap. And a router that ignores pings is reported as untimeable rather than broken, because plenty of them drop pings by policy.
The Netfox Overview with the new Link panel: the wired half, the radio and its headroom, the error counters, and the first-hop split
What a certificate says about itself (0.19.0, 0.19.1). Every home network has a few: the NAS admin page, the printer, the router, a Home Assistant box. They all speak TLS, and almost nobody ever looks — until a browser refuses to open one and you are guessing whether the device is broken, misconfigured, or just doing what home devices do. Netfox now reads the certificate and says plainly whether it is expired, expiring within three weeks, self-signed, or not yet valid, which on a home network almost always means the device’s clock is wrong rather than the certificate. The next release added how the two of you are actually talking — the negotiated version and cipher, flagging an obsolete version or a key exchange with no forward secrecy, and honest that this is one server’s answer to one negotiation with your Mac, not everything it could do.
The server you forgot you started (0.20.0). A test run finishes, the terminal window closes, and something it started keeps holding port 3000. Netfox scans your network, and a server bound only to loopback is not on your network — so 0.20.0 looks in the other direction. The finding is not that a port is open: it is that the process was left behind. It waits fifteen minutes from the moment of orphaning, and it only reports listeners every process of which is yours, because one you cannot act on is not a finding, it is a nag. The naive version of that rule fired on three services out of eight on a test machine, all of them Apple’s own.
The one port worth asking twice about (0.21.0). There is one port on a home network more likely than any other to be running something it should not, and it is the boring one: 445, file sharing. What matters is not that it is open but which version the other end speaks — and a device that speaks only SMB 1 does not reply to a modern request at all. It just says nothing. Ask once and take silence for an answer, and the device most worth warning you about is the one reported as fine. So it asks twice, and where it cannot settle the second question it says nothing rather than telling you SMB 1 is off: a reassuring sentence about a question nobody managed to ask is worse than no sentence.
The same release found the landing screen burning 9.6% of a processor against 0.6% on any other screen — beside a comment in the code claiming “well under 1%”.
A window that fits where you put it (0.22.0). If you have ever dragged Netfox to one side of the screen to watch it while you worked, you know it refused to go narrower than 960 points and its right edge simply left the display. It now goes down to 500×340 and changes shape rather than squeezing: not a smaller version of the page, a different one, with the figures that matter all on screen at once. Fixing it also turned up a click at that size that selected nothing — while 959 existing tests passed the entire time. There is now one that clicks a row at that size and fails if nothing opens.
And the one I would rather have not shipped: every build up to 0.18.0 carried a setting that belongs on a development machine, the one that lets a debugger attach to a running program and read whatever it is holding. Nobody added it — it arrives automatically on a development build, and the step that prepares the release faithfully preserved everything the build had produced. That is fixed in 0.18.1.
Netfox in its widget shape: four tiles filling a small window — risk posture at 3 medium and 29 low, 23 devices of 32 on the network, the public IP on a direct connection, and 3 alerts — with every figure on screen at once
octoscope
The version it prints is the version you have (0.31.1). Every octoscope release is built with a linker flag whose entire job is to stamp the tag into the binary. It never did anything: the version was declared as a Go constant, and the linker cannot patch a constant. No warning was ever emitted, so nobody knew — and here is the honest half, measured rather than assumed: across all 42 releases the hand-edited line had matched its tag every single time. What was missing was never correctness. It is that nothing checked. There is a test now, and it was written carefully, because a check that can silently measure nothing is precisely the failure this release is about and it would be a poor joke for it to be one itself.
Your lockfile, read line by line (0.33.0). The lockfile is the one part of a pull request nobody reads that way. The scan now does it for you, flagging dependencies that newly run install scripts and versions republished with different bytes. Worth knowing how rare that is: across the last twenty revisions of the lockfiles in axios, npm/cli and undici — fifty-seven revisions in all — the files churned the entire time, the subset that runs code at install moved twice, and the same version was republished zero times. It is npm only, and the report says so on every repository it could not compare, because silence would look exactly like nothing here runs code at install, and that is the one thing it must never imply.
It stops blaming you for your own refactor (0.34.0). You reorganise a monorepo and the scan lights up. Nothing started; you moved a folder. Packages are now keyed by their declared name rather than their path, and the fix was measured before it was chosen: across 115 workspace entries in seven npm-workspace monorepos, 85 carried a name, and all 85 differed from the directory they sat in. A baseline recorded in the old format now reads as not comparable instead of being diffed against a different naming scheme.
And a green release that did not mean what it looked like (0.34.1 → 0.34.3). If you installed 0.34.0 on macOS and the command did nothing at all, that was real and it was ours: a Homebrew cask’s download is quarantined, unlike a formula’s, and Gatekeeper killed the ad-hoc-signed binary at launch. The stopgap stripped the quarantine flag, the next release found that the hook it used had been deprecated in a way that actually broke installs, and 0.34.3 closed it properly — Developer ID signing and Apple notarization, which also covers what Homebrew never could: a .tar.gz downloaded straight from the releases page.
The measured finding along the way is the one I would keep: signing without notarizing changes nothing at all. The binary is still killed, and spctl still reports it rejected. There is now a CI job that downloads the published binary, checks its notarization, re-quarantines it and actually runs it — with a negative control, an ad-hoc-resigned binary that must fail, so the check cannot pass silently on a runner that is not enforcing. brew install exiting 0 is exactly what the broken release did.
Also in the fortnight: octoscope now installs four ways rather than two — as a gh extension, a roughly 7 MB container image, a standalone platform binary, or through Homebrew — and CI builds and runs the Windows binary natively for the first time, having shipped one since v0.2.0 on the strength of cross-compilation alone.
octoscope 0.34.3 on the Overview tab: the profile card, the social and activity counters, the languages bar, the top repositories by stars and the network row
https://gfazioli.github.io/octoscope
Mantine extensions
A window gesture you can compose (mantine-window 3.2.0). Window now emits onDragStart, onDragEnd, onResizeStart and onResizeEnd, completing a set that previously had only the two change callbacks — and those fire on every frame. The edges are what make a gesture composable: persist a layout once when it ends instead of debouncing by hand, pause an expensive child while the user drags, record one undo entry per gesture rather than one per pixel.
Two guarantees came out of the implementation rather than the API sketch. Every start is paired with exactly one end — the global listener ends both gestures at once, so a naive implementation would have emitted an unpaired onDragEnd during a plain resize — and no start is emitted without a real gesture, so a press on a resize handle, an interactive child or a data-no-window-drag region never opens one that would not close. Verified with real pointer events in a browser rather than only in jsdom: across one drag and one resize the continuous callbacks fired 20 times and the lifecycle ones twice.
Beyond that it was a fortnight of alignment: the whole family moved to Mantine 9.6.0 at the start of the period and to 9.6.1 at the end of it, which is two publishes for every component and nothing you have to do.
mantine-window · mantine-extensions.vercel.app
The Raycast extensions
macOS Tweaks went from 108 tweaks to 129, with a new Windows & Stage Manager category covering Stage Manager, the Sonoma click-the-wallpaper behaviour and Sequoia’s window tiling, plus a modifier key for each hot corner — the corners were already there, the modifiers that stop them firing by accident were not. Four AI tools make a 129-entry catalogue findable, because nobody looking for “let me see hidden files” knows to search for com.apple.finder AppleShowAllFiles; the two that write both ask for confirmation showing the exact defaults command and which process will restart.
Every domain and key was verified against a live system, and each declared default was diffed against the real current value — which caught one that had been wrong: double-clicking a title bar has defaulted to Fill since macOS 15, not the older Maximize. In the same spirit, the two smart-quote tweaks had been showing as modified when untouched, because defaults read prints non-ASCII characters as escape sequences and the comparison never matched.
App Updates got its Sparkle scan under control: reading the Info.plist of every app in /Applications used to fan out one process per installed app. Measured on a 32-app machine, that was 31 concurrent processes before and 8 after, with an identical scan result.
https://www.raycast.com/Undolog
And on the site
gfazioli.github.io now opens with the three most recent releases across every project and a count of the last thirty days, so the front page answers “what has been happening” before you go looking. The macOS app cards are backed by their public website repositories, which is why they finally carry real descriptions and dates.
The React component libraries — flip, tilt, toggle and the guru meditation — shipped nothing this fortnight; they are stable and the last release of each still stands.
Undolog — open source studio. Every project in one place: gfazioli.github.io · this newsletter lives at undolog.com
FinderGit — a Git-aware file browser for macOS · findergit.app
Netfox — a network monitor for macOS · netfox.app
octoscope — a terminal dashboard for GitHub · gfazioli.github.io/octoscope
Mantine extensions — 26 components on npm · mantine-extensions.vercel.app
Raycast extensions — for the Raycast launcher · raycast.com/Undolog






