Skip to content

Windows shell - #19

Draft
neriousy wants to merge 59 commits into
mainfrom
windows-shell
Draft

Windows shell#19
neriousy wants to merge 59 commits into
mainfrom
windows-shell

Conversation

@neriousy

Copy link
Copy Markdown

No description provided.

Checkpoint of the Windows port work plus this session: the decluttered
speech-model browser (flat list, pinned active model, uninstall,
single-select filter), Cohere Transcribe recommendation coverage,
windowless curl downloads, and the start of the shared ui component
layer over gpui-component (Fluent theme bridge, embedded Lucide icons,
first Select migration) with the main window hosted in Root.
src/ is now common/ (paths, audio, dictation, history), desktop/ (shared
shell layer), speech/ (models and engines), platform/{windows,linux,macos}/
(one shell per OS, prefixes dropped), and ui/ (shared components). The old
flat module names stay valid through crate-root re-exports so call sites
migrate folder by folder; meeting_live/meeting_tests ride along via their
existing #[path] declarations. Verified by full check+tests on Windows and
module-tree parse checks against the linux and macos targets.
32/64px taskbar/caption icons now derive from app/AppIcon.icon/Assets/
Image.png (squared on a transparent canvas, Lanczos downscale).
The four hand-rolled Windows dropdowns (transcription model, dictation
language, microphone, interface language) now share ui::combobox — a
stateless, controlled DropdownButton+PopupMenu wrapper whose items are
rebuilt on every open (microphones re-enumerate then; enumeration errors
show as an inline note). Deletes the per-dropdown open flags, canvas
bounds tracking, backdrop/panel/flip-up layout machinery and its two
tests, and the Windows fork of disclosure_button.
The split DropdownButton read as a bare label with a boxed caret and only
dropped the menu from the chevron. The trigger is now a single 220px
secondary Button with an inline caret via the DropdownMenu trait, so the
whole control opens the menu and carries the standard Fluent border.
The component library's styling pipeline fought the Fluent look (trigger
refinements silently moved onto popover wrappers), so the dependency,
theme bridge, Root wrapper, embedded icon assets, and both migrations
(catalogue Select, settings comboboxes) are gone; the hand-rolled
dropdown stack and disclosure buttons are back. Kept from the detour:
dropdown panels can now be wider than their trigger (growing leftward)
so long microphone names fit, and height clamps quantize to whole rows.
desktop/ui.rs now has a single token palette (the Windows Fluent 2 dark
values) and a single body per widget — header_button, compact_button,
disclosure_button, toggle (position-interpolating Fluent pill), keycaps,
navigation_item, section labels, settings copy, segmented controls —
with only the icon primitives (Segoe glyph / SF Symbol / heroicon)
per-platform. The dropdown stack (backdrop, anchored flyout panel with
whole-row height clamping and leftward width growth, item rows) moves
from the Windows shell into the shared layer, as do selection_pill and
the control-fill constants; windows_ui re-exports its historical names.
A tr() shim translates on Windows and passes through elsewhere. macOS
and Linux now inherit the Windows look for every shared widget.
The Linux shell gains the listener hero card (status dot, device and
shortcut hint, in-window Start/Stop via the host actions) and a Problem
section that finally surfaces operation errors with a Dismiss control —
previously they were computed but never rendered. Section labels move
to the shared title-case style, and the content column widens to the
shared PANE_CONTENT_WIDTH via pane_content(). SUCCESS/CRITICAL join the
shared token set. Verified by Windows build+tests and a linux-target
resolution check (no name or field errors; only absent-crate noise).
macOS-parity hints, but real end to end: the local transcriber now
passes trimmed hints to Whisper as its initial decoder prompt via
transcribe-cpp's run extension (previously the field existed only in
settings and was never consumed by the GGUF runtime, on any platform).
The Windows settings pane gains the Recognition hints multiline row,
shown only while the active model supports hints; edits persist
immediately and a running listener restarts through the existing
"Applying settings" path after a 1.2s debounce. Hints survive model
switches — models that cannot use them load a stripped selection so
validation never rejects them.
macOS parity: with the new toggle on (shown under Double-tap to lock),
holding the shortcut no longer records — the first complete tap arms
the double-tap window silently and the second tap starts hands-free
capture, ended by the next press. Implemented in the CaptureState
machine with a recording flag so armed taps emit nothing, persisted in
WindowsSettings, exposed through the snapshot, and applied via the
usual listener restart. Covered by two new state-machine tests.
The tray glyph was a hand-rasterized white "H"; it now ships the actual
app icon, pre-rendered from app/AppIcon.icon/Assets/Image.png to a
32×32 raw-RGBA blob embedded at compile time. Full color works on both
taskbar themes, so the light-theme probe and the capsule rasterizer are
gone.
tray-icon defaults menu_on_left_click to true, so the context menu
swallowed the left click that should surface HEX. The menu is now
right-click only.
macOS parity for the "While dictating" behavior with three choices in a
segmented settings row: Mute silences every other process's WASAPI
render session (remembering exactly which ones it muted), Pause media
pauses currently-playing system media-transport sessions and resumes
them afterwards, Do nothing stays out of the way (default). Suppression
engages when recording starts and restores on finish or cancel, runs on
a dedicated COM/WinRT worker thread so the dictation loop never blocks,
restores on shutdown, and treats every failure as log-and-continue.
Adds scoped windows/windows-future dependencies for the WASAPI session
and GlobalSystemMediaTransportControls APIs.
macOS-parity toggle: with it on, the listener starts with the capture
device closed and opens it on demand when the shortcut is pressed —
recording begins at the first chunk, trading pre-roll and first-capture
latency for not holding the microphone (and its in-use indicator) open
all day. Release/cancel before the device finishes opening discards
cleanly, on-demand open failures report through the event log instead
of killing the listener, and the device label survives closed periods
for status lines. Recording finish/cancel closes the device again.
First slice of macOS Modes parity: a mode names case-insensitive
application substrings (matched against the focused process's
executable stem) and carries corrections that run after the global text
replacements, scoped to the app the dictation lands in. History entries
now record that application too. Modes persist in settings, hot-reload
through the same Arc<RwLock> path as replacements, and get an editor in
the Modes pane: add/remove modes, name and application fields, and
per-mode correction rows. Matching is covered by unit tests.
The Application section gains a Software updates row whose button
reflects the updater state: Checking while a query runs, an
accent-colored "Update to X" that opens the release page when one is
available, and "Check now" otherwise — backed by a new request_check()
that wakes the background worker immediately instead of waiting out the
six-hour interval.
Hold Ctrl+Alt+I (toggle in Settings, off by default), speak an
instruction, release: the transcription plus the focused application's
selected text go to the local OpenCode service and its reply pastes at
the cursor. Pieces: a third hold-to-talk binding in the keyboard hook
with its own capture state (Escape cancels, modifier release finishes,
mutual exclusion with dictation); clipboard-preserving Ctrl+C selection
capture in the paster; a minimal Windows client for the same
opencode2 api CLI the macOS processor drives (deadline-bounded, hidden
console, identical prompt protocol); voice jobs flowing through the
existing output worker with the reply stored as the last dictation for
re-paste. The settings row warns inline when OpenCode is not installed.
Works with release-microphone-while-idle via the pending-start path.
Voice Action moves out of the Settings pane into a dedicated nav tab
(between Modes and History, matching the macOS order) with the same
structure: an explainer paragraph, a Capture section holding the
shortcut toggle and keycaps, and a Processing section that reports
whether OpenCode is installed.
The voice-action arms now engage the While-dictating suppression on
start and restore it on finish/cancel, and play the start/stop/cancel
feedback tones symmetrically with ordinary dictation.
The duplicated OpenCode CLI client in the macOS dictation processor moves
to common/opencode.rs; both platforms now share discovery, the api
transport with deadlines and cancellation, the model catalog, prompts,
and generation. Windows discovery prefers the real opencode2.exe over npm
cmd shims (which shred JSON argv) and strips verbatim \?\ paths from
the x-opencode-directory header.

The Windows Voice Action pane gains a model picker fed by the catalog:
the OpenCode default is adopted automatically (also at startup when a
hotkey is configured), the choice persists in settings, and the running
listener picks it up per job. Without a chosen model the request omits
the model field so OpenCode's own default answers.

run_command now kills the whole process tree on deadline, cancel, and
exit - a process group on unix, a kill-on-close job object on Windows -
so pipe reader joins cannot hang on orphaned descendants. Install
detection only counts spawnable .exe files, and dictation failures now
surface in the settings hero instead of dying in the event log.
Modes with corrections, Voice Action with in-app model selection,
recognition hints, release-microphone-while-idle, while-dictating audio
control, and background update checks all shipped; the matrix and the
next-slices summary now say so instead of listing them as missing.
…model catalog

The Linux settings pane now matches Windows: separate Model (Browse +
dropdown), Language, and Microphone rows on the shared dropdown stack
instead of the combined picker row. The speech-model browser is one
shared dialog (desktop/model_catalog.rs) driven through a delegate trait;
Windows renders it with its caption inset and Fluent glyphs, Linux with
text controls, and both keep the language filter, install/uninstall,
progress, and the pinned active model.

Microphone choice persists in Linux settings and opens by exact name at
listener start, with automatic fallback when the device is gone; the CLI
--device override keeps its substring semantics. Devices re-enumerate on
every dropdown open, and changing the microphone, model, or language
requires a stopped listener - the controls dim and refuse while running.
The tray icon is rendered from the branded app icon instead of the
hand-drawn H.

Also fixes a pre-existing bare eyre! macro in the Linux dispatch arm that
failed to resolve, which broke every Linux build. The whole tree now
compiles and passes its suite (72 tests) on a real Linux toolchain under
WSL, replacing the old parse-only gate.
The shared search engine already matched text, application, and profile;
the Windows pane now has the search field in its header, filtering the
list live and keeping the selection when it survives the query.
A new Windows context adapter reads the foreground browser's page URL
from its UIA Document element value pattern - locale-independent and
working across the Chromium family and Firefox. The read starts when a
dictation is submitted so the accessibility walk overlaps inference, is
single-flight so a hung provider cannot pile up blocked threads, skips
DevTools and sidebar documents by requiring a web URL, and degrades to
application-only context after 300ms with a logged warning.

Modes gain website rules: exact hosts that beat application substrings,
mirroring the macOS selector ordering. Entries accept bare hosts, pasted
URLs, host:port forms, and IDN text, all reduced to their comparable
host. Voice Action prompts now carry the real browser host too.
The ed25519 trust core moves to common/self_update.rs - feed
verification, per-target content-addressed manifest validation, bounded
HTTPS-pinned downloads - and the Linux updater consumes it unchanged in
behavior. Windows managed installs (running from the user profile's
support versions layout, the ACL analog of Linux's ownership checks) now
verify, stage, and activate updates with an atomic current-version
pointer, repoint Launch at login, and restart through a watcher that
only starts the new version once the old process is gone; the Settings
row offers a one-shot 'Restart into X'. Source builds keep the GitHub
release link.

scripts/release-windows.sh mirrors the Linux prepare/publish discipline
with the same signing key, an MZ and --version artifact check, and a
stale-safe release lock.
The translation table, tr/tr_fill, and the language setting machinery
move from the Windows shell to desktop/i18n.rs; the Windows path stays
as an alias. System-locale resolution is per platform - the Windows user
locale as before, POSIX LC_ALL/LC_MESSAGES/LANG on Linux. The Linux
shell gains the persisted Interface language row with the same choices,
applies it at startup, and its remaining raw literals now translate,
including the statuses, dropdown labels, and the shared model catalog.
install-windows.ps1 creates the self-updating layout the updater
activates on - a checksum-verified, version-probed hex.exe under the
support versions directory, the current-version pointer, and a managed
Start Menu shortcut - and uninstalls cleanly while preserving settings.
Updates retarget the managed shortcut alongside the Launch-at-login
entry, so every launch path follows the active version. The release
script publishes the installer next to the signed feed. Bootstrap trust
is TLS plus the feed's content address; every subsequent update is
signature-verified by the app.
…ions

A fresh install (no persisted settings) gets a welcome dialog on both
port shells: pick a dictation language, download its recommended model
with live progress, and start dictating - or set up later. The
microphone stays cold until the dialog closes. recommended_selection
moves to the shared model catalog and now recommends per language
instead of falling back to English for everything but Polish, which
also upgrades Windows first-run defaults for other locales.

Per review: finishing persists the dialog's pending selection only when
its model is installed and verified (dropping any stale prepared
transcriber), every action is gated while any preparation is in flight,
progress rows name the model actually preparing, and re-picking the
current language no longer cancels a download.
Both shells gain the macOS Activity surface as one shared read-only
pane: live session state and age, the capture device, the recent
transcripts the shared activity reader already collected, the last
failure, and the observations path. Linux gets its first multi-pane
navigation for it.

The sidebar now matches macOS ordering with Activity last, and both
port shells render the same bundled vector icons drawn to match the
macOS SF Symbols, replacing the Windows Fluent glyphs. This commit also
carries the shells' onboarding wiring (first-run detection, delegate
impls, deferred listener start) alongside the pane plumbing they share
files with.
neriousy and others added 29 commits August 19, 2026 21:22
Keep commands and HUD Lab developer-only until Linux ships its native command assets and completes physical X11 validation.
Share the rich foreground-context vocabulary while keeping macOS capture and monitoring native.
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.

2 participants