Skip to content

TTP 3.2.5: the microphone turns off, Bluetooth keeps its sound, updates come by themselves - #7

Merged
AmirK-S merged 11 commits into
masterfrom
polaris/v3.0.0
Oct 2, 2026
Merged

AmirK-S merged 11 commits into
masterfrom
polaris/v3.0.0

Conversation

@AmirK-S

@AmirK-S AmirK-S commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Released as v3.2.5.

  • Capture: cpal 0.17.1, so the microphone really closes after each dictation; CoreAudio is asked afterwards (capture.mic_released / capture.mic_still_live).
  • Bluetooth default input: record from the Mac's microphone so the headset stays in music quality (capture.start.route).
  • Hotkey: detect an event tap that is enabled but hears nothing, and rebuild it (hotkey.tap_deaf, hotkey.secure_input).
  • Updates: hourly background check from Rust (update.checked).
  • Dictionary: classify on the live Groq model, and log failures.
  • Tray: back to the system-placed menu.

🤖 Generated with Claude Code

Amir KELLOU SIDHOUM and others added 11 commits September 27, 2026 17:41
cpal 0.15.3 gave every stream opened on a device picked by name a
disconnect listener holding a strong reference back to the stream. Dropping
it freed nothing, so each dictation on a pinned microphone left one more
AudioUnit running: on 2026-09-27 eight clean capture.start/capture.stop
pairs sat over a MacBook Air Microphone that never closed. cpal 0.16 breaks
the cycle with a Weak; a scratch repro on this Mac shows input live after
drop on 0.15.3 and off on 0.16.0. rodio keeps its own 0.15 for output,
which never registers the listener.

The trace could not see it because every capture check reads TTP's own
bookkeeping. mic_release asks the HAL, 0.3 s and 2 s after each drop,
whether this process still runs input: capture.mic_released normally,
capture.mic_still_live when it leaked, and the analyser's new
capture-mic-still-live check reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ooth headset

Opening a headset's microphone puts it in its call profile for as long as the
microphone is open, so whatever the user is listening to drops to call
quality while they dictate (every AirPods capture in the 2026-09-27 trace ran
at 24 kHz), and waking that profile is what makes Bluetooth starts slow.

With no device pinned, input_route now picks the Mac's own microphone when
the OS default input is Bluetooth. The headset stays when the lid is closed
(the built-in microphone is disconnected in hardware) or the Mac has none;
a pinned device still wins. capture.start carries the reason as `route`,
and the Settings option reads "Automatique" with a line saying what it does.

capture.stop's device_changed now compares the default at start with the
default at stop, as documented. It compared the device opened with the
default, so every pinned dictation reported a change that never happened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… builds

The e9f2652 build died four seconds after launch on 2026-09-27 (SIGSEGV at
0x4 in HALDeviceList::GetData). cpal 0.16.0's audio_devices() declares
`let data_size = 0u32` without `mut` and hands CoreAudio a pointer to it, so
an optimised build treats the device count as zero, allocates nothing, and
passes the empty Vec's dangling pointer — the crashed binary literally loads
`mov w5, #0x4` before AudioObjectGetPropertyData('dev#'). Debug builds, which
is all the tests and the leak repro ran, happen to work.

0.17 declares it `mut` and keeps the Weak disconnect listener: the leak repro
rebuilt in release with LTO shows input off after every drop. 0.17.1, not
0.17.3, because 0.17.3 cannot resolve alongside rodio's cpal 0.15 (alsa-sys).
`sample_rate()` is a plain u32 now; `name()` is deprecated for
`description()`, whose name is the same CoreAudio string, so a device pinned
under 0.15 still matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 2026-09-27 no key reached TTP from 16:22 to 16:37: no hotkey.press, no
ignored or dropped event, and every tap_health said enabled:true. Amir
could not dictate anywhere; installing an update relaunched TTP and fixed
it. An enabled tap that receives nothing was, to every existing check,
indistinguishable from nobody pressing anything.

The hardware keeps its own last-keyboard-event clock, independent of any
tap. Each watchdog pass compares it with the last key event the tap
delivered; a hardware event the tap has missed on two passes in a row
means a deaf tap, which is rebuilt (at most every 30 s, never abandoned)
and traced as hotkey.tap_deaf. Secure input, when keys reach no tap by
design, is traced as hotkey.secure_input with its owner and never counted.
TTP's own injected keystrokes are timestamped and left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The periodic check was a 4-hour setInterval in the hidden main window,
which App Nap freezes, so TTP only ever checked at launch. On 2026-09-27
3.2.4 was out all afternoon and the session running since 10:17 never
offered it; Amir found it by pressing "Check".

A Rust thread now checks 5 min after launch and then hourly, on wall-clock
time so a sleeping Mac does not stretch it, and stages what it finds for
the existing idle applier, which installs after two quiet minutes. Every
path shares stage_latest_update, which no longer downloads a version that
is already staged. Each background check is traced as update.checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS 27 a status item's window is 57 pt tall against a 33 pt menu bar,
and AppKit opens an attached menu below that window: TTP's menu floated at
58 pt while Control Center's panels sit at 33. A bare AppKit status item
with a plain NSMenu lands at the same 58 pt, so this is the system.

The NSMenu is now taken off the status item after every set_menu, and a
left or right click opens it with popUpMenuPositioningItem at the bottom of
the menu bar, left-aligned with the icon (a probe lands it at 34 pt).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The anchored menu still opened at 58 pt on Amir's Mac (a3f1588), while an
AppKit probe using the same call lands at 34 pt from a timer, from a real
mouse-down, and with the status item as the menu's delegate. The trace now
says whether the click reached TTP (tray.clicked), whether the menu was
still attached, and every number the position came from (tray.menu_opened),
plus each detach (tray.menu_detached).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x it

After the first menu rebuild TTP's own popUp, asked for 33 pt, appears at
58 pt with the status-menu style (117 pt tall, 111 before), with the menu
detached. A stand-alone tray-icon + muda lab does not reproduce it.
tray.menu_shown now records the menu window's real top, and a temporary
TTPTrayMenuMode default (plain, nodelegate, inview, move) selects the
strategy at click time, so each can be tried on one installed build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening the menu ourselves against the menu bar (a3f1588, 4a4dbf4,
cfb2751) did put it at 34 pt, but on macOS 27.0.1 AppKit then hides the
first item behind a scroll arrow, and touching the arrow scrolls the menu
back down to the 58 pt it wants (Amir's screenshots, 2026-10-02). A menu
with a hidden "Start recording" is worse than a gap, so the tray goes back
to exactly what 5413e96 shipped: the status item's own menu, opened by
macOS. Getting it flush would need a custom panel instead of an NSMenu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
classify.rs still asked Groq for llama-3.3-70b-versatile, shut down on
2026-08-16. Every call came back 404 "model does not exist", the error was
swallowed (fail-closed), and the dictionary stopped learning without a trace.

It now uses polish::MODEL (openai/gpt-oss-120b) with the same reasoning
settings as polish. The token budget includes the reasoning headroom: with
16 tokens alone gpt-oss spends them thinking, content comes back empty, and
the old parser read that as IGNORE, which would have kept the silence.

Failures are logged: 404, 401/403, 5xx and unreadable bodies as errors,
429 and network faults as warnings. The dictated text is never logged.
The verdict parser takes the first keyword and treats a reply with neither
LEARN nor IGNORE as an error instead of an IGNORE. Eight offline tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es come by themselves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AmirK-S
AmirK-S merged commit 662fe04 into master Oct 2, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant