Releases: nurkamol/replay-swift
Release list
Replay 0.9.10
Install
Homebrew is the recommended way, and the only one with nothing to click
through — the tap clears the quarantine flag on install and on every upgrade:
brew trust --tap nurkamol/tap
brew install --cask nurkamol/tap/replay-appbrew trust is required by Homebrew 6 before it will load any third-party tap.
Skipping it looks like a broken tap rather than a consent prompt.
Also in the tap: nurkamol/tap/replay (the command-line reader) and
nurkamol/tap/replay-app-source, which compiles on your Mac and needs a full Xcode.
Or the zip
Replay-0.9.10.zip — unzip it and drag Replay.app to Applications.
macOS will refuse to open this one the first time. Replay has no Apple Developer
ID yet, so a downloaded copy shows "Apple could not verify Replay is free of
malware", offering only Move to Trash or Cancel. That message is about a
missing certificate, not about anything found in the app.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
A signed, notarised disk image — no warnings on any route — appears here the day
there is a certificate. The workflow that builds it is already written.
Added
-
The scope controls read in your language. "Today / This Week / This Month" on Apps,
"All time / Week / Month" on Search, "Last 7 Days / Last 30 Days" on the Timeline, every
page subtitle beneath them, and the section headings on Apps. The enums that define them
are compared against the reference by the parity suite, so they still return English and
the translation happens at the view layer — the same seamSessionTitle.englishuses. -
The rituals on Story read in your language — the first of the runtime-assembled
surfaces to be converted. "Morning tends to lead with" was built by pasting the day part
onto an English tail, which is untranslatable into any language that orders it differently;
it is one format string with a positional argument now, and the part goes through the same
spelled-out switch the day's story uses. -
Story, Chapters, Autobiography, the Museum and the Legacy view read in your language.
NarrativeCopyholds the prose those five surfaces are made of, and every one of its
sentences was already a key and already translated — the views just never looked any of
them up.Text(NarrativeCopy.storyTitle)renders the English constant whatever language
the app is in, so five surfaces stayed English whilestatusreported Uzbek complete and
the Uzbek sat in the catalogue unused. Thirty-three render sites, now looked up. -
The command palette reads in your language. All of it: the commands, their
descriptions, and the group headings above them. It was renderingText(item.title)with
no lookup at all, so ⌘K opened an English window in a translated app — and matching was
against English too, which is why searching it in Uzbek found nothing. -
Counted nouns follow the language's own plural rules. "3 sessions" was chosen by
n == 1, which is English's rule rather than a rule. Russian has four forms — 21 takes
the same one as 1, and 11 takes neither — and Arabic has six, so every counted noun in
either would have been wrong however carefully the two English forms were translated. Each
is now a.stringsdictentry with one form per category, resolved by Foundation against
the locale's CLDR rules.- The categories come from ICU rather than a hand-kept table:
translate.mjsasks
Intl.PluralRules, the same CLDR data Foundation uses, so Arabic scaffolds 36 plural
rows, Russian 24 and Japanese 6, and nothing here can fall behind a CLDR release. - English ships no
.stringsdict— its rule isn == 1— so the two-form path stays as
the fallback for any noun a language has not finished, and the contract is untouched.
- The categories come from ICU rather than a hand-kept table:
Fixed
-
Eleven counted nouns were built by appending "s" in code.
count == 1 ? "session" : "sessions", spelled out across eight files, bypassingLoc.countentirely — so they were
untranslated and carried the English-only rule this release exists to remove. All now go
throughLoc.count. -
Five counted nouns were never in the catalogue at all.
%@ day,%@ visit,
%@ visits,%@ switchand%@ switchesreachLocthroughLoc.countrather than
Loc.t, which the key scanner does not read — so they were missing fromkeys.txt, from
every CSV, and rendered in English whilestatusreported Uzbek at 100%. The count could
not see them, which is the third distinct way scanning source for strings has been wrong
here. Now collected, and asserted by a test. -
statusno longer calls the new plural rows orphaned. Twelve rows per language
reported as dead would be an invitation to delete them — and a bulk orphan sweep has
already destroyed correct translations in this project once.
Replay 0.9.9
Install
Homebrew is the recommended way, and the only one with nothing to click
through — the tap clears the quarantine flag on install and on every upgrade:
brew trust --tap nurkamol/tap
brew install --cask nurkamol/tap/replay-appbrew trust is required by Homebrew 6 before it will load any third-party tap.
Skipping it looks like a broken tap rather than a consent prompt.
Also in the tap: nurkamol/tap/replay (the command-line reader) and
nurkamol/tap/replay-app-source, which compiles on your Mac and needs a full Xcode.
Or the zip
Replay-0.9.9.zip — unzip it and drag Replay.app to Applications.
macOS will refuse to open this one the first time. Replay has no Apple Developer
ID yet, so a downloaded copy shows "Apple could not verify Replay is free of
malware", offering only Move to Trash or Cancel. That message is about a
missing certificate, not about anything found in the app.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
A signed, notarised disk image — no warnings on any route — appears here the day
there is a certificate. The workflow that builds it is already written.
Fixed
-
brew upgradeno longer swaps the bundle out from under a running Replay. The cask had
nouninstall quit:, so an upgrade replaced the application directory while the process was
still in it — holding an open SQLite handle it writes to every few seconds. A launcher
survives that; a recorder can lose the tail of the day, and the record is the product, with
no cloud copy to restore from. Homebrew now quits Replay first and reopens it afterwards. -
The in-app updater no longer overwrites a Homebrew-managed copy. It recognised a
formula build by its/Cellar/path, but a cask moves the app into/Applications,
where it is indistinguishable from a copy dragged there by hand. So from the day 0.9.8
shipped a cask, the updater would replace it — leavingbrewdescribing a version it no
longer had, and the nextbrew upgradeputting the older one back. It now checks
Homebrew's own receipt under both prefixes.
Changed
-
Every build carries the same signature, so an update stops asking again. Replay was
signed ad-hoc, and an ad-hoc signature has no identity: its designated requirement is the
code hash, so it changed with every build. Homebrew 6 compares that requirement across an
upgrade and carries a reader's Gatekeeper approval forward only when it matches — otherwise
printing "the signer changed so macOS will prompt at next launch". That branch was taken
every time, so each update meant another trip through System Settings, forever rather
than once. A stable self-signed certificate fixes it, and fixes the same churn in the
in-app updater. It is not a Developer ID and does not make the first launch any quieter.
docs/SIGNING.md has the whole argument and the commands. -
The tap clears the quarantine flag, and says so. Installing the cask no longer ends at
a dialog offering only Move to Trash. What that trades is stated in the cask's caveats, in
the README and on the site rather than buried: macOS did not check the app, Homebrew checked
the download against the cask's SHA-256 instead, and that is a check published by the same
project that publishes the app. The source build remains the route for anyone who would
rather Apple's check applied. -
brew trust --tap nurkamol/tapis now the first line of every install instruction.
Homebrew 6 refuses to load a third-party tap until it is trusted, and the refusal reads like
a broken tap rather than a consent prompt — which is what several confusing install errors
turned out to be. -
The cask's version and checksum are bumped by the release workflow, from the artefact it
just built. They used to be copied between two repositories by hand, and 0.9.8 duly shipped
for a while carrying 0.9.7's checksum against a 0.9.8 URL: every install failed, and failed
in a way that reads as a corrupted download rather than a mistake in the tap.
Added
-
Memories reads in your language. The surface that exists to be read was the one still in
English: its frame translated and every card inside it said "Your longest focus", "A late
night", "Where it began". All seven now follow the chosen language, along with the
on-this-day names, the heatmap's months and weekday initials, and the dates throughout.- Word order moves with it. In Uzbek the application's name leads — "Firefox ilovasida 2h
13m…" — which is what positional arguments are for and what glueing clauses together
could never produce. - The English is untouched: it is the reference's own wording, and a
factspayload on each
moment is what lets the sentence be assembled a second time rather than replaced.
- Word order moves with it. In Uzbek the application's name leads — "Firefox ilovasida 2h
-
Update messages are translated too. The eleven sentences that appear when a check or an
install fails were English in every language — which is the worst moment to meet a language
you did not choose. -
A day you reopen reads in your language. Six English strings were sitting on that
surface — the time-of-day word in the story, the headline date, the toolbar subtitle, the
reflection prompt, and both Export menus — and nobody had ever seen them, because the
screenshot harness drove 21 surfaces and a past day was not one of them. It drives 25 now:
a reopened day, the welcome screen's two pages, and What's New.- One of the six was self-inflicted, and is the useful half. The day parts are looked up
through a variable, which the key scanner cannot see, so their translations looked
orphaned and a tidy-up deleted them.docs/TRANSLATING.mdnow carries the rule: never
sweep orphans in bulk.
- One of the six was self-inflicted, and is the useful half. The day parts are looked up
-
Every SF Symbol is checked against the oldest macOS this app supports. A symbol newer
than the deployment target draws nothing — no crash, no warning, a gap where an icon
should be, on a Mac the developer does not own.tools/symbol-audit.mjsreads Apple's own
availability manifest and fails the build instead. All 52 in use are safe on macOS 14; that
was true by luck until now.
Replay 0.9.8
Download
Replay-0.9.8.zip — unzip it and drag Replay.app to Applications.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
Added
-
Reinstall, in Settings ▸ About. Downloads the published build of the version you are
already on and replaces this copy.- A version number stops being the whole truth when a release is rebuilt under it. Two
builds then share a number, "check for updates" correctly answers "you are up to date", and
a copy of the earlier one has no way forward. That happened to this very release, twice. - Also the repair for a bundle that is merely damaged — right version, wrong contents.
- Never automatic and never suggested. The same download, checksum and swap as an ordinary
update, with only the "is it newer?" test skipped. Your record is untouched. - It says so afterwards. A reinstall comes back at the version it left, so the note that
normally follows an update — decided by comparing versions — had nothing to compare and
said nothing at all. Pressing a button and getting silence is the one outcome that leaves
you no better off, so the banner now reads Reinstalled Replay 0.9.8.
- A version number stops being the whole truth when a release is rebuilt under it. Two
-
The permissions Replay does not use now have rows, in Settings ▸ Privacy and on the welcome
screen. Each says where it stands and what it is for; Reset takes back anything granted
earlier, which was previously impossible from inside the app.- Accessibility's button asks macOS to prompt, which puts Replay in the list — so all
that is left is the switch. The two buttons here before opened System Settings and stopped,
leaving somebody to press+and find Replay in/Applicationsby hand. - App Management has no button of that kind, because macOS offers none. No app can read
or request it, so the row says so rather than showing a tick that would be a guess. - Every row offers a way in and, where there is something to undo, a way out — so a
permission can be granted, taken back and granted again without leaving Settings. App
Management always offers Reset, precisely because macOS will not say whether it is on:
hiding the button until a permission "looks" granted would have made the one thing nobody
can check also the one thing nobody could undo. - Nothing here is required and the rows never imply otherwise: no red, no warnings, no
"action needed". Every row off is the state Replay is built for. - No password is ever asked for. macOS collects authorisation in its own window; an app that
put up a password field to approve a toggle would be collecting a credential it has no
business seeing, and it would not work anyway.
- Accessibility's button asks macOS to prompt, which puts Replay in the list — so all
-
Replay runs on macOS 14 Sonoma. It required macOS 26 before, which is a version most
Macs are not on. Exactly two calls in the interface needed something newer — the system's
glass material (26) and a colour blend (15) — found by building the app against each floor
in turn rather than by reading the code. Both are guarded, so three OS generations gained
the app and nothing about it changed on a current Mac.- Below macOS 26 the Surfaces setting offers Solid and Frosted, and not Glass. A
setting that silently did nothing would be worse than one that is not there. - A
Glasspreference carried over from a newer Mac — in a backup, or across an account —
draws as Frosted rather than as nothing. - macOS 13 is not reachable the same way: the models are built on Observation, which begins
at 14. That is a rewrite rather than a guard, and it is not planned.
- Below macOS 26 the Surfaces setting offers Solid and Frosted, and not Glass. A
-
A report on a schedule. Settings ▸ Data writes a report of the period that has just
finished — yesterday, or the week just gone — into a folder you choose, as Markdown or
HTML, keeping the eight most recent and removing only its own. The sibling of the scheduled
backup and deliberately the same shape, with one difference worth stating: a backup is
insurance you hope never to open, and this is the one file in the app meant to be read.- Past tense on purpose. A report of a day at two in the afternoon is wrong by six.
- A period with nothing in it writes no file. A folder that fills with "nothing happened"
is a folder somebody stops opening. - Not PDF: that is one page with a pointer to HTML, which is right for a document somebody
asked for and wrong for a file that arrives on its own and might cover a busy week.
Changed
- The parts of the app you read most are now translated. A session is called "Late night
in Terminal" and a gap "8m not recorded" — sentences the app assembles at runtime, which had
no whole string for a translator to be given and so stayed English in every language. Each
is now a format string whose pieces a translation can reorder, which matters: in Uzbek the
application's name comes first and the time of day after it. Session titles, the gaps
between them, the day's headline figures, the sidebar sections, the resume card, the
reflection prompt and the relative day and time labels all follow the chosen language.
Uzbek is complete at 519 strings.- Still English everywhere: Story, the autobiography, Memories, the morning briefing,
Collections and Projects. Those are the same kind of work and are listed as undone in
docs/TRANSLATING.mdrather than left to be discovered.
- Still English everywhere: Story, the autobiography, Memories, the morning briefing,
Fixed
-
The schedules section reported one schedule as if it were both. Its footer was the
backup's, so "Nothing has been written yet" sat directly under a line saying a report had
just been written. -
A VoiceOver label was built from a malformed format string. One argument was handed to
a two-placeholder format on the Chapters screen, mixing positional and non-positional
specifiers — both undefined behaviour, and the label is read aloud.
Replay 0.9.7
Download
Replay-0.9.7.zip — unzip it and drag Replay.app to Applications.
Running 0.9.3 or newer already? Settings ▸ About ▸ Check for updates will offer this one and
install it for you, checking it against the SHA-256 published beside the zip. A Homebrew copy
is left to brew upgrade, as it should be.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
Added
- Replay can be read in another language, and there is now a way to translate it.
Settings ▸ General ▸ Language, listing only the languages this build actually carries —
and the switch takes effect immediately rather than at the next launch. - A translation kit, because the two things that translate text — a person and a service —
both want a table, and neither wants a.stringsfile.node tools/translate.mjs new fr
writestranslations/fr.csv, a service or a translator fills the second column,build fr
turns it into the catalogue that ships. Re-runningnewkeeps what is already translated
and adds rows for anything the app has gained since.statussays how far each language is.
Instructions, including what a translator needs to know about%@, are in
docs/TRANSLATING.md. - A language ships only when it is complete.
buildrefuses below 100% unless--partial
is passed: an.lprojtells macOS "Replay speaks this", and a Mac set to it would then show
translated and English lines mixed with no way to tell which is which. - Uzbek, complete — all 423 strings, so it ships without
--partial. It is machine-made
and wants a native reader: corrections belong intranslations/uz.csv, and rebuilding is one
command. Twelve more languages are scaffolded and empty, ready for a service or a person.
Fixed
- Swift unicode escapes were being carried into the table as text. Three of the longest
strings in the app contain\u{2019}in the source; the extractor took them literally, so
those keys could never have matched the string the app actually looks up. Found by
translating them — nothing else would have. - The sidebar was never translatable, and no audit could have said so: its names arrive as
Navigation.Surface.rawValue, andtools/strings-audit.mjslooks for literals. The most
visible column in the app was English in every language while the audit read clean. It goes
throughLocnow, and the extractor knows about the helpers that take copy as an argument.
Replay 0.9.6
Download
Replay-0.9.6.zip — unzip it and drag Replay.app to Applications.
Running 0.9.3 or newer already? Settings ▸ About ▸ Check for updates will offer this one and
install it for you, checking it against the SHA-256 published beside the zip. A Homebrew copy
is left to brew upgrade, as it should be.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
Added
-
A travelling border while an update installs. The one moving border in the app, on the
one stretch where Replay is working on something you should not interrupt and cannot be told
how long it will take. Built here rather than taken from a package — CLAUDE.md forbids
dependencies, and the effect is thirty lines. Off entirely under Reduce Motion. -
Pausing can end by itself. "Pause for…" in the menu bar offers 15 minutes, an hour, or
until tomorrow, and recording comes back on its own — including after a quit or a night
asleep, because a timed pause is a promise about a span of time rather than about this
process. The menu bar and Settings ▸ Privacy both say when it ends. Pausing with no end is
still there and still indefinite; the problem this fixes is not pausing, it is forgetting. -
After an update, Replay says what you got. The updater replaces the bundle and restarts,
so "after the update" is really the next launch — and until now that launch said nothing at
all. It now compares the version it is running against the one that ran last time and, if it
is newer, answers in one of two ways.- You pressed Update: the What's New window opens by itself. You asked a question thirty
seconds ago, the app disappeared and came back, and this is the answer. - It arrived some other way —
brew upgrade, a bundle dragged into place — a banner in
the same slot the update offer uses: "Updated to Replay 0.9.6", a What's New button and
a dismiss ✕. Nobody is waiting on an answer, so nothing takes the screen. SPEC §8's "never
interrupt" is exactly this distinction. - A downgrade says nothing: putting an older copy back is deliberate, and announcing the
release somebody just escaped would be the app arguing with them. - The note is true for one launch and remembers nothing. Read or dismissed, it is gone.
- You pressed Update: the What's New window opens by itself. You asked a question thirty
Fixed
- Replay Story moved in lurches. Each stop ran three movements where it should run one: a
flight that started at full speed, a lean toward the next stop, and another flight that
started at full speed. It arrived, stopped, crept, stopped and jerked away — two stops and a
lurch per hop. The lean was this port's own idea and has gone; the dwell is still, and a
hop now eases at both ends, because it begins from a camera at rest and nobody asked for it
in the instant it happens. The reference's timings are untouched and still contract-checked. - Compacting no longer freezes the window.
VACUUMran on the app's own connection, on
the main actor, wrapped indefer { busy = false }— so "Compacting…" could never draw a
frame and the window simply stopped answering for the length of a whole-file rewrite. It runs
on its own connection now, off the main actor, with recording paused around it so no tracker
write meets an exclusive lock. Importing a backup is still synchronous; it is seconds rather
than tens of seconds, and it is on the backlog. - A pinned ambient screen switched auto-start off. The idle watch refused to raise anything
while any display was open, so ambient mode left running on a second monitor stopped the
screensaver ever arriving on the screen you work on. The guard is about the screen now: two
displays on one screen is what it was for, and two displays on two screens is the setup. - The timeline beside the Canvas now travels with the story. It stayed on whatever was
selected when the story began, so a story was a camera moving through one memory beside a
list describing another — the Glaze version keeps the two in step, and now so does this.
Replay 0.9.5
Download
Replay-0.9.5.zip — unzip it and drag Replay.app to Applications.
Running 0.9.3 or newer already? Settings ▸ About ▸ Check for updates will offer this one and
install it for you, checking it against the SHA-256 published beside the zip. A Homebrew copy
is left to brew upgrade, as it should be.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
The update check spending its own attempts badly, and one claim measured rather than
believed.
Fixed
- A refused check counted as the day's check. The "last checked" stamp was written the
moment a reply arrived, whatever it said — so a rate-limited attempt recorded a check and
the next one was 24 hours away, while an attempt that failed because the Mac was offline
threw before the stamp and retried freely. Backwards in both directions: the failure that
clears within the hour burned the day, and the one that might last all day did not. Only a
200or a304counts now.
Added
- It says when the limit clears. GitHub names the moment in
x-ratelimit-reset; the
message reads "It clears at 6:05 AM" rather than "try again later", and the daily check will
not ask again before then — an attempt inside a window GitHub has already closed can only be
refused, and spends one of the sixty an hour that were the problem. - The check asks a smaller question. It sends
If-None-Matchwith the tag from the last
answer, so a day when nothing has changed comes back as an empty304rather than a release
it already had. The release it did have is kept between launches, so that empty reply is
still a complete answer.
Measured, and it went the other way
A 304 does not save rate limit on this endpoint. GitHub documents conditional requests
as exempt, and that is written for authenticated ones — on the unauthenticated per-IP cap
this app uses, three requests decremented the counter three times whether they answered 304
or 200. The table is in docs/FINDINGS.md. If-None-Match stays for the
two reasons that survived measurement, bytes and clarity, and this entry exists because the
first draft of it claimed the saving.
Replay 0.9.4
Download
Replay-0.9.4.zip — unzip it and drag Replay.app to Applications.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
Added
- A screen to show them on. Settings ▸ Display ▸ Show on names which display the
screensaver and ambient mode take. Replay follows the keyboard unless told otherwise, and a
named screen that is unplugged falls back to the keyboard's without forgetting the choice. - Ambient mode can be left open while you work. On a screen other than the one Replay's
window is on, it stops taking the keyboard and stops closing when you type — the second
monitor the mode was always for. It never applies on the screen you are working in, because
a display that covers your work and takes no keys is a trap rather than a feature. - Quiet hours. The idle drift can be kept to a span of the day. Spans that run through
midnight work, which is the case the obvious implementation gets wrong. - Backups nobody has to remember. Settings ▸ Data writes the same full backup every day or
every week into a folder you choose, atomically, keeping the eight most recent and removing
only its own older ones. Off until a folder and a schedule are chosen; nothing leaves the Mac. - A note and a bookmark on the stretch you are in, from the menu bar — or ⇧⌘N from
anywhere. The moment worth marking is the one you are in, and it passes while you are
finding the session in a window. - Ambient mode can drift in on its own. The idle delay used to have one outcome — the
screensaver — because that is the only thing the reference's timer raises
(openAmbient("screensaver"), hard-coded). Settings ▸ Display now opens with an Auto-start
section: which of the two displays the delay raises, then the delay itself. The default is
the screensaver, so an existing install behaves exactly as it did. - What starts on its own leaves on its own. An ambient screen raised by the timer
dismisses on any key, click or movement. One opened by hand still stays, which is the whole
point of a display left on a second monitor — upstream says so in as many words, and this is
the case that comment does not cover.
Fixed
- "Theme: Light" left half the app dark. The appearance was applied by the main window and
the menu bar panel and by nothing else, so Settings, What's New and the note panel followed
the system instead of the setting. It is applied once now, where every window picks it up.
Found by looking at the light-mode screenshots; every check in the suite passed throughout. - VoiceOver read an unlabelled image before every application name — three of them per
session on the Timeline. Application icons are decoration beside a name that is always
there, and are marked as such. - "Exit on mouse movement" had nothing to listen to. Neither full-screen window set
acceptsMouseMovedEvents, and AppKit does not deliver.mouseMovedto a window that has not
asked for it — so the monitor watching for it could never fire. The switch is off by default,
which is why a setting that did nothing survived this long.
Changed
- The line under "Auto-start when idle" now says which display it will raise. The screensaver's
wording is still the reference's, read from the contract rather than restated.
Replay 0.9.3
Download
Replay-0.9.3.zip — unzip it and drag Replay.app to Applications.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
Added
- Updates install themselves. The banner's button was a link to a release page, which
left somebody to download a zip and drag a bundle over the one they were running. It now
downloads the zip, fetches the SHA-256 the release publishes beside it, hashes the download
and compares, extracts withditto, checks the bundle is signed and is this application
and is the version that was advertised, then replaces itself and restarts. - What the trust rests on, said out loud: HTTPS to a repository named in the source, plus
a checksum published in the same release — the Homebrew-formula model. It proves the bytes
are the ones that release carries, not that the release is trustworthy. Anyone who can
publish to the repository can publish an update. A Developer ID would add authorship and is
still the thing worth buying. - It refuses more often than it installs, which is the design rather than a limitation. A
Homebrew copy is left tobrew upgrade, since replacing the bundle would leavebrew
believing it has a version it no longer has. A copy running from macOS's read-only
translocation mount — what an app still sitting in Downloads gets — has nothing to replace.
A read-only location refuses rather than half-installing.
Fixed
- Rate limiting says something useful. Testing exhausted GitHub's sixty-per-hour
unauthenticated cap, and "GitHub replied 403" tells nobody anything they can act on.
Replay 0.9.2
Download
Replay-0.9.2.zip — unzip it and drag Replay.app to Applications.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
0.9.1 shipped an app that would not open. This is that fixed, and the check that should
have caught it.
Fixed
- The app died on launch if it could not find its strings catalogue.
Bundle.modulecalls
fatalErrorwhen the bundle is missing, andLocreached for it on a path that runs during
startup. Every other path inLocdegrades to English by design — a missing.lproj, an
unreadable table, a key nobody has translated — and missing the whole catalogue should
degrade the same way rather than take the app down. It resolves the bundle by hand now and
gives up quietly. - How it got through, because that is the useful part. The resource bundle has two shapes:
SwiftPM's native build produces a flat one withInfo.plistat the root, Xcode's produces
a deep one withContents/Resources/. Every local build was deep and worked; the release
was built flat and did not. No test sees either — they run where the bundle is always found.
951 contract checks, 115 behaviour cases and four audits all passed on a build nobody could
open. - The release workflow now runs the app it is about to publish and fails if it exits on
its own. The only way to catch this class of failure is to launch the artefact.
Replay 0.9.1
This release is withdrawn. Its download would not open.
Bundle.moduletraps when it cannot find its resource bundle, and the release build
shaped that bundle differently from every local build, so the app died on launch. The zip
has been removed. Use v0.9.2, which fixes it and adds a check that runs the app before
publishing it.
Download
Replay-0.9.1.zip — unzip it and drag Replay.app to Applications.
macOS will refuse to open it the first time. Here is why, and what to do.
Replay has no Apple Developer ID yet, so macOS marks it as unverified and shows
"Apple could not verify Replay is free of malware" — offering only Move to
Trash or Cancel. That message is about a missing certificate, not about
anything found in the app. Every unsigned app gets it.
To open it:
- Try to open Replay once and dismiss the warning.
- Go to System Settings ▸ Privacy & Security, scroll down, click Open Anyway.
- Confirm. It opens normally from then on.
Or one line in a terminal, which does the same thing:
xattr -dr com.apple.quarantine /Applications/Replay.appRight-click ▸ Open does not work. That bypass existed for years and Apple
removed it in macOS 15. It is still the advice in most projects' READMEs.
Or skip all of that
Homebrew compiles it on your machine, so it is never downloaded, never quarantined,
and opens with no warning at all:
brew tap nurkamol/tap
brew install nurkamol/tap/replay-app # the application
brew install nurkamol/tap/replay # the command-line readerA signed, notarised disk image — no warnings, nothing to click through — appears
here the day there is a certificate. The workflow that builds it is already written.
Everything below happened after 0.9.0 was tagged the same day. Two of them are the reason to
take this build rather than that one: a data-loss bug, and the last functional gap against
the reference closing.
Added
- PDF export, which the ledger had recorded as a decision rather than an omission after
three WebKit routes died. The fourth route is not WebKit:ImageRendererreturns a
CGContext, a PDF context is aCGContext, and a SwiftUI view draws straight into a page
with no browser and nothing to paginate. Every earlier failure was a pagination failure, and
the reference caps its own PDF at one page — so the honest shape is a summary that counts
the whole span and names what it left off, rather than the HTML report again. - A menu bar popover in place of the menu. The menu answered two questions well and could
not answer more, because a menu is rows of text. This adds the day's total, the focus goal
as a bar, the last three sessions with their applications' icons, and a pause control. tools/screenshots.sh— every surface captured in one command, about two minutes. Not a
UI test and it makes no assertions: 951 contract checks had never caught a layout bug, and
the bottleneck was never whether to look but that looking cost a dozen commands each time.- Localisation, to the point where it needs a translator rather than a task. Every string
a reader sees goes throughLoc, whose key is the English text — so a missing translation
falls back to correct English rather than a raw identifier, and the contract keeps comparing
the strings it always did.tools/strings-audit.mjsholds it there. - A landing page at
nurkamol.github.io/replay-swift, and a v0.9.0 release with a
zipped app, since Homebrew and a source build are not routes everyone has.
Fixed
- Two copies of Replay could zero each other's live session.
ActivityStore.open()closes
any session left with no end — right after a crash, destructive while another copy is
running, since it setsended_at = started_at, duration = 0on the first instance's
in-flight session. Both then wrote to one SQLite file with no busy timeout, so a contended
write returnedSQLITE_BUSYand the event was lost with no error anywhere. A second launch
now hands the front to the first and exits; the store waits five seconds for a lock. Found
from a question, not from a check — whether running the Glaze app alongside this one could
explain a crash. It could not. This was underneath the question. - The menu bar panel opened mid-sentence. The popover is reused so the button can toggle
it and remembered its last size, while the content changes height — so taller content was
clipped off the top, taking the header and the day's total with it. - The year heatmap's weekday key sat a hundred points from the year it labelled. The grid
anchors trailing so it opens on today, which is right; applied unconditionally it also shoves
a grid that fits against the right edge. True at every window from about 1000pt, which is
every default window, and found by the first light-mode render in the project's history. - The week's rhythm strip left a dead gap before the durations at any wide window. Its
560pt cap was redundant — the page measure already bounds the row — and was concealing that
the strip is twenty-four bars that each want to fill, so removing the cap pushed the duration
column off the edge entirely until it was given layout priority. - A page never grew with its window: every surface was capped at the prose measure and
pinned left, so widening the window grew a dead column against the scroll bar. - The Canvas field is still. Its sway was this port's own, cost a permanent 30fps redraw,
and diverged from a reference whose graph does not move.
Known gaps
- Still not signed. The download is a zip and macOS will refuse it once; the release notes
say why and how. Homebrew and a source build avoid it entirely. - One language. The mechanism is built and every string goes through it; nobody has
translated anything.Loc.counthas two plural forms, which is English — Russian needs
three, Arabic six. A.stringsdictis the seam waiting for a language to be chosen.