Skip to content

LiveLingo 2.0: composer rewrite, voice dictation, model warm-up and auto-update - #1

Merged
Hongwei-MB merged 71 commits into
mainfrom
feat/composer-v3
Nov 2, 2026
Merged

Hongwei-MB merged 71 commits into
mainfrom
feat/composer-v3

Conversation

@Hongwei-MB

Copy link
Copy Markdown
Collaborator

What this is

LiveLingo 2.0: a rewrite around a single composer window. Summon it with a global hotkey, type, paste or dictate, pick Clean up, Translate or Original, and press Enter to write into the app you came from.

v2.0.0 is already tagged and released from this branch. Merging brings main up to the released code; it does not publish anything.

Contents (70 commits, see CHANGELOG.md)

  • Composer + web UI: Avalonia shell with the UI rendered in a WebView (Preact + htm, no Node build); all state and logic stay in C# and the page only draws snapshots and sends a closed set of intents.
  • Voice dictation: live text (X-ASR), settled text (Qwen3-ASR), fidelity-checked cleanup (Qwen3.5 4B), translation (Hy-MT2) on a pinned llama-server. Optional, per-feature cloud routing (text only, off by default).
  • Preferences: per-app remembered output and direction, default output, auto-dictate, shortcuts, drag/bounds.
  • Model warm-up: models are loaded ahead of use (dictation starts in 4-15 ms instead of 3-5 s). See docs/superpowers/specs/2026-11-02-model-warmup-design.md.
  • Auto-update: Velopack feeds in every release, background download, Restart to Update, install on quit.
  • Cleanup of 1.x: removed the 1.x docs, spike, unused Core/Platform code and the HfGguf.Cli tool.
  • CI/release: tests run on macOS (the only place the pinned llama.cpp runtimes and both Playwright engines exist), WiX pinned to 6.0.2, release job refuses a release whose feed does not name a package that is in it.

Checked before merging

Check Result
./build.sh Test HfGguf 4, Core 428, Desktop 666, Web 292, all green, 0 warnings; 98.8% line / 96.0% branch
Release pipeline (v2.0.0, and a v2.0.1-rc.1 dry run) compile, test, Windows + macOS pack, feed check, release all succeeded
Real Velopack client against the published GitHub release found and downloaded the update on both the win and osx channels
macOS end to end (installed 2.0.1, local feed with 2.0.2) app found the feed, downloaded 2.0.2 once, was replaced in place on quit (Info.plist and sq.version 2.0.2), and the updated app starts and no longer downloads
Existing users' settings found a blocker and fixed it: all 1.x/2.0.0 settings files hold the placeholder update URL, so installed apps could never have found an update. Replaced on load, with tests
Velopack hooks fixed: VelopackApp.Run is now first in Main, before the single-instance lock
Flaky test fixed the race behind TheSlowNotice_... (countdown is created before "working" is published)

Not verified

  • Windows apply step. Packing, the feed and the client download are verified for win; installing the downloaded update on Windows has not been run (no Windows machine). The logic is the same Velopack path.
  • Restart-now path (tray Restart to Update): not exercised; the quit path is.
  • Real-device behaviour of drag, shortcuts, bundle-id/exe-name app identity, auto-dictate; M2/16 GB timings (measured on M3 Pro).
  • Installers are unsigned (SmartScreen / Gatekeeper warn on first install).

Known issues, not part of this PR

  • Windows data location. On Windows the Velopack install root is %LocalAppData%\LiveLingo, which is also where settings, logs and the models (several GB) live. Uninstalling deletes the whole folder. The data should move out of the install root (needs a migration).
  • v2.0.0 cannot self-update (released without feeds); users install the next release once by hand. The .msi and development builds never self-update.
  • Updates are full packages (about 60 MB); no delta packages yet.

Hongwei-MB and others added 30 commits October 31, 2026 18:23
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop the 1.x translation stack (Gemma/Marian engines, routing and fallback
chain, language detection, old speech engines, processors, overlay and
settings UI, benchmark tool) and keep the platform layer, model download
infrastructure, localization and update service. Desktop is reduced to a
tray + global hotkey shell with a schema 3 settings model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…commit snapshot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…check and retry

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…archives

Every asset now carries a SHA256 that is verified after download; a mismatch
deletes the file and fails the install. Archives are unpacked with the .NET
readers (tar.gz, zip) or SharpCompress (tar.bz2) into one flattened directory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A failed Hugging Face request is no longer re-sent to a third-party host.
Only a mirror the user configured explicitly is used.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- verify existing asset files before trusting them; delete on hash failure
- serialize LlamaServerHost disposal with per-role locks
- fail all pending translations together on a service-level failure
- mac window tracker returns no target instead of a pid posing as a window id

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e next start

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d draft

Audio becomes units of a DraftSession; live hypotheses go to Draft.Pending.
Sentence splitting reuses TextUnits (now also ends at ;, … and repeated
terminators) instead of a second splitter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…he draft

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ry, parity probe

The llama-server cleaner matches the POC cleaner on 93% of gate outcomes.
Requests now close their connection after each call, which fixes sporadic
connection errors against llama-server.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The composer can listen (tap = hands-free, hold = push-to-talk), show the live
text, clean sentences while speaking and translate them as they finish.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…esults

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…yed text selectable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…their wording

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r and glossary

The glossary feeds second-pass hotwords, cleanup, the fidelity gate and
translation prompts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tible endpoint

Per-role consent switches in settings (off by default, text only), https
endpoint validation, API key kept in the system secret store, routing read on
every call with no local fallback after a cloud failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hongwei-MB and others added 29 commits November 1, 2026 14:25
Extend the spike with --bench, --park and --headless; record the macOS
results of S8a-S8g in the design (IME needs a keyCode 229 check, warm
window is parked instead of hidden, headless adapter is unusable).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the Vite/TypeScript toolchain with vendored Preact and htm assembled
at load time by the host, test with Playwright .NET, and record the Blazor
WASM memory comparison that ruled out the C# UI option.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Vendor preact and htm, split the prototype stylesheet into tokens and base,
assemble the embedded assets into one nonce-protected page in WebPage, and
add LiveLingo.Web.Tests that check the page and drive it in WebKit and
Chromium with Playwright.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- WebIntent: closed, strictly parsed intent set (size/range/version checks, no echo in rejection reasons)
- ComposerViewState.From: pure snapshot of the composer view model, with golden JSON fixtures for 25 prototype states
- WebBridge: ready handshake, 16 ms coalesced whole-state pushes, monotonic rev, strings message
- ComposerIntents: maps intents onto the view model; source revision rule keeps IME input safe
- WebSurface: NativeWebView host (no navigation, no new windows, non-persistent data)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preact/htm page that draws ComposerViewState and sends closed-set intents.
Playwright tests replay the golden snapshots in WebKit and Chromium.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace ComposerWindow.axaml with a persistent, parked ComposerWindow that
  hosts a long-lived WebSurface; the page is reloaded on every park so each
  summon starts from a clean page.
- ComposerWebSession wires one ComposerViewModel to the page per summon
  (strings, snapshots, toast, focus, openMenu, size, wide).
- IWindowTracker.Activate returns the keyboard to the application the user
  came from when the composer closes; text injectors reuse it.
- Remove the unused `reset` message from the page contract.
- Delete the old XAML composer, its dialogs and tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The settings window is a thin Avalonia shell around the shared web bundle (entry "settings").
State stays in SettingsViewModel; the page draws whole snapshots and sends closed-set intents
(setting, addTerm, removeTerm, cloudToggle, consent, saveKey, forgetKey, removeLegacyModels).
The cloud consent dialog and the hotkey recorder moved to the page; the API key is never part
of a snapshot and never printed. The old XAML window, the custom Avalonia controls, the Avalonia
theme and the headless test host are deleted. style-src-attr is relaxed for WebKit's native select.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…kept-original reasons

- Composer.CleanupEnabled (default on): off creates no CleanupScheduler, hides the "Clean"
  output, starts the composer on the original wording and stops the cloud chip claiming a
  cloud cleanup. The settings page gets the switch.
- The models group gets a status dialog listing version, system and switch states; it never
  contains terms, endpoint, deployment, device name or key.
- FidelityGate now returns a typed GateRejection instead of an English sentence, so the
  "original kept" note follows the interface language (composer.gate.* keys).
- Modal helper shared by the consent and status dialogs.

Recognition language and keeping recordings are intentionally not added: the speech engine has
no language option and nothing records audio to disk.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…or the web UI

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… window

Avalonia 12 only shows a TrayIcon that is registered via TrayIcon.SetIcons.
When our own app was frontmost, the window tracker returned the parked
composer as the target and the composer was placed off screen.

Co-authored-by: Cursor <cursoragent@cursor.com>
…screen

Show the window before moving it to the parked position (a first position far
off screen gives a 0 x 0 window on macOS), and move it on screen, still
transparent, before the session starts: WebKit runs no animation frames for an
off-screen page, so the page could never report its size.

Co-authored-by: Cursor <cursoragent@cursor.com>
…th an in-margin shadow

Typed text was sent to the cleaner on every key; each new request cancelled the
previous one, which also killed the llama-server that was still loading (22
starts in one second). The last sentence is now cleaned after 400 ms without
edits. The composer panel was see-through and its 88 px shadow was clipped to a
visible rectangle by the 18 px window margin.

Co-authored-by: Cursor <cursoragent@cursor.com>
…creen

The window only placed itself on the first measurement, so a result that grew the page pushed the footer buttons
below the screen. It is now re-placed on every size change, capped to the working area of its screen, and the page is
told how tall its panel may get so it scrolls inside instead. The header starts the system's own window drag (an
NSWindow drag on macOS, the caption move loop on Windows); the page only reports that the drag began, because pointer
screen coordinates differ between web engines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s one click

The last unwritten draft used to be put back into the composer on the next summon, so the previous session's text
(often a voice input) had to be cleared by hand. The composer now starts empty; when the last draft was closed
without being written, the empty state shows a Continue last draft button. Written drafts are never offered.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r the other actions

Dictation no longer needs a mouse button held down: Command/Ctrl+D (or a click on the microphone) starts it and the
same key stops it, and it keeps running when the window loses focus. Hold-to-talk is removed. The other actions get
shortcuts too (output 1-3, swap language, copy, new draft, recent drafts, import, export, settings). They are defined
once in C#, travel with the snapshot, match on the physical key and are ignored during an input method composition
and while a dialog is open. Menus, tooltips and hints show the key caps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t stays a default)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The composer begins with the output (clean up or translate) and translation
direction last chosen for the target application. A note under the hint and a
line in the output menu say where the choice came from, with one click back to
the defaults. Original, cloud switches and the microphone are never remembered.

- preferences.json (separate from settings): app id, two choices, last export
  format; LRU of 50 apps, no expiry; atomic write, corrupt file starts empty.
- Target windows carry a stable app id (bundle id on macOS, exe name on Windows).
- Settings: remember switch, default output, start dictating when summoned
  (global), and an app list with forget / clear all.
- The export dialog marks and focuses the format exported last.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…irst

A scan could read a draft that already had the first keystroke before the change
notification recorded the edit, so the edit counted as made long ago and the
typed sentence was cleaned at once. Edits are now observed in the same locked
step that reads the draft. The settings session test now waits on the snapshot
instead of a property that is set before it is announced.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dictation no longer stops after 4 s of silence. The microphone is closed only
after 5 minutes without any sound, with a notice that what was heard is kept.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ures time-zone independent

The product ships for Windows x64 and macOS arm64, the only pinned llama.cpp
runtimes, so its tests cannot run on Linux. The release workflow also lacked
the Playwright browsers that the web tests need. The resume-draft fixture held a
local clock time; it is now normalized like the key caps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WiX v7 refuses to build until the OSMF EULA is accepted, which is a decision for
the project owner. 6.0.2 is what the 1.4.x releases used.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- IModelManager loses model deletion, disk usage and storage relocation; the
  stub manager, null download coordinator and Hugging Face web-URL helper go.
- HfGguf keeps only the resolve URL and downloader; repository listing,
  quantization choosing, repo id parsing and the HfGguf.Cli tool are removed.
- Unused Win32 / Cocoa declarations, AudioBuffer.TrimBefore and the Input
  Monitoring probe are removed, together with tests that only checked record
  equality.
- 1.x product design, proposals, roadmap, demo images, the WebView spike,
  duplicate icon sources and scratch files are removed; the architecture rule and
  CONTRIBUTING describe the 2.0 layout.
- The update URL default no longer points at a placeholder repository.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mselves

Releases only carried installers, so an installed app found nothing to update to.

- Pack builds the Velopack package and feed (releases.<channel>.json) for both
  platforms. macOS now uses vpk pack instead of a hand-built .pkg, which the
  updater cannot manage; it gets an icon and a version-stamped Info.plist. The
  vpk environment now keeps PATH.
- The release workflow uploads the packages and feeds, refuses to publish a
  release whose feed does not name a package that is in it, and marks versions
  with a suffix as pre-releases.
- The app downloads a newer version in the background, offers Restart to
  Update in the tray menu and installs it on quit. UpdateChecks owns when to
  check and is tested; VelopackUpdateService is only the adapter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e suffix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Starting dictation reloaded three speech models every time (3-5 s), and the first
cleanup waited ~10 s for llama-server. Models are now loaded ahead of use and kept
while recently used.

- ResidentModel<T>: one load however many ask, lent to a user, unloaded when idle.
- SherpaSpeechEngine owns the speech models (dictation, import, warm-up); sessions
  borrow the large recognizers and make only the small detector. Replaces the
  per-session factory and the file transcriber's own load.
- ModelWarmup decides when: speech 3 s after startup (setting, default on), after
  the speech models are downloaded, on every summon, then the model of the
  starting output; the new output's model when it changes.
- LlamaServerHost.WarmAsync / IInferenceWarmup; no local load for roles routed to
  the cloud. InferenceModels is the one place that maps a role to its model.
- The empty composer says "speech models are getting ready" while they load.

Measured on an M3 Pro: dictation starts in 4-15 ms after warm-up; the first
cleanup drops from ~10 s to ~1 s.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Checked end to end on macOS: an installed 2.0.1 downloaded 2.0.2 from a feed and was
replaced by it on quit. That check found what blocked real users:

- Every settings file of 1.x and 2.0.0 stores the placeholder update URL
  (github.com/user/LiveLingo), so the corrected default never reached them. It is
  replaced on load; a user-set address is kept and an empty one turns updates off
  (it used to be filled in again at every start).
- VelopackApp.Run is now the first thing in Main, before the single-instance lock, so
  a running instance cannot turn the installer or updater hooks away.

Also fixes a race in the composer: the slow-output countdown is now created before the
"working" status is published, which made five tests flaky.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CleanupScheduler: the test read the timer after a fixed 50 ms, before the
  scheduler's worker had set it on a loaded runner; it now waits for the timer.
- Web tests: a size report queued by a render could arrive after the intents
  were cleared and be taken for the reaction to a shortcut; clearing now lets the
  page's pending animation frames run first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Hongwei-MB
Hongwei-MB merged commit 9451d2c into main Nov 2, 2026
1 check 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