Repository navigation
.NET 10, portable core, and cross-platform spikes (stages 1-3) - #3
Merged
Merged
Conversation
Behavior-preserving upgrade from net8.0-windows to net10.0-windows. Pins the SDK in global.json and points CI setup-dotnet at it. No code, dependency, installer identity, or settings format changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
PrimeDictate.Core (net10.0) holds platform-neutral audio primitives, the versioned transcript model and events, session state, provider and model-registry contracts, microphone/model coordination, SQLite session storage, and separate transcription preferences with migrations. The Windows app references Core, keeps the dictation string path unchanged, and adds structured timed results (native timestamps for Whisper.net, approximate chunk timing elsewhere). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…ndings Adds PrimeDictate.Platforms (SoundFlow/miniaudio capture for macOS and Linux) and a minimal Avalonia 12 desktop shell, plus the spike record with measured results and decisions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Chunker, utterance detector, WAV reader/writer, exporters, session document host, file transcription runner and buffered live session. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Verified on Linux with mp3, mp4 and wav imports and live capture through a PulseAudio null sink (provisional then final segments, pause/resume, stop). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Import (picker and drag and drop), record with pause/stop/discard, model selector, session list, searchable editable transcript, copy, export, rerun and delete. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
WASAPI loopback (Windows, untested), PulseAudio monitor (Linux, tested on a virtual sink), macOS stub, CombinedAudioSource on one clock, minimal source picker. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
Adds SystemAudio and Meeting source types, takes the microphone lease only for sources that use the microphone, and keeps left=mic/right=system stereo working audio for meetings while recognition still gets the mono mix. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…; test channel order Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
… samples Runtime built at the pinned commit, models checksum-verified, file, live, pause, model-swap and limit checks run on CPU. Vulkan aborts on both GPUs at this commit. Real worker captures for the integration are under docs/architecture/nemotron-samples. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
… desktop crashes - flush the WAV header every second of audio (was only on pause/stop) - repair headers of interrupted sessions' audio at startup - log unhandled exceptions to logs/desktop-crash.log - fail the WASAPI lease instead of throwing on capture/timer threads - regression tests: system-only 48 kHz stereo session, header flush, repair Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
One lane per speaker on a shared time axis, provisional bars dashed and translucent, click to select a transcript line, wheel to pan and zoom. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…eline, live scrolling) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
Realtime socket session with commit offsets, provisional partials, speaker turns per utterance, and a streaming branch in the live session. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…els) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
…iders Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
The capture-to-inference queue was bounded at 256 items (about 5 s), so a slow model blocked capture and recording stopped. The queue is now unbounded (audio is already on disk; Backlog reports the lag), previews only run when the queue is nearly drained and back off by their own cost, and the utterance detector skips preview snapshots when none will run. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pop6MMPof79Dd7oPc93QUj
SherpaWhisperProvider now sets NumThreads (half the logical cores, 2-8); sherpa-onnx defaulted to one thread. Deleting a session that is recording discards the recording first, files that cannot be deleted are reported in SessionDeletionResult.FailedFiles instead of throwing, and window button handlers report failures in the status line instead of crashing. Windowed live utterances are capped at 15 s to bound re-recognition cost. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Long runs of flagged synthetic-silence or exact-zero audio are not sent to the worker (NeMo-Speech.cpp#48 wedges the realtime stream on them); they stay in the saved recording and offsets are unchanged. A watchdog replaces a connection that receives speech but returns nothing, drops the provisional/pending text the old stream owed, and keeps later speaker numbers apart from earlier ones. The frame flag is the Core contract from the desktop-audio thread's 6e01fc4. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…hole-recording speaker labels Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…to claude/dictation-parity-4k0tl9
Dictation in the new desktop app: loop, hotkeys, typing, focus guard, overlay, tray, settings
…tion for recordings that fit, test cleanup on Windows, acceptance item 3 wording Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…to claude/project-thread-gmksub
…hisper otherwise The workspace now lists installed Parakeet and Moonshine models. When the final-pass box is ticked and Nemotron is the chosen model, the live draft uses Parakeet, else English Whisper, else any Whisper. Other choices are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…tem speakers A new live-text choice next to the final-pass box runs no recognizer during the call (default, remembered). The status line says whether zero system speakers means silence, a diarizer failure, or words not recognized. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…ech the diarizer found, read diarizer JSON safely Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…he capture loop read it Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
… a single running instance Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKWc6Fugd7yn8xw3ozLq8n
…to claude/onnx-cuda-provider
…ed it Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKWc6Fugd7yn8xw3ozLq8n
ONNX speech models on CUDA, plus tray Exit and --quit
…their speaker and time; hide duplicate words in overlaps; report per-turn word counts Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
… a window by speaker turn only when it has no word times Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…ways finishes; a cut-short meeting is finished from its saved audio on the next launch Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
Quit could leave a session Running: the live session was only tracked once StartAsync returned, StopLiveAsync cleared it before stopping so the timeout fallback discarded nothing, Stop/Discard return early when a stop is already under way, and a stalled device made the graceful stop wait forever. Add LiveTranscriptionSession.AbortAsync (bounded waits, always ends Canceled), track starting and stopping sessions in the workspace, and sweep the store at exit so nothing stays Running, Paused or Finalizing. Finalizing may now be canceled. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKWc6Fugd7yn8xw3ozLq8n
… a rotated app.log next to the worker log Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f
…to claude/exit-finalize-recording
…nch finishes it A meeting with a pending after-Stop marker and a saved stereo recording ends Interrupted at exit, the state the resume-on-launch recovery takes, instead of Canceled (which means thrown away). Other in-flight sessions are still canceled. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKWc6Fugd7yn8xw3ozLq8n
Exit always leaves a recording Canceled or Completed
The after-Stop meeting pass cut system rows only where the speaker changed, so in the 9:14 PM run (build cbde440) one remote speaker's three diarizer turns became a single 24.5-46.9 s row around Justin's own line at 40.8 s. Rows are now cut from each channel's words, across recognizer windows: at speaker changes, where the diarizer's turn for that speaker ends (segments under 1 s apart are one turn), at pauses of 1 s or more, and where the other channel's row starts inside one (unless that leaves a piece under 0.5 s). Each word keeps the diarizer speaker covering it most; flicker smoothing stays within pause-free stretches, so a lone reply keeps its speaker. Echo words get hidden rows of their own. The run is marked SegmentsAreRows, and TranscriptTurns shows such a result one segment per row in start order, as exports list them, instead of joining lines within 5 s again or folding short ones into an earlier row. Older runs read false. Tests: the 9:14 PM word and diarizer times (times only) end to end, each row rule, and old stored runs still loading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ping-6b2617 Final pass: one row per speaker turn, in start order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new app looked the WPF-saved microphone (a Windows endpoint id) up among miniaudio's device names, so it never matched: dictation and the wake word never opened a microphone, and the only sign was a 6 s overlay notice. On Justin's PC the saved device no longer exists at all; the WPF app had been falling back to the default microphone. - MiniAudioCaptureSource accepts a Windows endpoint id and looks it up by the name Windows gives it (the same name miniaudio lists). - Dictation and the wake word record from the default microphone when the chosen one is not connected, as the WPF app does, and say so once. - "The selected model is not installed" is said once, not on every wake-word check, and names the model used instead. - app.log records why dictation did not start, why a transcript was not typed, when the wake word stops listening, and hotkey hook failures. Never text. Checked on Justin's PC: the wake listener opened the default microphone 3 s after launch, and app.log shows the fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From Justin's first Windows run of the new app. Overlay: - Shown only while dictating (and briefly for a notice or the final words), unless "Keep the overlay on screen when not dictating" is on; the tray icon covers the rest of the time. - Compact is a microphone pill; full is a fixed-size box with the latest four lines. Neither resizes as words arrive, and a size change keeps the bottom edge where it was, so nothing jumps. - Drag it anywhere; the spot is remembered (OverlayAnchorX/Y, new app only) and kept on a connected screen. Settings can move it back. - A close button hides it until the next dictation; a notice still shows. - No longer click-through (so it can be dragged and closed), still never activated, so the caret stays in the app being dictated into. - A notice is no longer cut short by the final words' 1.5 s linger. Settings window: never taller than the screen's working area; the form scrolls with Save and the status line kept in view; long labels wrap; inputs sit under their labels; the model download row keeps its buttons inside the window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dictation on Windows: open the microphone, steady overlay, settings that fit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CakeRepository
marked this pull request as ready for review
September 30, 2026 04:08
CakeRepository
added a commit
that referenced
this pull request
Sep 30, 2026
Its base, the old work branch, was merged into main (PR #3). Merged cleanly; the probe tool builds with 0 warnings and all 304 Core tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Justin · project thread
Before: PrimeDictate is a .NET 8 WPF app with no shared transcription core, no cross-platform shell, and no record of how a Nemotron runtime would fit.
After: the Windows app targets .NET 10, a portable
PrimeDictate.Corelibrary holds the transcript model, session storage, settings, and scheduling, and spike projects show an Avalonia shell and macOS/Linux audio capture running on Linux. Dictation behavior is meant to be unchanged.This branch carries stages 1 to 3 of the transcription-mode work as separate commits. It can be split into stacked PRs if that is easier to review.
Commits
net10.0-windows, pin the SDK inglobal.json, point CI at it. Windows CI is green on this stage.src/PrimeDictate.Core(audio frames and resampler, versioned transcript model and events, SQLite session store, separate transcription settings with migrations, microphone and model coordination, provider and model-registry contracts) withtests/PrimeDictate.Core.Tests. The app gains structured timed results beside the unchanged string path.src/PrimeDictate.Platforms(miniaudio capture),src/PrimeDictate.Desktop(Avalonia shell), anddocs/architecture/stage-3-spikes.mdwith measurements and decisions.docs/architecture/nemotron-spike-local.mdlists the steps left to run Nemotron.Verified (Linux x64 only): 51 Core tests pass; the whole solution builds with 0 warnings; Windows self-contained publishes for x64 and ARM64 succeed; the Avalonia shell renders under X11; a clip played into PulseAudio was captured, resampled, and transcribed correctly by the existing Whisper ONNX engine.
Not verified: anything running on Windows, macOS, or a real microphone; Wayland; any Nemotron inference (model downloads were blocked); an MSI upgrade install over 5.1.0; a Windows smoke test of SharpHook, Whisper.net, and sherpa-onnx, which now load their
net10.0builds. Nothing here is production-ready. No UI uses Core yet.🤖 Generated with Claude Code
https://claude.ai/code/session_01GUzvd3VYM6n8xTXtBdDt5f