Clarify ONNX Runtime guidance with direct-open install hints, restore desktop WebRTC VAD visibility, map mouse side buttons through focused PTT capture/runtime paths, and wait for server acks before showing chat sends as successful.
Constraint: Linux release UX must stay functional when ONNX Runtime is optional and GNOME portal availability varies
Rejected: Keep desktop VAD locked to Silero only | misleads users when ONNX Runtime is skipped
Confidence: medium
Scope-risk: moderate
Directive: Preserve the protocol send-ack wait path for chat so UI success always tracks real server acceptance
Tested: flutter analyze lib/main.dart lib/widgets/chat_views.dart lib/widgets/input_dialogs.dart lib/widgets/startup_dependency_screen.dart; flutter test test/widgets/input_dialogs_test.dart test/widgets/chat_views_test.dart test/services/startup_dependency_check_test.dart test/widgets/startup_dependency_screen_test.dart test/widgets/voice_settings_controls_test.dart test/widgets/audio_processing_config_state_test.dart; cargo test -p chanora_protocol --lib; cargo test -p chanora_audio ptt_backends --lib
Not-tested: Live manual GNOME portal rebind/global PTT on a real desktop session; observer-bot chat against a live server after the sender-name fallback change
Constraint: SRS-163 requires Android back to be handled by the shell/platform layer, and the Dart service existed but was not wired into the live app.
Rejected: Leave BackIntentService test-only and unwired | System back would bypass app policy on Android.
Confidence: medium
Scope-risk: narrow
Directive: Keep back-intent probe state in sync with every new dialog or pushed route added to the Flutter shell.
Tested: dart format apps/chanora_flutter/lib/main.dart; flutter analyze lib/main.dart test/services/back_intent_service_test.dart; flutter test test/services/back_intent_service_test.dart
Not-tested: Manual Android device back-navigation smoke test
Closes DEC-032. Restores the canonical Android ABI set
{arm64-v8a, armeabi-v7a, x86_64} per SDD-073 item 4 / SDD-118 item 3.
Root cause was the audiopus_sys + cmake-rs + NDK toolchain-file gap:
cargo-ndk 4.x sets ANDROID_ABI / ANDROID_PLATFORM as env vars per
invocation, but upstream cmake-rs 0.x does not forward them to the
child cmake invocation as -D variables, so armeabi-v7a and x86_64
configure steps fell through to the toolchain-file default and
failed to build.
Fix:
- Cargo.toml: add a workspace [patch.crates-io] stanza pinning the
cmake crate to fork pr2502/cmake-rs @ commit
bdad5edc569d82151922c5c6c4685b1563f12aa1 (branch android-build),
which carries cmake-rs PR #257
(https://github.com/rust-lang/cmake-rs/pull/257). The patch is a
9-line addition that forwards ANDROID_ABI and ANDROID_PLATFORM
from the env to the child cmake as -D variables.
- Cargo.lock: regenerated by 'cargo update -p cmake'; the lone
cmake entry now points at the fork rev.
- apps/chanora_flutter/android/app/build.gradle.kts: restore
abiFilters to {arm64-v8a, armeabi-v7a, x86_64}; remove the
TODO(x86_64/armv7 follow-up) comment.
- docs/governance/product-decision-register.md: mark DEC-032 as
Resolved (2026-05-18) with the resolution mechanism, update the
§3 / §7 rows, and append a 0.9.8.1 change-history entry.
Verification (host: Linux):
cargo update -p cmake -> pulled fork rev
cargo check --workspace --all-targets -> PASS
cargo test --workspace -> PASS (no regressions)
cargo ndk --platform 28 -t arm64-v8a build -p chanora_bridge -> PASS
cargo ndk --platform 28 -t armeabi-v7a build -p chanora_bridge -> PASS
cargo ndk --platform 28 -t x86_64 build -p chanora_bridge -> PASS
Upstream tracking: re-evaluate the [patch.crates-io] override once
cmake-rs PR #257 merges and a fresh cmake release lands on
crates.io; at that point switch to a plain dep bump and remove the
override.
Resolve the macOS half of the SDD-119 item 3 single-source-of-truth follow-up. The chanora_bridge cdylib macOS deployment-target floor ('10.15') was previously hard-coded at 7 sites across Podfile and chanora_bridge.podspec; this commit collapses them to a single Ruby constant declaration in a new SoT file.
Selected Option B (Ruby constant) over Option A (.xcconfig — rejected because the podspec prepare_command runs before any xcconfig is applied) and Option C (versioned text file — rejected as overkill given both consumers are already Ruby). Realizes SAD-087(a)'s 'single macOS build-configuration location' mandate.
Out of scope: the pbxproj 'MACOSX_DEPLOYMENT_TARGET = 10.15' lines (565/666/717) belong to the PBXProject default config and are independently overridden to '11.0' at the Runner PBXNativeTarget level; they are the Runner app's floor, not the bridge cdylib's floor. The iOS half (IPHONEOS_DEPLOYMENT_TARGET=13.0) remains an open follow-up.
Files: new apps/chanora_flutter/macos/macos_deployment_target.rb (MACOS_BRIDGE_DEPLOYMENT_TARGET = '10.15'.freeze); Podfile + chanora_bridge.podspec require_relative the constant and consume it at 7 sites; docs/architecture/sdd.md SDD-119 item 3 rewritten + Notes bullet updated + v0.9.17 changelog entry.
Dart consumer for the Android permission state pipeline. New
AndroidPermissionsService listens on the app.chanora/android_permissions
MethodChannel and exposes a ValueListenable for the UI. The voice-join
flow in main.dart calls ensureRecordAudio() before rust.voiceJoin and
clamps to listen-only via setHardMute on denial. A non-modal banner
above the VoiceBar surfaces the Grant / Open Settings action depending
on whether the state is Denied or PermanentlyDenied. On non-Android
hosts the service short-circuits to granted; the banner is never built.
Also adds the BackIntentService Dart consumer (back_intent_policy +
back_intent_service) which the Kotlin BackIntentBridge invokes via
MethodChannel for deterministic route-pop ordering.
Trace: SDD-028, SDD-106, SRS-163, SRS-209.
Android P0 platform shell:
- ChanoraApplication: early System.loadLibrary("c++_shared") +
System.loadLibrary("chanora_bridge") so JNI is hot before
MainActivity.onCreate.
- MainActivity: configureFlutterEngine + onResume/onDestroy wiring
for BackIntentBridge and AndroidPermissionRequester; publishes
permission-state changes through both the MethodChannel (Dart UI)
and the JNI hook (Rust audio engine).
- AndroidVoiceForegroundService: microphone-type foreground service
with notification channel chanora.voice.session per SDD-107.
- AndroidPermissionRequester: RECORD_AUDIO state machine with
persisted "has-ever-requested" flag so PermanentlyDenied is
correctly distinguished from never-asked across cold launches.
- BackIntentBridge: API 33+ OnBackInvokedCallback + pre-33
OnBackPressedDispatcher with deterministic Dart-side policy.
- MethodChannels: centralized constants for app.chanora/*.
- build.gradle.kts: SDD-118 Gradle automation that auto-builds the
Rust cdylib via cargo-ndk with per-ABI Exec tasks, minimal-env
isolation, CMAKE_TOOLCHAIN_FILE pinning, libc++_shared.so staging,
release-inspection assertion. abiFilters temporarily reduced to
arm64-v8a only per DEC-032 (multi-ABI restoration pending).
- AndroidManifest.xml: INTERNET, RECORD_AUDIO, FOREGROUND_SERVICE,
FOREGROUND_SERVICE_MICROPHONE, POST_NOTIFICATIONS,
MODIFY_AUDIO_SETTINGS, BLUETOOTH_CONNECT permissions; service
declaration with foregroundServiceType=microphone.
- proguard-rules.pro: keep rules for JNI native methods + Flutter
plugin entry points + FRB bindings.
Trace: SDD-073, SDD-105, SDD-106, SDD-107, SDD-108, SDD-110, SDD-118,
SRS-111, SRS-119, SRS-163, SRS-187, SRS-209, SRS-215.
Per SDD-106 §5 add BridgeEvent::PermissionState{permission, state}
with the PermissionStateKind enum (Granted, Denied, PermanentlyDenied,
Unknown). The Kotlin side publishes mid-session permission changes
through a new JNI entry point Java_app_chanora_chanora_1flutter
_MainActivity_publishPermissionState routed by the new
permission_jni.rs module; the Rust audio engine subscribes and
authoritatively clamps the transmit gate (see SDD-106 §6).
Adds crates/chanora_bridge/build.rs to emit
cargo:rustc-link-lib=dylib=c++_shared on Android so libchanora_bridge
.so carries DT_NEEDED libc++_shared.so; this is required by Android
API 24+ per-library linker namespaces to resolve __cxa_pure_virtual
and friends at System.loadLibrary time.
Includes the FRB-regenerated Dart counterparts so each commit is
independently buildable.
Trace: SDD-105, SDD-106 §5, SDD-118 item 6 (extended).
Debug and Profile had ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO which
blocked outbound TCP. All three configs now have:
- ENABLE_HARDENED_RUNTIME = YES
- ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES
This was the root cause of the 'Operation not permitted' connection
failure on macOS Sequoia.
macOS:
- Transparent title bar with hidden title, full-size content view
- macOS: inline Row header (no AppBar) with 56px traffic-light pad
- Other platforms: standard Material AppBar unchanged
- App name 'Chanora' in CFBundleName/CFBundleDisplayName (iOS + macOS)
- NSLocalNetworkUsageDescription added to both platforms
Linux:
- tools/build-linux.sh: builds Rust .so + Flutter bundle + tarball
- Verifies GTK3, libopus dev headers, Rust target
- Copies libchanora_bridge.so into bundle/lib/
Required for local network privacy prompt on macOS 15+ and iOS 14+.
App appears in System Settings → Local Network after connecting to a
LAN server. Internet-hosted servers only need network.client entitlement.
When macOS denies network access (PermissionDenied), show a localized
dialog explaining how to grant permission in System Settings, with
an 'Open System Settings' button that opens directly to the Local
Network privacy pane.
Also adds author info (Edison Jwa) to About dialog and moves
diagnostics button from AppBar into About dialog.