What can the user do, how do they reach it, and what would prove it works? This maps existing behavior. ROADMAP.md owns future work; AGENTS.md owns implementation invariants.
Initial source baseline: September 1, 2026, 9e9da53aa9ed. Public macOS release:
2.1.20, with custom-domain downloads, a GitHub DMG mirror,
and model-download recovery guidance. The initial map was source-only. Listed checks are
locators unless an executed result is explicitly recorded, as in the
keyboard-layout verification.
HEX // macOS release
├── Get ready // install, permissions, local model
├── Dictate // hold OR double-tap lock
├── Use the result // paste, Send, paste-last, History
├── Adjust behavior // shortcut, microphone, recording preferences
├── Process text // context-selected Modes
├── Opt in // Voice Action and Commands: separately off
├── Recover // prove the next attempt works
└── Maintain the app // menu bar, login, updates, quitDictation, Voice Commands, Voice Action, and Recovery have detailed maps. The other capabilities stay here until they need their own verification recipe.
Install the signed macOS app with brew install --cask anomalyco/tap/hex or the
manual DMG. The cask uses a versioned, checksum-pinned
DMG, requires Apple silicon/macOS 15+, and keeps Sparkle updates enabled. It does
not require Bun or OpenCode, launch the app automatically, migrate the legacy
Swift app, or forcibly replace an existing app destination. Homebrew uninstall
leaves runtime settings, models, and retained data intact.
Download Hex // setup.download-recovery
├── Website / README / Homebrew -> downloads.hex.kitlangton.dev -> R2 bytes
├── Alternative download: GitHub -> app-v<version> -> identical signed DMG
└── Older app cannot reach its update feed -> manually install the DMG
Prepare local models // separate network dependency
├── Dictation -> huggingface.co and its download CDN
└── Commands -> download.moonshine.ai
└── Transfer fails -> host-specific guidance -> restore access -> RetryThe website shows the pinned macOS version and platform requirements beside its
download links. App mirrors use explicit app-v… release tags rather than GitHub's
repository-wide latest release, which may refer to the SDK. The legacy r2.dev
endpoint remains available for installed clients. New builds use the custom host
for Sparkle and Linux updates; the signed Linux payload and verification key are
unchanged. Hosting does not guarantee access through every corporate policy.
The .dev site's decorative wind-up teeth have feet and hop around desktop
margins, or above the copy on narrow screens. A warm spotlight fades in first
(1.4–2.4 seconds). After three seconds hidden, the toy hops in from the left to
the center above the headline; the light fades as roaming begins. The canvas passes clicks through;
the website has no microphone button or microphone access. Reduced motion reveals
still light and toy poses at their respective delays. Rounded teeth, teal irises,
enamel, lacquer, and metal materials use local softbox reflections and soft shadows.
Its Open Graph image uses the same model; see
site authoring. Desktop/mobile animation frames were
inspected, and reduced-motion canvas captures were pixel-identical four seconds
apart in Chromium. These checks do not exercise a live microphone.
Sources: website, mirror publisher, macOS release publisher, Linux installer, and Linux updater. The macOS publisher verifies the downloaded R2 artifacts and GitHub mirror before publishing the appcast. Model transfer failures in transcription_models.rs and moonshine.rs explain the source host, retry, and separate prerequisites. These messages do not diagnose a particular firewall or establish a successful subsequent model download. Executed hosting checks and deployment details are recorded in Download Hosting. In-app feed defaults and recovery copy ship with 2.1.17; the initial 2.1.16 mirror preserves that release's original bytes.
Launch Hex // setup
-> Grant Microphone / Input Monitoring / Accessibility
-> Choose language and local model
-> Ready for ordinary dictation // no Bun, OpenCode, or Commands required
Settings > Local transcription
-> Prepare candidate
├── Success -> Save selection
└── Failure -> Preserve previous selection and show error
Permissions revoked later
-> Settings shows the missing access and repair actionApple silicon, macOS 15+. Choices come from the compiled model/language catalog; Apple Speech is currently excluded from the desktop picker.
Choose a supported language // setup.language-conditioning; macOS + Linux picker
├── Parakeet v3 -> Detect language from audio; selected language does not constrain output
└── Whisper -> Use selected language to guide decoding; accuracy is not guaranteedParakeet v3 remains available for Portuguese and its other existing language
choices. Its card explains the limitation and suggests Whisper for
language-guided transcription. This does not change saved selections or expand
the Auto picker choices. Multilingual audio support is not language conditioning:
the pinned v3 GGUF has no stt.parakeet.prompt.num_prompts metadata, which gates
language prompting in transcribe-cpp-sys 0.1.3.
Sources: model catalog,
shared picker, and the runtime
options in parakeet.rs and
linux_transcriber.rs. The regression
parakeet_v3_supports_portuguese_without_language_conditioning checks that the
Portuguese selection remains valid and available, sends no runtime language
hint, and contrasts Whisper's pt hint. It does not run inference.
The regression was observed failing with the old metadata and passing after the
correction. The macOS release picker preview built, but screenshot capture was
blocked by missing Screen Recording access; visual layout remains unverified.
Unreproduced recognition report: #68 reports Portuguese speech misrecognized as English. Source and pinned-model metadata inspection establish the prompting limitation, not the cause or repair of those transcripts. No v3 audio reproduction or transcription-quality fix has been demonstrated.
Checks start in onboarding.rs, transcription.rs, and transcription_models.rs. Setup/picker previews prove rendering, not clean-account permission grants or model preparation.
Menu bar > Transcription Model // setup.quick-model-switch; macOS
├── Downloaded models -> Last selected language shown beside each model
├── Choose -> Verify installed artifact -> Prewarm -> Save selection
│ ├── Success -> Checkmark moves; Settings reflects the saved selection
│ └── Failure -> Previous selection remains; open Settings for the error
├── Preparing -> Cancel Model Switch // never cancels dictation/output jobs
└── Manage Models… -> Settings picker // downloads and language changesThe menu does not open Settings merely to switch models, download a missing model,
or replace a corrupt artifact. Each model retains one last-used language/hints
selection in transcription_recents; the current selection takes precedence.
Older settings start with no remembered alternatives. Select an alternative's
language in Settings once to establish it. No additional model is loaded at startup
just to populate the menu, and the checkmark means selected, not live-worker readiness.
transcription_preparation.rs is shared by Settings and the menu bar: one worker plus one replaceable pending choice. Closing Settings does not abandon preparation. A superseded/cancelled completion cannot save, and delivered menu controls are drained before accepting completions. The desktop root commits through an existing editor to retain unrelated unsaved settings, or through persisted settings when no window exists.
Sources: status_item.rs,
meeting_watcher.rs, and
app_window.rs. Key checks are
preparation_has_one_worker_and_only_keeps_the_latest_pending_choice,
cancelling_a_completed_unpolled_preparation_never_commits_it,
model_switches_remember_language_and_hints_without_changing_other_settings, and
failed_model_selection_save_preserves_selection_recents_and_unsaved_edits in
the preparation owner and app_settings.rs.
Additional checks cover worker failure/retry, installed-only checksum verification,
and menu choice/tag projection. These do not establish a physical menu click or
an installed-app switch. Remembering a language does not change the language
conditioning limitations described above.
Observed September 4, 2026 (2.1.16): all 455 Rust tests passed in debug and release, with ten opt-in tests skipped per profile. All twelve keyboard-layout child scenarios passed per profile; formatting, strict all-target/all-feature Clippy in both profiles, and the release build passed. The Cohere fixture inference check was run separately; see dictation model windows. Native menu interaction, installed-app switching, and Linux runtime behavior were not exercised.
Known gap: is_installed checks size, not checksum. Not every native-load
path requires verification. Picker Active means selected/installed, not live
worker readiness; a later worker-load failure does not roll back saved selection.
Saved shortcut // dictation; Option by default
├── Hold -> Speak -> Release
├── Tap, tap -> Speak hands-free -> Press again // Double-tap to lock enabled
└── Escape
├── Capture active -> Cancel that capture
└── Otherwise -> Cancel newest cancellable job
Settings > Double-tap only // optional for key bindings
-> Disable single-hold activation
-> Keep the double-tap hands-free pathThe detailed map covers timing, successive captures, output
commitment, named checks, and native-proof gaps. Optional
spoken entry and controls belong to Commands.
The macOS CLI entry is hex listen, with an optional authoritative --device.
Do not start it beside an existing listener.
Completed output // output
├── Paste -> Current focus, in submission order
├── Spoken Send -> Paste, then Enter // requires Commands and voice capture
├── Paste Last Dictation // menu or Option-Shift-V / saved binding
│ └── Last completed dictation, not Voice Action
└── History
├── Inspect / search / copy / delete
├── Retention -> Seven days by default, always hard-capped
├── Off -> Stop new entries; preserve existing entries
└── Clear all -> Explicit deletionChecks in paste.rs, parakeet.rs, and history.rs cover output and retention boundaries. They do not prove that an arbitrary target consumed its clipboard contents. Send is not an atomic target-app transaction.
Easy to misread: History Off does not stop diagnostic logging. Diagnostic logs are separate and currently unbounded on disk. See the privacy guide.
Settings // settings
├── Dictation shortcut -> Capture a replacement binding
├── Microphone -> Automatic or saved device
├── Microphone mode
│ ├── Keep ready (fast) -> Open while idle; pre-roll available
│ └── Release when idle -> Open on press; no pre-roll; startup delay
│ └── Commands enabled? -> Confirm turning Commands off
├── While dictating -> Mute / Pause media / Do nothing
│ └── Intentional capture only, not ordinary shortcut chords
└── Sound volume -> Immediate feedback setting; zero suppresses tones
Intentional recording // recording.environment
├── Acquire native macOS no-idle-sleep assertion
│ └── Failure -> Warn and continue recording
└── Finish/cancel -> Release the assertion
// no caffeinate child process or executable-policy dialogSuccessful settings saves persist and project changes at safe runtime boundaries. Persistence, conflict, and ownership checks live in app_settings.rs, recording_environment.rs, and audio.rs. Settings previews do not prove physical device switching or native mute support; muting is best-effort, not universal. The recording-environment ownership checks cover overlapping acquisition and release, not a managed-device policy or native power assertion. An opt-in native assertion smoke exercises IOKit without starting microphone capture.
Easy to misread: an open microphone is not an active recording. Sleeping Commands still needs open input; it is not Release when idle.
Modes // modes
├── Select a row -> Edit that mode // does NOT manually activate it
└── Foreground context -> Select processing
├── Matching Brave website
├── Otherwise matching application
└── Otherwise Global
Selected mode // replaces Global, does not layer over it
-> Corrections
-> OpenCode transformation // optional provider-backed rewrite
-> Transformations // ordered text chain
-> Paste or Send
Stage fails -> Preserve its input // failed chain discards partial resultsBuilt-in text transformations run natively. Custom transformations require the managed Bun workspace, not Commands enabled. OpenCode rewriting sends documented context to the configured provider; speech transcription stays local.
The built-ins are Lowercase, SpongeBob case, and No trailing punctuation. The
last removes only the final contiguous run of sentence marks (including
full-width marks), preserving internal punctuation, trailing whitespace, and
closing quotes/brackets. Punctuation inside a final closing quote is preserved.
Select it after Lowercase for lowercase messages without the final period.
The no_trailing_punctuation_* and built_in_transformations_chain_in_selected_order
tests in personal_commands.rs cover text behavior
and native dispatch, not a live custom-host or target-app paste.
Selection and stage checks start in dictation_processor.rs, text_replacements.rs, and personal_commands.rs. They do not prove live provider availability or real application/Brave context changes.
Application activations compare the picker's bundle name with the foreground
application's localized name. When Finder shows all filename extensions, the
picker name arrives as Ghostty.app; context.rs strips
that suffix on both sides and app_settings.rs
rewrites already persisted activations on load, checked by
application_matching_ignores_finder_bundle_extensions and
loading_strips_finder_bundle_extensions_from_mode_applications.
Fixed in 2.1.14: through 2.1.13, a mode saved with that Finder
preference enabled never activates and every dictation falls back to Global.
Update to repair saved selections automatically. For older builds, the workaround
is to quit HEX, remove the .app suffix from
dictation_processing.modes[].applications in settings.json, then relaunch.
Voice Action maps the separate opt-in, hold-only shortcut, optional selection, local transcript-to-OpenCode-to-paste path, and cancellation boundary. It owns the mode-bypass/last-result rules, checks, and cold modifier-order gap.
Voice Commands maps the command opt-in and separate Moonshine model, awake/sleep state, contextual catalog, personal config, and spoken dictation controls. It owns the checks and host/startup gaps; actual capture and output remain in Dictation.
Cancellation / interruption / failure // recovery
-> Scoped stop, fallback, or error
-> Retry or fresh gesture
-> Next capture succeeds // this is the proof, not a cleared error
Menu bar and Settings // maintenance
├── Settings / Paste Last -> Reachable with Dock icon hidden
├── Launch at Login -> macOS owns registration state
├── Check for Updates -> Signed packaged-app Sparkle lifecycle
└── Quit -> Orderly shutdownLaunch Hex // maintain.startup
├── Setup incomplete / permissions or model missing -> Open setup or Settings
├── Show Dock icon on -> Open Settings
└── Show Dock icon off + usable menu-bar item -> No startup window
├── Dictation starts independently of the Settings window
└── Menu-bar Settings / Finder or Spotlight reopen -> Open or focus the window
Menu-bar installation fails -> Show Dock icon and open the window // recovery accessThe existing Show Dock icon preference controls quiet startup; there is no additional launch-window setting. This applies to normal and login launches, does not change login registration, and does not hide an already open window when the preference changes. Explicit previews always open their requested pane.
Checks in meeting_watcher.rs:
dockless_startup_stays_quiet_only_when_setup_and_menu_bar_are_ready and
dock_visible_startup_always_opens_the_app cover the startup decision. The
existing on_reopen and status-item OpenSettings paths bypass that decision.
These checks do not establish signed-app login behavior or native Finder/Spotlight
reopening; those still need an installed-app smoke test. This addresses the
menu-bar-only case in #64, not an
independent window preference for users keeping the Dock icon visible.
Executed September 3, 2026: 441 Rust tests passed (nine opt-in tests ignored), along with all twelve keyboard-layout child scenarios, formatting, and Clippy. The release Settings preview built and launched, but window screenshot capture failed; no visual or installed-app startup verification is claimed.
Update available // maintain.updates
├── Sidebar Update -> Sparkle update dialog
└── Menu bar > Check for Updates -> Same dialog
Remind Me Later -> Dismiss this offer -> Manual update -> Reopen the offerThe sidebar uses window-local deferred action dispatch in
app_window.rs. The executed
sidebar_update_click_dispatches_only_outside_preview regression clicks the
production control in an active in-memory GPUI window: zero actions before the
fix, one afterward, and no action in a preview. It does not start Sparkle.
Fixed in 2.1.13: older releases through 2.1.12 used app-global dispatch from inside the sidebar click, losing the action while the active window was borrowed. The click regression passed in debug and optimized release builds. The menu-bar path was exercised on the installed local 2.1.11 build and opened the native Software Update window; no installation was performed. That path remains the workaround for older versions, including after Remind Me Later. A repaired installed-sidebar click remains unverified.
Release 3675206, build 20113, was signed, notarized, stapled, and accepted by
Gatekeeper. DMG and ZIP payloads matched across 207 entries. Public DMG, ZIP, and
latest-DMG downloads matched their prepared SHA-256 checksums; the public feed
led with 20113, and its ZIP signature verified against the app's public key.
Published September 3, 2026: 2.1.14, release commit
cc7f843, build 20114. The combined release passed 442 Rust tests (nine opt-in
tests ignored), all twelve keyboard-layout scenarios in debug and release,
46 command-SDK tests, Clippy, formatting, and app identity guards. The first
optimized harness attempt exceeded the command timeout during compilation;
the rerun completed and all twelve scenarios passed.
The app and DMG were Developer ID signed, notarized, and stapled; Gatekeeper
accepted both. DMG and ZIP app payloads matched across 207 entries, including
file bytes, permissions, and symlink targets. After artifact-first/feed-last
publication, public DMG, ZIP, and latest-DMG downloads matched the prepared
SHA-256 hashes. The public feed led with 20114, and its ZIP signature verified
against the app's public key. The publication script's recursive diff emitted
framework directory-loop warnings; the separate no-follow manifest comparison
verified the complete payloads without traversing symlinks.
The installed app and live settings were not modified. The live quiet-startup, Finder/Spotlight reopen, and physical dictation smoke test was explicitly waived for this release; those paths remain unverified on the installed candidate. No Sparkle installation or Linux binary release was performed.
Published September 3, 2026: 2.1.15, release commit
9ec1b51, build 20115. The current-version checks passed 444 Rust tests in each
of debug and release (nine opt-in tests ignored per profile), all twelve
keyboard-layout scenarios in each profile, 46 command-SDK tests, strict Clippy in
both profiles, formatting, and app identity guards.
The app and DMG were Developer ID signed, notarized, and stapled; Gatekeeper
accepted both. A no-follow manifest comparison matched all 208 entries including
the app root, file bytes, modes, and symlink targets across DMG and ZIP. After
artifact-first/feed-last publication, public DMG, ZIP, and latest-DMG downloads
matched their prepared SHA-256 hashes. The public feed led with 20115, and its
ZIP signature verified against the app's public key. The existing recursive-diff
warnings in the publication script remain; the separate no-follow check passed.
Homebrew cask PR #16 passed style, strict online audit, Sparkle livecheck, and checksum-verified download. Installation and uninstall succeeded with an isolated temporary application directory. The installed candidate passed identity, signature, stapled-ticket, and Gatekeeper checks. An existing-app fixture was refused without replacement; the real installed app's Info.plist and executable hashes were unchanged.
Screen Recording preflight reported unavailable, so no screenshot is claimed. The installed app was not replaced or launched, and no physical dictation, Sparkle installation, or Linux binary release was performed.
Published September 4, 2026: 2.1.16, release commit
76dc552, build 20116. Both profiles passed 455 Rust tests and all twelve
keyboard-layout scenarios, with ten opt-in tests skipped per profile. The real
Cohere synthetic-audio regression passed separately. Both profiles passed strict
Clippy; formatting, app-identity fixtures, 46 command-SDK tests, and 45 public-SDK
tests passed, along with SDK typechecks and the public SDK build.
The app and DMG were Developer ID signed, notarized, stapled, and accepted by
Gatekeeper. The DMG and Sparkle ZIP matched across 208 no-follow payload entries.
After artifact-first/feed-last publication, fresh public DMG, ZIP, and latest-DMG
downloads matched the prepared artifacts byte-for-byte; the public feed led with
20116, and its Ed25519 signature verified against the app's public key. The
existing recursive-diff framework-loop warnings were independently covered by
the no-follow comparison.
Homebrew cask PR #17 updated the version/checksum and was merged after style, strict online audit, livecheck, checksum fetch, isolated installation/uninstallation, app identity/signature, stapled-ticket, and Gatekeeper checks. An existing-app fixture was refused without replacement. The test cask was removed; the real installed app's Info.plist and executable hashes were unchanged. Release replies were posted and #73 and #74 were closed after publication.
Screen Recording preflight remains unavailable. This release has no physical
menu-click, installed-app model switch, microphone/paste, or Sparkle installation
proof. No Linux binary is published. The
Linux/Nix CI run
passed for release commit 76dc552: the Linux job completed in 3m50s and Nix in
40m10s. This includes isolated X11 grabs, Wayland paste into GTK, virtual-microphone
capture/inference/paste, signed-installer tamper rejection, and the installed Nix
package checks. These CI checks remain separate from native macOS and physical
Linux verification.
The performance investigation changed
no runtime defaults.
Published September 14, 2026: 2.1.18, release commit
62b055e, build 20118. The optimized suite passed 468 Rust tests and all twelve
keyboard-layout child scenarios, with ten opt-in tests skipped. Strict release
Clippy and app-identity fixtures passed. The signed bundle's isolated HUD preview
was visible over native fullscreen TextEdit; no microphone capture or installed
app replacement was performed.
Apple accepted the app and DMG for notarization; both were stapled. Gatekeeper
accepted the candidate. DMG, Sparkle ZIP, and tested bundle matched across 208
no-follow entries (bytes, modes, and symlink targets). Public DMG, ZIP, latest-DMG,
and GitHub mirror bytes matched the prepared artifacts. The public feed matches
the prepared feed and leads with 20118; its ZIP signature verified against the
bundled public key. Publication resumed at the feed upload after a server restart,
following fresh verification of all uploaded artifacts.
Both production marketing sites show the versioned 2.1.18 links. Their GitHub
download buttons completed downloads in headless Chromium with the primary host
blocked. The Homebrew cask passed style, strict online audit, and checksum fetch.
The separate .com site's lint, typecheck, and build passed; its dependency audit
reported four existing development-tool advisories, tracked separately.
The Ubuntu CI run passed Rust tests, isolated X11 grabs, and native Wayland paste,
then exposed a virtual-microphone fixture mismatch: its partial saved settings
selected legacy Ctrl-Shift-V while the GTK target expects Ctrl-V. The fixture now
sets paste_with_shift:false explicitly. The failed target check remains a failed
run, not native paste proof; see the subsequent CI result for verification.
Subsequent CI passed: run 34927405408
on cd3a8d9 passed both Ubuntu (4m15s) and Nix (39m58s). This includes the
corrected virtual-microphone capture/inference/paste fixture, X11 grabs, Wayland
paste into GTK, signed-installer tamper rejection, service IPC/lifecycle, strict
Linux Clippy, and the installed Nix package build and tests. These isolated checks
do not establish physical Linux device or compositor compatibility.
Published September 18, 2026: 2.1.19, release commit
983c71c, build 20119. The debug and optimized suites each passed 468 Rust
tests plus all twelve keyboard-layout child scenarios, with ten opt-in tests
skipped per profile. Strict all-target/all-feature Clippy, formatting, app
identity guards, and the release build passed.
Apple accepted the app and DMG for notarization; both were stapled, and
Gatekeeper accepted the DMG. The public versioned DMG, latest-DMG pointer, and
GitHub mirror match the prepared SHA-256. The feed leads with build 20119, and
the published ZIP is the signed prepared update. The isolated memory probe in
Dictation establishes scratch reclamation
with the similarly sized Unified English model; it is not a Parakeet v3 or
installed-app memory measurement. No installed app replacement, physical
dictation, Sparkle installation, or Linux binary release was performed.
Both production marketing sites and their Vercel aliases show the versioned
2.1.19 primary and GitHub downloads. The .dev site build and Cloudflare deploy
passed. The .com site's lint, typecheck, build, and Vercel deployment passed;
its dependency audit reported the same four development-tool advisories already
present in 2.1.18. The Homebrew cask passed Ruby syntax, style, strict online
audit apart from the explicit existing token collision, checksum fetch, and
livecheck (2.1.19 ==> 2.1.19). No cask installation replaced the live app.
Published September 19, 2026: 2.1.20, release commit
4d4734c, build 20120. The debug and optimized suites each passed 468 Rust
tests plus all twelve keyboard-layout child scenarios, with eleven opt-in tests
skipped per profile. Strict all-target/all-feature Clippy, formatting, the
native IOKit assertion smoke, app validation, and the release build passed.
Apple accepted the app and DMG for notarization; both were stapled, and
Gatekeeper accepted the DMG. The versioned custom-domain DMG, latest-DMG pointer,
and GitHub mirror match the prepared SHA-256
fdd66e297c66178a1a0cfa2e19f5b0a5674135d202481b1aeb84d4d1268167af.
The published Sparkle ZIP matches the prepared SHA-256
a468575ac037b632cc78d479df7430f083748873dd5c71e576ce23be1197917b,
and the feed leads with build 20120.
Both production marketing sites and their Vercel aliases show the versioned
2.1.20 primary and GitHub downloads. The .dev site build and Cloudflare deploy
passed; the .com site's lint, typecheck, build, and Vercel deployment passed.
The Homebrew cask passed Ruby syntax, style, strict online audit, checksum fetch,
and livecheck (2.1.20 ==> 2.1.20). No installed app or cask was replaced, no
physical dictation was performed, and no Linux binary was published.
Recovery separates supported recovery from known defects. login_item.rs, status_item.rs, sparkle.rs, the identity test, and app validation are maintenance evidence starting points. Preview badges do not prove installed-app login registration or a signed update from an older supported build.
Linux beta // not macOS feature parity
├── hex start -> Per-user service -> Alt-Space capture -> Transcribe -> Paste
├── hex app -> Settings client; closing it leaves the runtime running
├── Recording sounds -> Shortcut press / capture stop / active cancellation
├── X11 -> No tray or recording HUD; service status and sounds remain available
├── Wayland -> evdev + compositor protocols; keys observed, not suppressed
│ └── Mouse-classified nodes excluded; live modifiers reused for paste
├── Escape -> Cancel active capture, not newest accepted job
├── Microphone failure -> Listener exits, not macOS automatic recovery
└── Paste -> Retain transcript clipboard, not restore previous contentsNo Linux Commands, Voice Action, Modes processing, retained History, or meetings.
hex listen instead prints Moonshine transcripts. Wayland needs explicit broad
input-device access; physical reconnect and click-through still need native
evidence. Its smoke explicitly isolates
HEX_APPLICATION_SUPPORT_DIR and checks that Settings can exit without stopping
the service.
Wayland discovery checks every event node for the required broad read access,
then excludes udev mouse-classified nodes even when Logitech-style receivers
advertise synthetic keyboard keys. Stable rejected nodes are cached until their
device identity changes. The active keyboard monitor publishes aggregate
modifier state to paste, avoiding two full device scans after every dictation.
mouse_nodes_are_not_monitored_even_when_the_receiver_advertises_keyboard_keys
and live_modifier_snapshot_tracks_edges_and_device_loss cover those policies;
the nested-compositor smoke remains the native insertion check.
See the Linux guide, linux_app.rs, linux_wayland_input.rs, and Linux CI for existing checks. The direct installer/updater and installer tests do not establish a published, supported-host-validated signed release. Nix owns its package updates; HEX updates only managed direct installs.
Linux runtime // maintain.linux-service
├── hex start / desktop autostart -> systemd --user hex.service; no Settings or tray
├── Settings / CLI -> Same-user, owner-only Unix socket -> Typed commands and snapshots
├── Close/crash Settings -> Keep microphone, accepted jobs, and model preparation
│ └── Uncommitted shortcut capture -> Cancel and restore the prior listening state
├── Service restart -> Client reconnects; uncertain commands are not replayed
└── hex stop / systemctl --user stop hex -> Stop workers and release devicesSources: the service owner and client in linux_app.rs,
linux_service.rs, and the managed installer/Nix
user units. hex status queries the running service; --lines explicitly reads
historical observations. hex app --hidden is a compatibility alias for starting
the service. GUI exit does not mean Stop Listening; the explicit listener and
service controls retain separate meanings. No root daemon or new input grants.
Checks: test-linux-service.py exercises
real IPC and process lifetime without a display, installed model, or audio device.
closing_a_client_does_not_stop_normal_dictation and
client_disconnect_cancels_its_edit_and_restores_previous_listening use controlled
runtime workers. The virtual-microphone and Wayland scripts now run separate
service/client processes. These checks do not establish a physical desktop logout,
cross-version signed update, or microphone/target-app success on the user's devices.
Observed September 4, 2026, local service build: 139 Rust tests passed (seven
native/opt-in tests skipped), together with strict Linux bin/tests Clippy, the
release build, the release-binary IPC/lifecycle script, and installer fixtures.
The managed Arch/i3 installation passed systemd-analyze --user verify after
correcting the unit's EnvironmentFile syntax. Its environment file cleared the
user manager's stale Wayland values; the service reported X11 and Listening.
The installed executable SHA-256 was
cbfa91894c5b592254da41a3d2791f9461e5995abdac3da90a2639aefe3e52f1.
The installed Settings client had no audio handles or runtime locks. Closing its
window left the same systemd service PID running and Listening. The screenshot
at /tmp/opencode/hex-service-settings.png also retained an empty-transcript
failure; it was not dismissed to make the lifecycle check pass. This validates
client-independent ownership, not transcription accuracy. The modified virtual
microphone/Wayland smokes, Nix evaluation, physical logout, and signed
cross-version service updates were not run on this host.
Linux Settings > Sound volume // dictate.feedback.linux; X11 + Wayland
├── Default / older settings -> 50%
├── Off -> No recording tones
└── 25% / 50% / 75% / 100% -> Save and apply live; preview the start sound
Capture // hex app and hex dictate
├── Shortcut starts capture -> Queue start sound immediately, before audio arrives
├── Finish retained recording -> Stop sound, before transcription/paste
├── Brief discarded tap -> Start sound, but no stop sound or transcription
└── Cancel active capture -> Cancel sound; idle Escape stays silentThe shared feedback player uses the bundled recording
sounds on both platforms; only macOS wake/sleep/error tones use afplay.
Decoding and output initialization happen before the Linux capture loop, and
playback admission is bounded and nonblocking. Output preparation failures are
logged without blocking dictation. Sound-volume saves in
linux_app.rs preserve listener ownership; failed saves
leave the previous selection active, and shortcut/model edits temporarily disable
the control to prevent overlapping settings writes. The start sound does not wait
for the 300 ms capture-retention threshold; input delivery and audio-device
latency still apply.
Checks: bundled_recording_sounds_decode_without_an_audio_device and
feedback_admission_never_waits_for_playback in the player, plus
recording_volume_defaults_for_new_and_legacy_settings_and_round_trips and
invalid_sound_volume_is_rejected_before_saving in
linux_settings.rs. The desktop host checks
invalid_volume_preserves_settings_and_does_not_stop_the_listener and
volume_changes_cannot_overwrite_an_in_flight_settings_edit cover invalid-volume
and overlapping-edit guards, not real disk failures.
start_sound_precedes_audio_and_does_not_change_short_tap_discard in
linux_dictation.rs exercises the production start
path with a sound spy: immediate feedback without any audio, while a 100 ms tap
still discards. Existing timing checks in dictation.rs
cover the unchanged intentional-hold boundary. These are not proof of audible
native playback or physical hotkey-to-sound timing.
Observed September 4, 2026, initial Linux sound build (before immediate-start
feedback): 134 Rust tests passed
(seven native/opt-in tests skipped), along with formatting, strict Linux
bin/tests Clippy, and the release build. The locally modified 4ff6c4c build
(executable SHA-256 609f16cac11eb66cea7054092e7c4caee3f02c3bd1b8828f5232cf3b2b67df31)
was installed and launched with hex app on Arch/i3/X11. It reached Listening
and opened a PipeWire ALSA playback stream; existing settings were unchanged.
The installed Settings capture at /tmp/opencode/hex-linux-sounds.png showed
the new volume control with 50% selected. Actual tone audibility, physical
hotkey-to-sound timing, Wayland UI, and macOS regressions were not exercised.
Immediate-start refinement, same day: 135 Rust tests passed (seven skipped),
including the new start-before-audio regression. Formatting, strict Linux
bin/tests Clippy, and the release build passed. The replacement executable
(SHA-256 470be7569cf9fa8be60e73554c8311df2b68487b221cefffc7cfd26cb7e11629)
was reinstalled and restarted on the same Arch/i3/X11 host and reached Listening.
There is no measured native key-to-sound latency or additional macOS/Wayland proof.
Both Linux CI triggers include tests/**, and the
Nix source fileset includes those sources. The
keyboard_layout harness skips on Linux; packaging it does not provide macOS
layout evidence.
TypeScript SDK + macOS local service
├── Caller records audio -> Explicit helper -> Raw transcription
├── Running desktop capture API -> Separate microphone ownership protocol
└── Distribution
├── SDK 0.3.0 -> Published
└── Turnkey signed helper -> UnfinishedThe SDK guide, service contract, and SDK tests distinguish those paths. Helper-only service mode is not a physical hotkey/microphone test. Embedded/packaged service smokes still expect API 1 while service/SDK require API 2. Low-level blocking-peek and FIN/half-close limitations in local_api.rs must not be generalized to every SDK cancellation path.
macOS debug runtime // not release navigation
├── Meetings -> Explicit recording, separate artifacts, recoverable drafts
│ └── Detection may offer recording, never start it automatically
└── Activity / HUD Lab / hex dev -> Observation and developer controls
Desktop previews -> Rendered fixtures // not proof capture/download/update happened
iOS prototype -> Record/transcribe/copy and keyboard handoff
Unmerged Windows / deferred features -> Not part of this mapSources: meeting.rs, meeting_watcher.rs, developer_control.rs, capture-preview.sh, iOS guide. iOS physical-device proof and temporary-WAV failure cleanup remain gaps; best-effort deletion is not guaranteed deletion.
default_paste_hotkeys_follow_build_capabilities in
app_settings.rs checks the production default runtime
and settings projections: Paste Last remains available in both build profiles;
meeting paste is present only in debug. meeting_paste_is_only_active_in_developer_builds
in suppression.rs checks the corresponding constructed
hotkey event, including ordinary-chord discard in release. These replace an
unconditional debug-only expectation and a test-only copy of the reservation rule;
they do not establish native keyboard delivery or meeting insertion.
structural_event_equality_preserves_serialized_identity in
events.rs covers Activity's full-event equality across
event variants, equal timestamps, non-ASCII text, and optional/error payloads.
It does not exercise native Activity selection or scrolling.
Executed September 3, 2026, source simplification pass: 444 Rust tests passed in both debug and release (nine opt-in tests ignored in each), with all twelve keyboard-layout child scenarios passing in each profile. Formatting and strict Clippy passed in both profiles. These checks cover the working-tree cleanup, not a new published app; no installed-app, microphone, or native Linux test was run.
One feature file
├── Sub-features -> Small action/outcome diagrams with stable IDs
├── Entry points -> Every user route and its prerequisites
├── Existing checks -> Source/test links and what they do not prove
└── Gotchas -> Exceptions, known defects, missing native proofThis is the map-only stage of the verification workflow, not a working driver. Keep checks beside the behavior and tests with the implementation. Split new files only when useful; link shared failure behavior to Recovery.
Future evidence should name the behavior ID, entry point, revision/binary, platform, prerequisites, outcome, and artifact location. Report skipped paths. Never publish tokens, personal transcripts, or unredacted diagnostics as proof. This map does not authorize operating a live installation.