Thanks for looking. This is a small, opinionated app, and the bar for changes is mostly about evidence rather than style.
git config core.hooksPath .githooksOnce per clone. Git does not carry hooks across a clone — deliberately, since a
hook is code that would otherwise run on somebody's machine without their
having read it. .githooks/pre-commit refuses a commit carrying a secret, a
*.local.md note, or build output. It judges only what is staged, and it can be
overridden with --no-verify when you are sure.
Then, before every push:
swift build # must be clean, and warning-free
swift run HashNotchChecks # must pass, all of themBoth are the gate, and they are yours to run — nothing catches a broken push for you.
The same two commands run again on GitHub (.github/workflows/build.yml) for
every push to main and every pull request, and on demand from the
Actions tab. This repository is public, so GitHub's hosted runners cost nothing
and a second machine is worth having on every change — both to catch what one
machine agrees with itself about, and so that anybody reading the repository can
see whether what is on main builds. That job also builds the release
configuration, assembles and verifies the .app, and checks that the README's
"checks passing" badge still matches the number that actually ran.
Measure before you claim. Nearly every real bug in this app's history was a plausible assumption nobody checked:
- The storage readout was wrong because it asked macOS "how much could be freed" instead of "how much is free", and both sound like the same question.
- Artwork for a music app was believed reachable from an identifier macOS publishes beside the cover it withholds. It is not: the identifier goes stale across a track change, so what shipped was often the previous song's cover, shown with total confidence. The evidence was there before it shipped — the same identifier had already appeared for two different songs — and was read as "album-scoped" rather than as "unreliable". When a derived value repeats across two things that are not the same, treat the repetition as disconfirmation until proved otherwise.
- A "verified" claim rested on a build command that silently recompiled nothing.
If a change rests on how something behaves, show the measurement in the commit message. "Should work" is the phrase that costs the most time later.
The opening window lists what the app reads. The test for being on that list is not "does macOS ask permission for it" — a consent window that lists only what macOS happens to gate is a consent window written by macOS.
The test is whether the reading is about the machine or about you. Battery, memory, temperatures and connection speed are counters about the hardware. Which programs you run, what you are listening to, what is in your Downloads folder and how much you have spent on AI are not.
Anything in the second group must be named in three places before it ships:
ConsentReadings.all (the opening window), the Privacy page in Settings, and
the table in SECURITY.md. A check pins the first list, so adding a reading
means changing a check, which is what makes the omission impossible to miss.
This has been got wrong once: naming the programs that used the network needs no permission from macOS, shipped on by default, and was left off that window for a day while it still said the network figures were "nothing about you".
Every capability is a self-contained module. The core never imports a feature and never learns what one does.
- Create
Sources/Feature<Name>/with a type conforming toNotchFeature. - Add the target in
Package.swift, depending only onHashNotchKit. - Add one line to
Sources/HashNotch/FeatureManifest.swift.
Where it appears on a fresh install comes from FeatureRegistry.defaultOrder,
not from where you put it in the manifest — a feature that is not named there
goes to the end, which is what a new one should do.
Three things a feature must honour:
-
Off means off. Stopping must stop the reading, not just the drawing. A feature that is switched off opens no files, runs no subprocess, and can trigger no permission prompt.
-
Panel-only work belongs to the panel. If a readout is only visible inside the panel, sample it with
VisibleSamplerso it costs nothing while shut. -
A display choice must change what somebody sees. Anything in
displayOptionshas to produce a visibly different row, and a check holds the options and the style enum together so one cannot drift from the other. This has been got wrong: the compact pill beside the notch stopped being drawn when the island was redesigned, and nobody removed it — so features went on building a view that could not appear, and every display choice that only changed a pill sat in Settings doing nothing. A setting that does nothing is worse than one that does not exist, because it looks like it worked.
Sources/HashNotchChecks is the whole suite: a plain executable, because this
machine has Command Line Tools only and no XCTest.
Write checks against pure functions, and extract one if there isn't a suitable one — most of the bugs here were in a decision buried in a view body, and pulling that decision out is usually the fix as well as the test. A check that constructs a fixture and then asserts things about that same fixture is worse than none; it passes forever regardless of the code.
Checks must leave nothing behind. If yours needs a settings store, it gets a throwaway domain, and the sweep at the end of the run removes it.
- One logical change each.
- Say why, not just what. The reasoning is the part that is expensive to reconstruct later.
- If you fixed something, say what it did wrong and how you know it is fixed.
- No AI co-author trailers.
- Anything that reads the screen or synthesises clicks. Accessibility is used for exactly three key presses — play/pause, next, previous — and that narrowness is what makes the permission defensible.
- Clipboard history. It contradicts the whole position of the app.
- Ad blocking or skipping, for the same reason as the first point.
- Naming apps to support them. Ask the system what is playing or what has the microphone; a list is wrong for everything not on it. The one exception in the codebase is documented with its reason beside it.
This project is under the GNU General Public License v3 or later. By contributing you agree your work goes out under the same terms.
In plain terms: anyone may use, read, change and share this. If they distribute something built on it, that has to be free too, with its source available. That is deliberate — an app whose whole claim is that you can check what it does should not be forkable into something you cannot.