New installs start detecting instead of being locked to one language; a stored
value from before this default still wins. The dictation chain asks
transcribe_detected() in auto mode, records the detected code in history as
speech_language, hands it to the cleanup prompt (a detected Turkish recording
gets the Turkish prompt and glossary rule), and reports it on the socket reply.
The stale comment claiming whisper.cpp's -l auto does not detect is corrected.
The spoken language is only knowable when the transcription model reports it,
and only whisper.cpp does: the hosted endpoints accept auto but never say what
they heard. transcribe_detected() asks for detection exactly where it can be
answered, the local server in auto mode, by requesting verbose_json with
no_language_probabilities switched back on for that one request (the server
runs with -nlp, which keeps the sweep off everything else).
The agent and meeting tabs opened with an intro paragraph, and the
cleanup provider box showed a long tooltip comparing the providers.
None of them earned the space: the intro texts were unclear and the
tooltip restated what picking a provider already shows. The orphaned
Turkish translations go with them.
Master grew Google AI Studio and Antigravity as providers, a doctor that
names each provider's own key, and settings that fetch every hosted
model list as the window opens. OpenCode Go is folded into each: its
name joins SERVICES and the doctor's key table, its key row sits beside
Google's, its Fetch button follows the per-provider pattern Google's
uses, and _load_hosted_models fetches its catalog at open when a key is
on file, filling the cleanup and agent boxes alike.
Qt's xcb plugin links libxkbcommon and libxkbcommon-x11, and the x11 half
hands keymap objects to the core half to free, so the pair must come from
one build. The build machine has only the core half installed, so
PyInstaller bundled that one alone and the other kept loading from the
user's system; an Ubuntu 22.04 core freeing what a current x11 half
allocated is the segfault of issue #57, which a Turkish layout happened
to move to startup. Every desktop that can show a window carries both
libraries from one build, so the bundle now carries neither.
Fixes#57
Codex's list already arrived on its own, because reading a cache on disk
costs nothing. The other three lists were behind a Fetch button, which
meant the built-in ones aged in front of anyone who never pressed it. Now
OpenRouter's and Google's lists are fetched when the settings window
opens, and Antigravity's comes off `agy models`, which prints one
id-tab-name line per model and answers over the network in a couple of
seconds; all three run off the interface thread.
Nobody asked, so nothing is reported: a failure changes nothing on
screen, the built-in lists stay, and the Fetch buttons remain both the
retry and the place an error is worth explaining. A provider whose key
has not been given is not called at all, so opening Settings is not by
itself a request to two vendors.
Also drops an import the merge had left in twice.
Co-Authored-By: Claude Fable 5 <[email protected]>
Codex already refreshes its boxes from the source at open, so the
built-in list is never the whole truth for longer than a window takes to
build. OpenCode Go now gets the same courtesy: one request to /models on
a background thread, both of its boxes refilled with the picked model
kept, skipped entirely when there is no key to send.
Nothing was measured that put OpenCode Go beside it, so the tooltip
claims only what is known: OpenRouter is the quickest, and OpenCode Go
merely needs nothing installed either. The translation follows word for
word.
ox-alpha-free is not in the /models answer, and the comment claimed the
chat endpoint hides Grok, Luna, MiniMax and Qwen when its own catalog
lists them. The built-in list is a starting set; the Fetch button asks
the endpoint for the full catalog of the day.
The tooltip gained OpenCode Go but its translation was added without the
llama.cpp sentence the tooltip actually carries, so a Turkish window
showed the English text. The full entry now matches the tooltip word for
word, and the two shorter variants nothing looks up any more are gone.
The one button lived in the OpenRouter model row, which leaves the
screen whenever another provider is chosen, so the OpenCode fetch path
could never be clicked. The OpenCode row now carries its own button into
the same handler, the row as a whole is what hides, and the fetched list
lands in the box it belongs to without touching the meeting box.
The settings-storage paragraph goes back to the one sentence it was. The screen list now shows native resolutions rather than the scaled ones, the Turkish tab is named Ekran, and the repositioning comment says what a named screen changes.
Codex now asks itself for its model list, doctor grew ready flags and a
per-provider line, and the Groq key joined the masked ones; the OpenCode
additions are folded into each. The no-CLI cleanup test follows the
fake_run to fake_cli rename.
Both sides rewrote the doctor: master rebuilt it around ready flags so a
fully local setup stops reading as broken, while this branch taught it
that Google has a key of its own and that the local model has neither a
key nor a program. Kept master's structure and folded the branch in: the
two hosted providers answer for their keys, a CLI for its program, and
the JSON keeps both the provider-aware "key" and master's "ready".
SECRET_KEYS gained groq on master and gemini here; the resolution keeps
all four. _conclude was also changed by both: master stopped matching
stderr wording and asks _API_TROUBLE instead, this branch renamed its
last parameter to the provider's short name so a session is stored under
the name it is read back by; the rename now rides on master's body.
codex_models() and the settings loaders landed beside the new Gemini
ones, so both stay. The new cleanup tests called the CLI fake by its old
name, fake_run, which master had renamed to fake_cli.
The paste block was rewritten by both sides: master taught press() to
put the remembered application back in front (focus), this branch moved
the history write ahead of the paste and stopped restoring the old
clipboard over a transcript the key press refused. Kept this branch's
order and error handling, and handed press() the focus it now takes.
deleteLater destroyed a window whose model boxes a daemon thread was
still reporting into: a running download died with a RuntimeError, the
.part stayed on disk and the UI said nothing. Dropping the reference is
how the ordinary close path lets a window go, and the closures a thread
holds keep the object alive until it is done, so the rebuild now does
the same. While the transcriber or either model box is working the
rebuild is skipped altogether; _built_language keeps the old language,
so the next quiet save asks for it by itself.
The new window is also placed and turned to the old tab before it is
shown, so it no longer comes up at the default size and jumps. And
tests/test_ui.py now says that a save with a changed language emits
language_changed, and one without does not.
Google's compatibility layer has no word for off. Sending
reasoning_effort "none" is refused outright, so choosing Thinking → Off
made every cleanup fail and paste the raw transcript instead:
HTTP 400: Request contains an invalid argument. (INVALID_ARGUMENT)
Measured against gemini-3.5-flash-lite, asking it to reply "ok":
nothing sent 50.59s
reasoning_effort "none" 400
reasoning_effort "minimal" 15.40s
reasoning_effort "low" 53.35s
reasoning_effort "high" 63.83s
So "none" lands on "minimal", which is both accepted and the quickest of
them, and quickest is what cleanup wants. The thinking_config route the
documentation offers is an SDK wrapper and is not a field this endpoint
knows: sending it is "Unknown name \"google\"".
Co-Authored-By: Claude Opus 5 <[email protected]>
Google answers some failures with a JSON array holding the object every
other provider sends on its own. _extract_error called .get() on it and
raised AttributeError, which is not the ApiError every caller is holding,
so a 503 from Google took the whole dictation down instead of pasting the
raw transcript with the failure shown beside it.
Found by dictating against a Google AI Studio outage:
HTTP Error 503: Service Unavailable
AttributeError: 'list' object has no attribute 'get'
It runs while an exception is being raised, so it now ends in a string
whatever arrives.
Co-Authored-By: Claude Opus 5 <[email protected]>
OpenRouter's free tier rate-limits and carries no free Gemini model, and
cleaning up through Claude Code costs a fixed few seconds because it opens
a whole CLI session to drop three "uh"s. Google's own free tier suits a
short, frequent request, and its OpenAI-compatible endpoint answers
/chat/completions, so cleanup there is one request and the same code path
OpenRouter already takes.
The one thing that is not shared is the thinking level. Google reads
OpenAI's flat reasoning_effort rather than OpenRouter's object, and "none"
is how thinking is turned off, so it is sent rather than skipped: a Flash
model left to think spends exactly the second this provider was chosen to
save. Its top two rungs land on "high", which is as far as Google goes.
Speech to text stays where it was. That endpoint has no
/audio/transcriptions behind it, audio only goes in as base64 inside a
chat message, and what comes back has none of the segment times a subtitle
file or a meeting transcript is built out of.
Antigravity joins as well, on cleanup and as an agent. It is a CLI like the
other two and costs the same session, so it is here for people who already
pay for it rather than as an answer to the speed. It takes neither an empty
tool list nor a read-only sandbox, and cleanup.py now says so plainly
instead of implying parity; what it gets is a project of its own, the home
directory, and its slash commands off.
Three things were already wrong and are fixed on the way past, because the
new providers walk the same paths: doctor raised KeyError on the local
model, whose executable is ""; the history recorded Claude's model whoever
answered; and every agent row read "asked Claude".
Co-Authored-By: Claude Opus 5 <[email protected]>
test-key accepts opencode and reports the model count its /models
endpoint offers, ask --provider accepts it and maps --model to its own
setting, doctor reports its key for cleanup on it, and config list
masks the key like the other two.
A key row under Keys, a model box in the cleanup tab and a box in the
agent tab, each with its own model list of the models OpenCode Go
serves over /chat/completions. Fetch model list reads whichever cleanup
provider is on screen, and the Turkish strings cover the new rows.
OpenCode Go serves open coding models from an OpenAI-compatible endpoint. It cannot transcribe, so it joins
the two LLM jobs: transcript cleanup and the plain-chat agent. The key
falls back to OPENCODE_API_KEY, and api.chat() stops hardcoding the
OpenRouter name so the same call serves both.
Pressing the shortcut while a transcript was being transcribed or cleaned up
did nothing, and the thought you had while waiting was lost. The microphone is
free the moment a recording stops, so the next dictation can now be spoken at
once; the pipeline queues it and each one is finished, pasted and reported in
the order it was spoken. The corner indicator stays with the recording under
way rather than being wiped by the previous run's progress, and a stop that
lands behind an unfinished run says it is waiting its turn.
Every model box has taken a typed name all along, but nothing said so, and a
closed-looking list reads as the whole choice. The Claude Code and Codex boxes
now carry a tooltip saying the list is a starting point, not a fence. Claude
gets no fetched list of its own: its CLI has no catalog command, and the
aliases it takes (sonnet, opus, haiku, fable) already follow the newest model
of each line.
The Codex model boxes carried a hand-written list, which was already out of
date. The settings window now asks `codex debug models` for the catalog the
CLI's own picker reads, off the interface thread, and refills both boxes with
it; the built-in list is only what is on screen until Codex answers, or when
it is not installed at all.
The macOS press learned a third argument, the application that was in front
when the recording started, and every backend is handed it. SendInput takes
no such thing: nothing on Windows moves the front while a recording runs, so
the process id arrives and is left alone.
Without this the Windows backend refuses the call the shared press makes, and
the five key tests in tests.test_paste.Windows stop at a TypeError.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The recording indicator (overlay) was shown in a fixed corner of the
screen it first appeared on. Moving the mouse to another monitor while
a dictation was in progress caused the indicator to disappear from view,
even though the recording was still running.
Poll the cursor position every ~333 ms inside the animation tick and
call _reposition() when the cursor has moved to a different screen. The
window then jumps to the same corner on whichever monitor the user is
looking at, making it clear that recording is still active.
The check is throttled to every ten ticks rather than every frame (30 Hz)
because screenAt() is a trivial coordinate lookup but there is no value
in calling it more often than the human hand can move between monitors.
When using long model names (e.g. OpenRouter models like
'openai/whisper-large-v3-turbo'), the combo boxes in Settings were
too narrow to display them, showing truncated text like 'open...ribe'.
Apply QSizePolicy.Expanding (horizontal) to all editable QComboBoxes
so they stretch to fill the available width of the form layout, just
as non-editable selects already do.
Affected boxes: transcribe_model, cleanup_model, cleanup_claude_model,
cleanup_codex_model, assistant_model, assistant_codex_model,
assistant_openrouter_model, meeting_model, paste_shortcut, repo,
and all shortcut picker boxes.
doctor judged the local provider by the API key it does not use, so a
fully local machine always saw a red mark, and it crashed outright with
local cleanup picked; both lines now ask readiness. config list printed
the Groq key in plaintext while masking the other two. The one subprocess
decoded with the locale codepage gets its UTF-8 back, so a Turkish
filename cannot hang a file transcription, and a redirected stdout on
Windows replaces what it cannot encode instead of failing after the work
succeeded. meeting-cancel stops advertising a --wait the server never
honoured. "you" and "bye" leave the hallucination list: people dictate
them. The minutes stage failing no longer burns the transcription
checkpoint, and an untouched meeting-length dial no longer rewrites a
value the command line set in seconds. A prompt box compared against the
wrong language's default after a switch no longer fossilizes the old
default as a custom prompt.
The 67 strings of the local-model box, the whole first-run screen of the
shipped default, get their Turkish. The hub cache moves to the platform's
cache directory instead of ~/.cache on every system; the old directory is
a few orphaned kilobytes with a six-hour shelf life. The last NO_WINDOW
spellings collapse into the constant paths already carries, one
windowed-executable lookup, one session-file reader, one install-record
reader, one download progress signal carrying its destination, and the
KDE conflict scan loses the branch its other branch already covered.
Co-Authored-By: Claude Fable 5 <[email protected]>