Build HEX as a local, observable macOS voice appliance with explicit Linux X11 and wlroots-compatible Wayland beta contracts. Keep the engine native Rust and keep consequential behavior explicit. Protected commands and typed captures remain compiled Rust; ordinary literal commands live in the explicit TypeScript user config. User-facing runtime settings persist in Application Support.
The distributed release starts in hotkey-dictation-only mode. Voice commands and
their catalog remain available as a persisted opt-in that defaults off.
Voice Action also requires its own persisted opt-in, defaulting off even for
existing settings without an explicit enabled flag.
DEVELOPER_FEATURES_ENABLED keeps meetings and their UI/CLI surfaces available
only in debug builds.
docs/features/README.md indexes existing user-facing
capabilities, entry points, checks, and gaps. Before designing, changing,
debugging, or reviewing a user-facing feature, read the index and relevant
feature files, then check their referenced implementation. The map is not a
substitute for source inspection or a second roadmap.
- Update the affected map in the same change when behavior, entry points, defaults, prerequisites, platform availability, recovery, or verification coverage changes. Do not churn unrelated maps for internal-only refactors.
- Organize by user capability, not Rust module or UI pane. Dictation, Voice Commands, and Voice Action are distinct features; link shared flows and recovery rather than duplicating their descriptions. Keep the index current when adding or splitting a feature file.
- Prefer small action/outcome diagrams in
tsfences with Unicode tree characters, not tables. Preserve stable behavior IDs and keep source/check references and proof limits beside the behavior they explain. - Distinguish implemented behavior, available checks, and observed verification.
A test's existence is not a passing run, and a preview is not native end-to-end
proof. Record missing coverage and known defects explicitly; never rewrite an
expected outcome merely to hide a regression. A map does not authorize live
device tests. Keep future work in
ROADMAP.md.
audio:cpaldevice enumeration and timestamped mono float PCM delivery, live selection, and bounded stream recovery.moonshine: the only Moonshine C adapter and the streaming recognizer.suppression: the macOS event tap, shortcut suppression, and the configurable dictation-hotkey state machine.keyboard: active-layout key resolution and balanced synthetic shortcuts.dictation: warm pre-roll, growable capture, and 16 kHz local-transcription resampling.dictation_audio: the authoritative microphone timeline, lazy stream lifecycle, recording owner, exact shortcut boundaries, recovery handoff, and disposable bounded command audio projection.recording_environment: serialized RAII ownership of idle-sleep prevention, output muting, and supported media-player pause/resume behavior.dictation_processor: context-selected corrections and deadline-bounded OpenCode rewrite profiles with corrected-transcript fallback. The macOS app discovers theopencode2beta executable, links missing installs tohttps://v2.opencode.ai/, and usesopencode2 api getto discover or start its managed service. Generation uses CLI-managed discovery with the matching owner-only service registration and authenticated loopback HTTP through the system curl; request bodies and credentials use stdin, never argv or new temporary files.parakeet: the strict-Metaltranscribe.cppadapter plus bounded inference, processing, ordered output, paste, last-result, and meeting-delta workers.apple_speech: the SwiftSpeechAnalyzerbridge with per-locale support checks, asset reservation, and batch transcription.transcription: runtime selection and transactional warm-model activation.transcription_preparation: application-owned model preparation shared by Settings and the menu bar, with one worker, latest-choice pending admission, cancellation fences, and an installed-only path that never downloads a model.transcription_models: the compiled model catalog, language recommendations, pinned artifact verification, and lazy installation.transcription_service: bounded host-audio admission, hostile WAV validation, normalization, cancellation, and warm inference ownership.local_api: authenticated loopback discovery or direct-child endpoint handoff, bounded HTTP parsing, model preparation progress, and raw transcription routes.sdk/typescript: Promise and Effect v4 host wrappers for direct-child lifecycle, authenticated model preparation, and host-audio transcription.developer_control: the typed debug-only command/reply protocol the local API uses to drive the running app fromhex dev.paste: clipboard insertion, continuation joins, and generation-safe clipboard restoration.accessibility: bounded focused-window and selected-text capture through Accessibility without touching the clipboard.recognition: the semantic coordinator for Moonshine, command mode, hotkey and voice-delimited controls, workers, and observations. It does not own the authoritative microphone timeline.command_grammar: typed command patterns, captures, overlap detection, and command catalog metadata.commands: pure contextual resolution plus bounded asynchronous macOS action execution.personal_commands: the Bun-hosted@hex/commandsTypeScript workspace: managed SDK provisioning, the watch-reloaded user command and transformation host, bounded invocation dispatch, and status snapshots.text_replacements: phrase-boundary-aware compilation of configured text replacements applied longest-match-first to transcripts.config: compiled commands, preferred input devices, and dictation profiles.context: native foreground application and window context capture plus browser context. Browser host is the domain concept; Brave AppleScript is only the first adapter.application_catalog: cached installed-application discovery with normalized bundle identity and rendered icons for the UI.app_settings: persisted settings and live runtime projection for commands, hotkeys, microphone and transcription selection, recording behavior, processing, sound volume, and Dock policy.login_item: the nativeSMAppService.mainAppServiceadapter. macOS owns registration state; it is deliberately not duplicated insettings.json.app_paths: the Application Support owner for runtime logs and shared state.onboarding: required permission health, selected dictation-model installation, the release startup gate, and opt-in command-model setup.sparkle: packaged-app-only Sparkle lifecycle and manual update checks.linux,linux_app,linux_dictation,linux_input,linux_paste,linux_settings,linux_transcriber: the Linux beta CLI, GPUI shell, hotkey capture-transcribe-paste loop, persisted settings, andtranscribe.cpp.linux_session: display-backend selection matching GPUI's nonemptyWAYLAND_DISPLAYrule, independent of persisted preferences.linux_wayland_input: read-only evdev input, explicit physical key mappings, cancellable shortcut capture, exact chord state, and bounded device rediscovery.linux_service: owner-only, same-user Unix IPC, bounded service/client transport, and per-user systemd lifecycle. Settings and CLI are clients of one runtime.linux_desktop: the service-owned GTK thread for the focus-free, click-through Wayland recording/processing HUD. Settings never owns its lifecycle; Linux has no tray icon.linux_updater: signed direct-install updates, bounded downloads, atomic version activation, and restart handoff for user-local Linux installs.history: the owner-only bounded retained-dictation store: retention windows with hard entry and byte caps, atomic crash-safe persistence, and search. Text and bounded metadata only, never audio.events: bounded asynchronous append-only NDJSON observations and bounded incremental reading;dashboardand the GPUI Activity pane are read-only projections.desktop_activity: the shared listener, device, session, and latest-failure projection overEventReader. Transcript rows read retained events directly.desktop_host: semantic desktop capabilities, portable UI snapshots, and typed actions implemented by the macOS root and contained Linux adapter.desktop_ui: platform-neutral GPUI visual tokens and controls shared by both desktop roots, including the mandatory pane scaffold:pane_header/pane_header_with_action,pane_body,pane_content, the sharedheader_buttonaction chip, and the singlePANE_CONTENT_WIDTHandPANE_LIST_WIDTHlayout constants.text_input: the shared GPUI single- and multi-line text input with editing, selection, clipboard, and input-method support.desktop_transcription_picker: the single GPUI language/model picker used by both desktop roots over portable model presentation and platform preparation callbacks.app_window: the production Settings, Modes with mode-owned corrections and processing, Voice Action, History, and opt-in Commands shell plus developer-only Meetings, Activity, and HUD Lab panes.status_item: the persistent macOS menu-bar owner for Settings, Paste Last Dictation, update checks, and orderly application shutdown.dictation_indicator: the click-through Metal/GPUI capture and processing HUD.meeting: explicit ScreenCaptureKit capture, owner-only WAV and transcript artifacts, final local-model publication, and recovery.meeting_live: bounded dual-stream Moonshine draft transcription and byte-offset transcript tailing.microphone_activity: permission-light CoreAudio process input observation.meeting_detection: pure provider classification, debounce, and suppression.meeting_watcher: GPUI application lifecycle, recognition ownership, meeting offers, and explicit handoff into capture.instance: the exclusive Application Support lock that prevents concurrent command listeners.feedback: bounded volume-controlled mode, capture, cancellation, and failure tones.
Keep modules deep. Callers should not coordinate Moonshine stream handles, CoreAudio formats, AppleScript details, or event serialization.
- Every build starts dictation-ready without loading Moonshine or a command executor unless the persisted command opt-in is enabled. Enabling commands loads Moonshine off the audio-consumption loop; disabling them unloads the recognizer and executor.
- On a new Mac, release dictation starts only after Microphone, Input Monitoring, Accessibility, and the selected dictation model are ready. Opt-in command recognition additionally requires Moonshine, but its background preparation or failure must never block hotkey dictation. Failed preparation exposes explicit retry and disable actions without silently changing the command opt-in.
- Among recognized voice commands, sleeping mode accepts only standalone wake phrases. Dictation and explicit paste shortcuts remain available.
- Unmatched completed speech is ignored and logged.
- The dictation shortcut defaults to Option but supports modifier-only, modifier-plus-key, standalone Globe/Fn, and standalone function-key bindings. Capturing a new binding suspends global matching.
- GUI startup must successfully call
keyboard::initialize_layouton the OS main thread before settings, keyboard-using workers, or GPUI start. That snapshot is fixed until restart; cached misses must not re-enter TIS/TSM. Uncached headless lookups retain live-layout resolution, with all native lookup and snapshot construction serialized by the same lock. - Stale shortcut recovery requires at least 100 ms of sampled keyboard neutrality,
no pending input, and no active or pending gesture. It emits no capture actions.
Native
SecondaryFnnavigation metadata may be ignored only with timestamped modifier-change evidence that Fn is up; the full key scan and delayed-event fences remain required. Never strip Fn globally from shortcut matching. - Hold the shortcut to dictate and release to transcribe. Captures shorter than 300 ms discard. A 450 ms hotkey pre-roll and one-second voice-trigger pre-roll protect speech onset. Capture has no automatic duration limit; release, explicit stop, or Escape ends it.
- When enabled, a second shortcut tap within 300 ms locks dictation. Press the shortcut again to finish or Escape to cancel.
- When commands are enabled, every dictation or paste hotkey action resets Moonshine so shortcut audio cannot leak into a later command.
- Recording audio behavior and idle-sleep prevention begin only after the intentional-hold threshold. Ordinary shortcut chords must not mute output, pause media, or prevent sleep.
Release microphone while idleand Commands are mutually exclusive through explicit confirmed transitions and a shared runtime policy. When release is enabled, pressing the shortcut opens the selected device asynchronously with no pre-roll, preserves the physical press for the hold threshold, discards a release before readiness, and closes only after the authoritative capture is idle. Accepted jobs never keep the stream open.- Dictation remains available while command recognition sleeps.
- Model inference, optional post-processing, paste, and application actions must never block authoritative audio capture. Their queues remain bounded.
- CoreAudio capture timestamps and annotated-session CGEvent shortcut timestamps share the macOS boot-time nanosecond clock. Do not apply the Mach timebase to annotated-session timestamps. Physical HID-tap timestamps arrived as raw Mach ticks on the tested Apple Silicon system, while some HID events already used nanoseconds; changing tap location requires revalidating timestamp units. Delayed press handling reconstructs the original onset from the timeline; delayed release handling excludes audio captured after the physical release.
- Active dictation capture is lossless with respect to Moonshine, event, UI, context, and worker stalls. Command recognition is explicitly best-effort: its backlog is bounded by duration, and pressure invalidates the generation, discards stale audio and updates, and resets Moonshine without touching the active recording.
- Starting a new capture never cancels accepted dictation work. Pending jobs preserve submission-order output while capture remains immediately available.
- Completing or pasting an older job must not reconcile or finish a newer capture. Shortcut boundaries come from delivered CGEvent timestamps; do not fabricate a release timestamp from later physical state.
- Escape cancels the active capture first, then the newest unfinished dictation. Cancelled jobs never paste, update the last result, or block later output.
- Model switches activate only after the pinned artifact is checksum-verified, loaded on strict Metal, and prewarmed. A failed switch preserves the active model and persisted selection.
- The menu-bar model picker lists only downloaded, available models and remembers each model's last selected language and recognition hints. It works without a Settings window. Settings and the menu bar share one preparation owner; a newer choice supersedes uncommitted work, and preparation/save failures leave the old selection intact. Menu-bar selections never silently download or repair a model.
- Cohere's advertised encoder input capacity is not its recommended inference window. Both GGUF adapters cap Cohere chunks at the reference processor's 35 seconds (or a stricter advertised bound); capture duration remains unlimited.
- A selected microphone switches live only while capture is idle. Opening the
replacement must succeed before the old stream is dropped. If the persisted
device is unavailable at startup, log the failure and fall back through the
compiled preferred-device order to the macOS default. An explicit CLI
--deviceremains authoritative for that listener process. A runtime stream failure cancels an incomplete capture, discards stale chunks, and retries the same selection with bounded backoff; it must not require a settings change or process restart. An initial open failure uses the same bounded recovery without stopping the listener. Cancellation does not stop warm-microphone retries; enabling release-while-idle stops them once capture is idle. - Feedback volume is persisted from zero through one; zero disables tones. Volume changes apply immediately and preview one recording-start tone.
- Launch at Login uses
SMAppServicefor the signed main app. Treat bothNotRegisteredandNotFoundas disabled before registration, representRequiresApprovalwith a link to Login Items settings, and poll macOS as the source of truth instead of persisting a parallel Boolean. - Voice Action is an optional second capture target. Its saved shortcut defaults to Option-Command, but no shortcut is active or reserved until the user enables Voice Action. Disabling it cancels its active capture, not accepted jobs, and preserves the saved shortcut and model. The toggle remains reachable when OpenCode is unavailable. Promoting an active Option capture preserves all recorded audio. Selected text is optional prompt context; inaccessible or empty selections act as no selection. Use the dedicated OpenCode model and deadline, return only paste-ready text, and paste at the current focus. Failed, empty, cancelled, or timed-out actions paste nothing. Voice Action jobs share normal queueing and cancellation but never update the last dictation.
- Mode processing is best-effort and ordered: corrections, optional OpenCode rewriting, then selected transformations. A failed stage preserves its input; the transformation chain is one stage, so failure discards its partial results. It applies to Paste and Send, but not Voice Action or meetings.
- OpenCode availability checks stay off the UI thread. When the app finds the
opencode2executable,opencode2 apidiscovers or starts the managed service; the catalog uses authenticated loopback HTTP to avoid large CLI pipe truncation. HEX never invokesopencode2 serve --serviceor owns that service's lifecycle directly. A missing beta install is retried at a coarse interval so installingopencode2while Settings is open refreshes the model catalog without restarting HEX. Catalog failures require an explicit retry rather than spawning clients in a tight loop. - Ordinary resolver commands execute only from completed Moonshine lines. Voice-delimited activation and stable control phrases are deliberate exceptions.
- Contextual commands enter the candidate set only when their predicate matches.
- Literal and typed command patterns compile into one registry. Reject overlaps at configuration time and generate the catalog from that registry.
- Successful commands rely on their action for feedback. Wake/sleep use quiet tones; execution failures use an error tone.
- Clipboard insertion is the fixed text-insertion path. Restore the previous clipboard only if no newer paste or external clipboard change superseded it.
- Option-Shift-V pastes the last completed dictation. Option-Control-V pastes completed meeting turns added since the previous successful invocation.
- Do not persist captured audio by default. Explicit foreground meeting
recording must remain visibly active. Diagnostic dictation retention is an
explicit, bounded, owner-only opt-in through
HEX_RETAIN_DICTATION_AUDIO. - Retained dictation history records only successful pasted output: raw and final text plus bounded metadata, never audio, full browser URLs, or window titles. History defaults to seven days, and every retention window remains subject to hard entry and byte caps. No new entries are accepted while Off. Turning retention Off preserves existing entries; clearing history is explicit. Time-based retention expires during idle uptime through the shared history owner; searches never return expired entries while waiting for disk cleanup.
- Developer-only meeting detection may inspect process audio metadata but must not capture samples. Detection can offer recording; it must never start automatically. Release builds must not start the meeting controller.
- Meeting recording prevents idle sleep but does not apply dictation mute or pause-media behavior.
- Live meeting drafts remain available if final transcription fails. Final
publication is atomic; adjacent same-source entries within three seconds form
one displayed turn labeled
YouorComputer. - Meeting paste cursors advance only after successful insertion and reset when HEX restarts.
- Visible state remains
Transcribinguntil every accepted transcription or paste output has completed, including ordered post-processing and insertion. - The HUD is observational and click-through. It must not alter capture boundaries, steal focus, or block controls in the foreground application.
- Every desktop pane renders the shared scaffold from
desktop_ui: the bounded pane header plus one centered content column atPANE_CONTENT_WIDTH, with list+detail panes using the fixedPANE_LIST_WIDTHlist column. Panes must not introduce their own header treatments or content widths, and text columns beside a fixed column carryflex_1with a zero min-width so long lines wrap instead of widening the pane. - Public app updates are Developer ID signed, notarized, stapled, EdDSA signed, published artifact-first/feed-last, and installed through Sparkle.
- Linux direct-install updates accept only a strictly newer signed stable
x86_64 manifest, verify exact size and SHA-256 from a content-addressed
artifact, and atomically switch the user-local
currentversion. Never overwrite development, root, or package-manager-owned binaries. - The Wayland beta requires compatible clipboard, virtual-keyboard, and
layer-shell protocols plus explicit read access to all
/dev/input/event*nodes. Do not silently fall back to XWayland or privileged input injection. Raw input observes, but does not suppress, physical US-labeled keys. Explain the broad keystroke access; never grant device permissions automatically. - Linux Settings owns no microphone, model workers, saved settings, or tray. The
per-user
hex.serviceowns these independently of GUI lifetime. Closing or crashing Settings must not stop capture or accepted work. Socket access is owner-only and peer-UID checked; framing, queues, and I/O remain bounded. Client disconnect cancels only its uncommitted shortcut capture, with a bounded lease fallback. Never replay uncertain mutation requests after reconnecting. - X11 shortcut capture uses focused GPUI input, not evdev permissions. Editing shortcut/double-tap settings stops and restores only a previously running listener; cancellation restores the old binding unless its save already committed. Settings and the CLI must expose service state, recovery, and errors. Explicit service shutdown drains workers; closing Settings only disconnects its client. HUD teardown only hides that listener's HUD.
- Linux paste keeps transcripts off helper argv, bounds helper I/O, and waits for physical modifiers without discarding accepted output. Shutdown cancels that wait. New installs use Ctrl-V; the terminal-paste preference selects Ctrl-Shift-V and defaults on for persisted legacy X11 settings. The beta retains the transcript clipboard; arbitrary MIME restoration and consumption acknowledgments remain unimplemented.
The public TypeScript SDK is @kitlangton/hex in sdk/typescript. The first
release, 0.1.0, was bootstrapped manually; subsequent user-facing SDK changes
must include a Changeset under sdk/.changeset/ unless preparing an initial
unpublished package. Run package commands from the sdk workspace root.
Before release, run:
cd sdk
bun install --frozen-lockfile
cd typescript
bun run check
bun run test
bun run build
npm pack --dry-runWhen exports or packaging change, install the packed tarball in a clean consumer
and import both @kitlangton/hex and @kitlangton/hex/effect. The release
workflow is .github/workflows/release-typescript.yml; npm trusted publishing
authorizes anomalyco/hex and that exact workflow filename for npm publish.
The workflow uses npm trusted publishing without requesting a Sigstore
provenance bundle.
Use the configured Changesets release command (cd sdk && bun run release), not
direct npm publish, after the bootstrap release.
./scripts/setup.sh
./scripts/setup-parakeet.sh
bun run --cwd sdk/commands build
cargo run -- listen
cargo run -- status
cargo run -- meeting record --title "Design sync"
cargo run -- meeting probe
cargo run -- meeting watch --preview
cargo run -- app --preview-dictation
cargo run -- preview onboarding
cargo run -- preview transcription-picker --language zh --model-state installed
./scripts/capture-preview.sh /tmp/hex-preview.png settings
./scripts/capture-preview.sh /tmp/hex-update.png settings --update-available
./scripts/capture-preview.sh /tmp/hex-model-missing.png settings --model-missing
./scripts/capture-preview.sh /tmp/hex-command-model-missing.png settings --command-model-missing
./scripts/capture-preview.sh /tmp/hex-microphone-confirmation.png settings --confirm-release-microphone
./scripts/capture-preview.sh /tmp/hex-history-retention.png history --open-history-retention
./scripts/capture-preview.sh /tmp/hex-modes.png modes
./scripts/capture-preview.sh /tmp/hex-modes-collapsed.png modes --collapse-mode-processing
./scripts/capture-preview.sh /tmp/hex-modes-picker.png modes --collapse-mode-processing --open-transformation-picker
./scripts/capture-preview.sh /tmp/hex-modes-global.png modes --collapse-mode-processing --select-global-mode
./scripts/capture-preview.sh /tmp/hex-voice-action-off.png voice-action
./scripts/capture-preview.sh /tmp/hex-voice-action-unavailable.png voice-action --voice-action-enabled --opencode-unavailable
HEX_PREVIEW_SKIP_BUILD=1 ./scripts/capture-preview.sh /tmp/hex-modes.png modes
./scripts/install-app.sh
cargo fmt --check
cargo test
cargo clippy --all-targets --all-features -- -D warnings
git diff --check
./scripts/test-app-identity.sh # macOS only
./scripts/test-install-linux-release.sh # x86_64 Linux only
cd sdk/typescript && bun run check && bun run test && bun run buildUse scripts/capture-preview.sh as the default desktop-UI iteration loop. It
builds the release binary, launches one isolated deterministic preview, waits
for that process's HEX window, captures only that window, and terminates the
preview. Targets include settings, modes, voice-action, commands,
meetings, activity, onboarding, transcription-picker, hud-lab, and
dictation-hud. The Modes preview includes representative activation,
correction, model-variant, and transformation data so it exercises the complete
editor. Pass --collapse-mode-processing to expose the lower transformation
and deletion states without scrolling. Use --open-transformation-picker,
--select-global-mode, and --opencode-unavailable to capture those states
directly instead of scripting pointer coordinates. After a successful build, set
HEX_PREVIEW_SKIP_BUILD=1 for repeated
captures that do not require recompilation; HEX_PREVIEW_BINARY can override
the release binary path. Keep release previews authoritative for production
navigation because debug builds expose developer-only panes.
The supported Linux beta and release host use x86_64 Arch Linux. Install its native build dependencies with:
sudo pacman -S --needed base-devel git rustup python alsa-lib curl jq openssl xxd \
util-linux gtk3 gtk-layer-shell libappindicator-gtk3 libxkbcommon \
libxkbcommon-x11 libx11 libxcb openblas vulkan-headers vulkan-icd-loader \
shaderc spirv-headers clang cmake pkgconf wl-clipboard wtype
rustup default stableFor a source install, run scripts/install-linux.sh, then hex model install.
The installer owns the user-local version layout, desktop entry, and autostart
entry; only that managed layout participates in automatic updates.
nix develop supplies the same native build environment as the Nix package;
nix flake check checks modules, shell configuration, session readiness, and
the display-free Rust suite. See docs/nix.md. Nix owns package updates and
optional Home Manager autostart. Native Linux PR checks do not replace real
X11/Wayland, keyboard hotplug, microphone, and target-application smoke tests.
Automatic microphone selection follows the compiled preference order in
src/config.rs, then falls back to the macOS default. A saved microphone takes
precedence while available; override everything with --device. The app bundle
build requires Xcode 26 for Icon Composer compilation and a Developer ID signing
identity. scripts/release-app.sh prepares a notarized and stapled DMG plus its
signed Sparkle appcast; run scripts/release-app.sh publish only after validating
the prepared artifact. Public app downloads and update feeds use
https://downloads.hex.kitlangton.dev, connected directly to the hex-releases
R2 bucket through Custom Domains. Preserve the legacy public R2 endpoint for
installed clients. The publisher verifies both R2 artifacts and the identical
GitHub DMG mirror before publishing the appcast. GitHub app releases use
app-v<VERSION> tags with --latest=false, keeping them distinct from SDK
releases. Never overwrite a published artifact with different bytes.
After publishing a new macOS version, update MAC_VERSION in site/src/App.tsx,
the explicit GitHub mirror/version link in README.md, and the Homebrew cask.
Build the site with bun run --cwd site build, then deploy from site with
wrangler deploy; its checked-in config targets hex-voice-site at
hex.kitlangton.dev. Verify both download links after deployment.
Also update the separate https://hex.kitlangton.com site in
/Users/kit/code/projects/hex-marketing-site: its MAC_VERSION lives in
src/app/components/TerminalSimulation.tsx. Run its documented checks and
vercel --prod from that repo. The linked Vercel project also owns
hex-marketing-site.vercel.app and hex-marketing-site-kit-langtons-projects.vercel.app.
Both marketing sites must show the same release, custom-host download, and
explicit GitHub alternative; verify the production domains after deploying.
The Rust app is named Hex, packaged as Hex.app,
with bundle identifier com.kitlangton.hex2 and executable hex. Kit has approved
his personal Developer ID signing team QC99C9JE59 for this app. Signing requires
an explicit VOICE_CONTROL_TEAM_ID and matching HEX_NOTARY_PROFILE; the build
and release scripts verify the selected team rather than assuming an Anomaly team.
scripts/validate-app.sh checks this identity before preparation and publication.
The manual DMG contains Hex.app; the signed Sparkle ZIP preserves HEX.app
as its archive root so existing Rust 2.0.x installations can discover the update
despite the new bundle ID. Both artifacts contain the same signed, stapled app.
Sparkle compares CFBundleVersion, not source changes or signing timestamps.
Public releases must advance beyond distributed beta builds as well as the feed;
the older local Rust 2.1.0 beta already used build 20100. Do not reuse a released
version/build for changed code. Validate updates from both the public 2.0.x app
and the older 2.1.0 beta when changing update identity or packaging.
There is no Swift migration: never import Swift preferences or data, adopt its
bundle identifier, publish Rust artifacts to its S3 feed, or automatically quit,
delete, or replace the Swift app. Prefer a website-only informational item in
the legacy Sparkle feed, with no enclosure, pointing to the new app for manual
installation and fresh setup. Publish it only after the new artifact is live. See
docs/plans/swift-app-handoff.md. The Rust data root remains unchanged, but the
new app identity requires fresh macOS permission grants. Validate signed
distribution with the selected signing team before publishing an app update.
SMAppService is meaningful only from a signed app installed in
/Applications. When replacing a local bundle outside Finder during a login-item
smoke test, register the new bundle with Launch Services before launch:
'/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister' \
-f /Applications/Hex.appPass the actual installed Rust host path to lsregister if it was renamed.
Verify both registration and unregistration with sfltool dumpbtm, and restore
the user's original login-item state after the test. Do not add release test
hooks or persist a second launch-at-login flag.
Linux releases are prepared and published separately on the supported x86_64
Linux host. Inject the Ed25519 private PEM as HEX_LINUX_SIGNING_KEY, run
scripts/release-linux.sh prepare, validate the artifact, then run
scripts/release-linux.sh publish. The script refuses a key that does not match
the public key compiled into HEX, refuses a non-monotonic stable release, and
publishes the signed feed last.
Use termctrl to verify TUI changes at both wide and narrow dimensions. The
dashboard keys are 1 for commands, 2 for the activity log, 3 for meetings,
Tab to cycle, and q/Escape to quit.
~/Library/Application Support/voice-control/logs/live.ndjson: state, transcript, command decision, outcome, processing, and context.~/Library/Application Support/voice-control/logs/process.log: Rust, CoreAudio, Moonshine, and context-adapter diagnostics.~/Library/Application Support/voice-control/meetings/: manifests, separate tracks, recoverable live drafts, and atomically published final transcripts.
When diagnosing a missed command, inspect both logs and distinguish microphone capture, transcription, command mode/dictation, context matching, command resolution, and action execution before changing aliases or thresholds.
See ROADMAP.md. Do not add hypothetical seams for roadmap items. Introduce a
seam once there are two real adapters or a current test requires substitution.
ROADMAP.md is the authoritative work list. Keep these constraints visible:
- Validate public onboarding from a clean macOS account and signed Linux updates on the supported Arch/i3 host.
- Validate native Wayland on a real compatible compositor, including physical device reconnect, focus/click-through, target paste, and tray-less shutdown.
- Add a second real browser adapter before generalizing browser context.
- Do not turn concrete macOS, Linux X11, or command modules into hypothetical platform or plugin frameworks.