• christian released this 2026-10-15 16:05:50 -04:00 | 0 commits to main since this release

    Downloads
  • christian released this 2026-10-15 15:26:07 -04:00 | 1 commits to main since this release

    Downloads
  • christian released this 2026-10-15 15:17:40 -04:00 | 2 commits to main since this release

    Downloads
  • christian released this 2026-10-15 14:15:12 -04:00 | 3 commits to main since this release

    Downloads
  • christian released this 2026-10-15 14:00:48 -04:00 | 4 commits to main since this release

    Downloads
  • christian released this 2026-09-24 13:24:22 -04:00 | 11 commits to main since this release

    Fixes a machine that falls behind and never recovers.

    What was happening

    A campus Mac mini (M4 Pro, 24 GB) went red within about 90 seconds of starting, with nobody listening and no language being translated — so this was transcription alone, not translation load. Measured over SSH on the machine itself:

    sentences   4.2 – 4.7 s each, against roughly 4–5 s of audio
    queue       46 and growing
    lag         152 s and still climbing
    input       real speech, transcribed accurately
    

    The app has a mechanism for precisely this: three slow sentences in a row and it steps down the speech model. It could not fire. It triggers past 8 seconds, on the reasoning that 8 s means slower than people talk — true when a chunk could be 8 s long, but chunks are capped at 5.5 s (HARD_CUT_MAX). A 4.4 s sentence is already losing ground and never comes near 8 s, so the ladder never stepped down and the backlog grew without bound.

    What changed

    It now compares the time taken against the audio the chunk actually held, rather than against a constant.

    Two numbers in that test are measured rather than picked:

    • 0.85 margin. The Mini's 4.4 s against 4.5 s of audio was nominally faster than real time while still losing badly — the work outside that timer (segmenting, the preview model sharing the GPU, the scheduler) is enough to lose on its own.
    • A backlog of five. Direct evidence the machine is actually losing, so one slow sentence on a healthy Mac touches nothing.

    Checked against eight cases including the Mini's real numbers and a healthy M2 Max. The old absolute rule stays for a model that is genuinely hung.

    When it fires, the log now names the arithmetic: transcription is slower than people are speaking (4400 ms for 4500 ms of audio, 46 waiting) — stepping down to a lighter model.

    Downloads
  • christian released this 2026-09-24 13:05:11 -04:00 | 12 commits to main since this release

    The one to test the updater with. If you are on 115, this should arrive with a single click: Settings → Updates → Check → Download and install. The app closes, swaps itself and reopens on 116 — no disk image, no dragging.

    What it adds

    /healthz now reports the app's real version.

    That endpoint has always carried a build field, but it is a hash of the listener page's assets — a cache-busting token, not a version. So the only way to tell what a campus Mac was actually running was to install something and watch the hash change, which is how the last three updates were confirmed. With an updater in the field, "what version is that machine on" is the first question anyone asks.

    {"ok":true,"live":false,"build":"fd663436aa","version":"3.23.2","appBuild":"116"}
    

    One unauthenticated request per campus, from anywhere on the network.

    It proved itself immediately: the development Mac reported appBuild 112 while its source tree was at 115, because copying loose files over an installed bundle leaves that bundle's package.json behind. A packaged install reports correctly.

    Downloads
  • christian released this 2026-09-24 12:50:24 -04:00 | 13 commits to main since this release

    The update installs itself. One button: Download and install.

    Downloading and then asking the operator to quit, mount a disk image and drag the app across was the friction the updater was meant to remove. Now it downloads, closes, swaps itself and reopens — about a minute and a half on a church LAN.

    How the swap is done safely

    An app cannot replace itself while it is running, so a small script outlives the process: it waits for the app to exit, moves the old bundle aside, copies the new one in, clears the quarantine flag and relaunches.

    Moved aside, not deleted. If the copy fails half way — a full disk, a truncated download, the volume going away — the old app goes straight back and is reopened. The failure this is written against is a campus Mac with no working app and a service in an hour, which is far worse than not updating.

    Tested in a sandbox across all three paths:

    result
    clean swap new app in place, no leftovers
    copy fails mid-way old app restored, no leftovers
    disk image has no app aborts before touching the installed one

    It refuses while translation is live. Quitting stops every listener's captions, and no update is worth that — press Stop translating first.

    No administrator password: /Applications is admin-writable and the bundle belongs to the operator. If that is ever untrue, the copy fails and the rollback runs.

    If the install cannot proceed for any reason, the downloaded disk image is still in Downloads and Finder opens on it, so the manual route is always available as a fallback.

    Downloads
  • christian released this 2026-09-24 12:36:56 -04:00 | 14 commits to main since this release

    The operator page now says what is loading the machine when it falls behind.

    Why

    Translation cost is roughly linear in the number of active languages, and it all goes through one NLLB process — measured at five languages in 1.9 s. So when a Mac is running behind, the single most useful fact is how many languages it is carrying. That number was being computed one function away from the health verdict and never shown.

    A campus reported being constantly in the red. The cause was eight languages with let listeners pick any switched on, against a pipeline tuned and measured against one — and finding that took an afternoon of reading logs. The verdict now says it in the same sentence that reports the problem:

    Falling badly behind — some sentences are captions only. Carrying 8 languages, and listeners may add more.

    It stays quiet when it cannot help: nothing is added below two languages, and the listeners may add more clause appears only when that setting is actually on.

    Installing

    If you are on build 113, use Settings → Updates — this is the first release that can arrive that way. Older builds need one manual install of 113 or later first.

    Downloads
  • christian released this 2026-09-24 12:19:42 -04:00 | 15 commits to main since this release

    Everything in build 112, plus the app can now fetch its own updates.

    This is the last build that has to be installed by hand. Once a Mac is on 113, the next one arrives through Settings → Updates.

    Updates, from the church's own server

    Settings gains an Updates block. It asks this Gitea for the newest release, compares the tag against the running build, shows what changed, and downloads the installer to Downloads.

    Deliberately not electron-updater: that wants a static feed published beside the artifacts, and this build is ad-hoc signed with no Team ID, so a silent in-place swap would meet Gatekeeper on a machine nobody is sitting at. Nothing here happens on its own either — a background check announcing itself three minutes into a sermon is a hazard. The operator presses check, presses download, and drags the app across when the room is empty. Settings, models and voices are untouched by an install.

    Tags are v<version>-<build>, compared numerically field by field, so 3.9.0 does not read as newer than 3.23.2 and build 113 beats 112.

    Two faults that only real testing found, both fixed here: Gitea answers with whatever address it was reached on, so release assets came back on the IP while the forge is configured by hostname — the same-host guard refused a perfectly good installer, and the URL is now rebuilt on the configured origin. And a private repository answers 404 rather than 401, so "no releases published yet" told the operator the opposite of the truth; without a token that case now names the remedy.

    While the repository is private the check needs a read:repository access token, pasted once per machine under Settings → Updates. It stays on that Mac and is not carried by Save settings to a file. Making the repository public removes that step entirely.

    Also included

    tools/diag.sh — a read-only one-shot diagnostic for the "it is behind / in the red" case. It prints the hardware, whether the machine is swapping, which models the app actually chose at startup, and how long sentences are really taking.

    Downloads