From d93131b820cd094db00fd5fe7faaefc13b55c8f5 Mon Sep 17 00:00:00 2001 From: Bojie Li Date: Tue, 25 Aug 2026 20:32:46 +0800 Subject: [PATCH] release: bump version to 0.3.0 Minor rather than patch: the default model changes, which changes what every new install transcribes with. VERSION plus the seven fields scripts/stamp-version.sh propagates it to. Both CLIs report `dnt 0.3.0 (6938aa8, 2026-08-25)`. The near-miss gate was re-run because the model changed, which is the condition docs/RELEASING.md names. It came back 40/48 matched with 2 regressions rather than the 0 the suite asks for; the numbers and what they do and do not mean are in the pull request rather than buried here. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015XKX6zEAGiZFE5wAxJgYbV --- CHANGELOG.md | 92 +++++++++++++++++++++ Resources/Info.plist | 4 +- Sources/dnt/Dnt.swift | 2 +- VERSION | 2 +- android/app/build.gradle.kts | 4 +- ios/project.yml | 4 +- windows/Directory.Build.props | 2 +- windows/DoNotType.App/app.manifest | 2 +- windows/DoNotType.Cli/InspectionCommands.cs | 2 +- windows/DoNotType.Cli/Program.cs | 2 +- 10 files changed, 104 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f2decb..48dbe66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,98 @@ repository's local calendar date. ## Unreleased +## 0.3.0 - 2026-08-25 + +A model that quietly dropped most of a long dictation, and the three ways that is now caught. The +default model moves back to `gemini-3.6-flash`, a new guard notices a transcript too short for the +audio, and cuts in a long recording are placed by Silero rather than by frame energy. On Windows, +the settings windows and the recording pill finally scale with the display. + +### Fixed + +- **A long recording could come back missing most of what was said, with nothing to indicate it.** + Measured on a 90-second Mandarin recording, `gemini-3.5-flash` returned roughly 100 characters of + a 310-character transcript on **6 runs in 10**, stopping mid-sentence at the identical point each + time; `gemini-3.6-flash` did it 0 times in 20. It is not the output-token cap. Nothing in the + pipeline noticed, because `HallucinationGuard`'s rate ceiling is a *maximum* and 1.1 characters a + second passes it by being below the floor of suspicion rather than above the ceiling — so the + text read as fluent and plausible and was only wrong in what it did not say. `TruncationGuard` + now measures the transcript against Silero-confirmed speech rather than recording length, because + length cannot separate the cases: across 350 real dictations the legitimate minimum is 1.55 + characters a second of audio and the truncated transcript ran 1.09, while against speech the + truncated runs are 2.00 and 2.27 and the lowest real dictation is 4.92. Nothing is deleted — a + truncated transcript is still part of what was said — and short clips, unknown speech length and + an empty transcript are all left alone. + +- **Windows split long recordings at the first quiet frame instead of near a minute.** A C# record + *struct* ignores its primary constructor's defaults for `new()` and zero-initialises instead, so + `AudioChunker.DefaultPolicy` was every field zero: no minimum chunk length, a zero target, a zero + horizon that emptied the preferred set on every call, and a minimum pause of zero that made a + single 20 ms dip a legal cut. Swift and Kotlin aimed at 60 seconds throughout. Every chunker test + passed, because they assert that cuts land in silence and that no audio is lost, and both stay + true when the chunks are tiny. The default is now explicit and pinned field-for-field in all three + cores. + +- **A cut preferred a breath near the target over a clean sentence break slightly earlier.** The + boundary score subtracted the raw distance from the target — linear, unbounded, and in the same + units as nothing else in the score — so it dominated every quality term. Normalised by the width + of the acceptable window, distance still breaks ties but can no longer overrule a much better + pause inside the range the policy already called acceptable. Measured over the 60 retained + recordings past the splitting threshold: the median pause a cut lands in goes from 0.76 s to + 1.32 s and cuts landing in a pause of a second or more from 40% to 60%, with the same number of + chunks and a slightly shorter final chunk. + +- **The Windows settings windows and the recording pill scale with the display.** No window ever + assigned `AutoScaleMode`, so it stayed at `Inherit` and scaled nothing while the manifest asked + for PerMonitorV2 and the fonts scaled regardless; every pixel count in those files is a 96 DPI + number. The General tab's Save button sat below the fold with nothing left to scroll, because a + scrolling panel takes its extent from the last control's bounds and not from the container's + bottom padding — and the same was true of the Context Inspector's body, which renders the whole + encoded context and so does overflow. The recording pill was a 96 DPI drawing carrying a 9pt font + that grows with the display: at 200% a 24px line was asked to sit in a pill laid out for a 15px + one. + +### Changed + +- **`gemini-3.6-flash` is the default again.** It was demoted on 2026-08-17 because it was slow: + 4.39 s median on a short clip against 1.34 s, a 10.59 s p90, a 39.06 s maximum and 16% of + requests over 8 s. Re-measured, it answers the same clip in 1.95 s median against 1.45 s, p90 + 2.36 s, max 2.57 s, and nothing over 8 s in 20 runs; across recording lengths it costs 1.14–1.43×, + or +0.4 s to +1.8 s. The near-miss margin between the two is 41/47 against 39/47, inside the + suite's own per-pass noise, and is deliberately not the argument — the truncation behaviour above + is. **An existing installation keeps whatever model it has explicitly stored; only fresh installs + and unset fields change.** + +- **Cuts in a long recording are placed by Silero, not by frame energy.** Silero has been loaded and + run on every recording since it replaced the noise-floor heuristic, but only ever as a yes/no gate + on whether a chunk contains speech. The energy finder fragments a long pause the moment a breath + or a keyboard tap crosses the floor — 226 pauses of two seconds or more across 147 real + recordings, against 451 that Silero finds in the same audio. Taking the gaps between finalised + speech runs instead moves the median pause a cut lands in to 2.14 s and cuts landing in a pause of + a second or more to 77%. The model's recurrent state is carried across the capture rather than + re-read, so a live recording costs about 1.6 s of CPU for five minutes of audio instead of + roughly a core; audio Silero cannot parse still falls back to the energy finder rather than + growing into one unbounded request. macOS and iOS; Android still uses energy boundaries. + +- **The tokens a thinking level spends are reported rather than discarded.** The Gemini API returns + `total_thought_tokens` beside `total_output_tokens`, and every client threw it away, so the cost + of the dial was invisible in history and in the logs. It is a separate field, not part of the + output count: `total_tokens` is input + output + thought. Measured on a 22-second clip, `minimal` + and `low` both report exactly 0 on both shipping models — they are the same behaviour under two + names — and `medium` reports 500 and 700 against an 81-token transcript. + +### Documentation + +- **`docs/INCREMENTAL.md` records an investigation that ended in the feature not being built.** + Transcribing a long dictation while it is still being spoken, with a slower model on the hidden + latency and the earlier transcripts as context, was measured and rejected: segmenting recovered + more than 15% more text on 0 of 10 long recordings, was consistently worse on 6 Mandarin ones, + and a review of 258 aligned disagreements preferred the whole-file transcript. Raising the + thinking level was worse on both models, 1.6–2.3× slower, and quintupled screen-context + regressions on 3.6. The document keeps the rejected designs, the measurements that killed them, + and the mistakes made along the way — chief among them citing the near-miss suite for a question + its short clips cannot see. + ## 0.2.0 - 2026-08-20 Release preparation repaired the Android build after the AGP 9 migration, refreshed the Windows diff --git a/Resources/Info.plist b/Resources/Info.plist index 57571c4..8a6ed9f 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.2.1 + 0.3.0 CFBundleVersion - 201 + 300 LSMinimumSystemVersion 14.0 LSUIElement diff --git a/Sources/dnt/Dnt.swift b/Sources/dnt/Dnt.swift index a78b611..7c52c0c 100644 --- a/Sources/dnt/Dnt.swift +++ b/Sources/dnt/Dnt.swift @@ -38,7 +38,7 @@ struct Dnt: AsyncParsableCommand { dnt doctor --probe dnt logs --follow --level debug """, - version: "dnt 0.2.1 (df32636, 2026-08-20)", + version: "dnt 0.3.0 (6938aa8, 2026-08-25)", subcommands: [ Transcribe.self, Providers.self, Doctor.self, HistoryCommand.self, LogsCommand.self, PromptCommand.self, diff --git a/VERSION b/VERSION index 0c62199..0d91a54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.1 +0.3.0 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index aeb2011..7f0febf 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -32,8 +32,8 @@ android { // coroutine APIs used here available without desugaring. minSdk = 26 targetSdk = 35 - versionCode = 201 - versionName = "0.2.1" + versionCode = 300 + versionName = "0.3.0" // The value is a Java literal, so the quotes are part of it. buildConfigField("String", "BUILD_COMMIT", "\"$buildCommit\"") buildConfigField("String", "BUILD_TIMESTAMP", "\"$buildTimestamp\"") diff --git a/ios/project.yml b/ios/project.yml index 126fb2c..1b59535 100644 --- a/ios/project.yml +++ b/ios/project.yml @@ -15,8 +15,8 @@ settings: SWIFT_VERSION: "6.0" DEVELOPMENT_TEAM: 5BR9M56H9W CODE_SIGN_STYLE: Automatic - MARKETING_VERSION: "0.2.1" - CURRENT_PROJECT_VERSION: "201" + MARKETING_VERSION: "0.3.0" + CURRENT_PROJECT_VERSION: "300" SWIFT_STRICT_CONCURRENCY: complete packages: diff --git a/windows/Directory.Build.props b/windows/Directory.Build.props index 55a8ebd..0fa5758 100644 --- a/windows/Directory.Build.props +++ b/windows/Directory.Build.props @@ -10,7 +10,7 @@ true - 0.2.1 + 0.3.0