Commit Graph
131 Commits
Author SHA1 Message Date
Edison Jwa 714a6b758c perf(ui): stop polling snapshots from audio stats 2026-05-31 23:03:19 +09:00
Edison Jwa e8e9fa8ccf build(ios): remove onnxruntime pod wiring 2026-05-31 22:30:46 +09:00
Edison Jwa fe6e07353e chore: restore product scaffold to rollback baseline 2026-05-29 14:02:04 +09:00
Edison Jwa 487aff4e7c fix: improve voice and chat controls 2026-05-25 18:26:56 +09:00
Edison Jwa d03ea937e6 fix: refine linux voice runtime behavior 2026-05-25 18:26:49 +09:00
Edison Jwa a2d686d9d0 feat: promote linux native audio path 2026-05-25 17:42:06 +09:00
Edison Jwa 862f83b297 fix: parse linux distro metadata 2026-05-25 16:02:01 +09:00
Edison Jwa 4a2a05ef83 fix: keep diagnostics available while connecting 2026-05-25 16:01:48 +09:00
Edison Jwa b8df25a195 Reduce Linux setup ambiguity and surface desktop input/message failures honestly
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
2026-05-25 11:55:10 +09:00
Edison Jwa 1709ae2ac2 Route Android system back through the Flutter shell
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
2026-05-25 03:21:02 +09:00
Edison Jwa 7c9660572e chore: refresh iOS project metadata 2026-05-25 01:20:36 +09:00
Edison Jwa 1326b03301 build: add Android release APK packaging script 2026-05-25 01:20:16 +09:00
Edison Jwa 5515ff6643 feat: stabilize voice activity and audio routing 2026-05-25 01:19:09 +09:00
Edison Jwa d7556cd39f feat: request Android startup permissions 2026-05-25 01:10:10 +09:00
Edison Jwa 11d4e7b7da fix: stabilize Android permission state names 2026-05-25 01:08:02 +09:00
Edison Jwa b8195acc9b perf: avoid duplicate bootstrap work on join 2026-05-24 21:02:16 +09:00
Edison Jwa b494f8902d perf: short-circuit granted mic permission checks 2026-05-24 20:54:49 +09:00
Edison Jwa 7d5d8c2c90 feat: integrate chat voice and diagnostics client 2026-05-23 06:51:55 +09:00
Edison Jwa 7e28791ec2 Handle pokes outside chat tabs 2026-05-23 05:25:36 +09:00
Edison Jwa 738b274748 chore: bump build number to +76 2026-05-22 10:03:17 +09:00
Edison Jwa bf284018e6 feat: Android Oboe voice backend — WebRTC APM, VAD, HW/SW toggle, BBCode welcome, link trust, foreground task
Audio engine (Rust):
- Android Oboe: WebRTC APM (AEC/NS/AGC/HPF) + TEN/Silero ONNX VAD
- Hardware effects (JNI) with software fallback per-effect
- Render reference buffer for AEC between output/capture callbacks
- Voice activity gate: suppress transmission when speaker muted (all platforms)
- Audio focus (SDD-109) + Bluetooth SCO (SDD-110) via JNI
- ONNX Runtime 1.26 via ort 2.0.0-rc.12 (down from rc.10, ndarray 0.17)
- VAD worker channel capacity 8→32, initial seq u64::MAX (warm-up fix)
- TEN VAD default backend (was Silero)
- Platform→WebrtcApm resolution after hardware binding
- oboe-rs edisonjwa fork with get_raw_session_id()

Android Kotlin:
- AndroidAudioFocusController + AndroidBluetoothScoController
- AndroidAudioLifecycleController (route changes to Flutter)
- ProGuard rules for new controllers

Flutter UI:
- VoiceSettings: Android HW/SW toggle (Platform auto / WebRTC APM)
- VoiceStatusChip: mute warning border + Speaker muted label
- BBCode welcome message parser (BbCodeText, case-insensitive)
- Welcome message foldable (expanded by default)
- Link trust dialog (domain wildcards, SharedPreferences)
- HapticFeedback on voice sheet opener
- Server name in AppBar, version v0.1.0
- Default channel (id=1) visible, serverquery clients hidden
- flutter_foreground_task integration

Config:
- ort load-dynamic on all non-iOS (Android/Linux/Windows)
- ONNX Runtime AAR 1.26.0
- ndarray moved to common deps (was Apple-only)
2026-05-22 09:29:57 +09:00
Edison Jwa 6af4ecab0f feat(voice): add iOS VAD runtime support 2026-05-21 20:51:45 +09:00
Edison Jwa 171baf6e41 fix(voice): clamp transmit when speakers muted 2026-05-20 16:37:48 +09:00
Edison Jwa f8a9c7a422 fix(android): add output device picker 2026-05-20 16:26:13 +09:00
Edison Jwa f4db27b565 fix(android): expose audio output devices 2026-05-20 16:26:07 +09:00
Edison Jwa 256874997b fix(android): request microphone permission on startup 2026-05-20 14:52:34 +09:00
Edison Jwa bc1bd89424 fix(android): strengthen user voice indicators 2026-05-20 14:52:34 +09:00
Edison Jwa b21fc16ee6 fix(android): clarify channel and voice status icons 2026-05-20 14:52:34 +09:00
Edison Jwa 34121b2eb7 fix(android): use row-only channel joins 2026-05-20 14:52:34 +09:00
Edison Jwa 48a38dba46 fix(android): prompt from locked channel rows 2026-05-20 14:52:34 +09:00
Edison Jwa f8b6485390 fix(android): show password action only for locked channels 2026-05-20 14:52:34 +09:00
Edison Jwa 273eaf21c1 fix(android): add channel password join action 2026-05-20 14:52:34 +09:00
Edison Jwa 42d0ca0953 fix(android): polish app chrome and join guard 2026-05-20 14:52:34 +09:00
Edison Jwa bc70da51df fix(android): derive current channel from snapshot 2026-05-20 14:52:34 +09:00
Edison Jwa 3ae8e1ab77 fix(android): adapt fold layout and diagnostics 2026-05-20 14:52:34 +09:00
Edison Jwa 30ff955439 fix(android): clear permission-owned hard mute 2026-05-20 14:52:34 +09:00
Edison Jwa eb1f4b895e fix(android): remove cpal product wording 2026-05-20 14:52:34 +09:00
Edison Jwa 210a6a0e11 fix(android): unblock mic permission startup 2026-05-20 14:52:34 +09:00
Edison Jwa d6763787aa feat(android): initialise Oboe runtime context 2026-05-20 14:52:33 +09:00
Edison Jwa 8c253f1d4d feat(voice): harden Android audio and channel joins 2026-05-19 01:58:07 +09:00
EdisonJwa 477394d83e fix(android,build): restore multi-ABI build via cmake-rs patch (DEC-032 exit)
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.
2026-05-18 14:48:16 +08:00
EdisonJwa a77a9b2ef2 docs(sdd-119)+feat(macos): consolidate deployment-target SoT (SDD-119 amendment v0.9.17)
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.
2026-05-18 14:39:14 +08:00
EdisonJwa 4c19410556 test+diag: SWE.4 unit tests for Rust paths, Dart service tests, diagnostics audio.android section
Verification + diagnostics:

- apps/chanora_flutter/test/services/back_intent_policy_test.dart:
  8-case truth table for the BackIntentPolicy pure function
  (SWE4-UV-042).
- apps/chanora_flutter/test/services/back_intent_service_test.dart:
  5 channel-routing tests (SWE4-UV-042, SWE5-IV-019 unit slice).
- apps/chanora_flutter/test/services/android_permissions_service_test.dart:
  12 tests covering inbound channel events, outbound requests,
  state-machine transitions, and non-Android short-circuit
  (SWE4-UV-041).
- SDD/SRS trace headers added to alpha_e2e_test.dart,
  beta_e2e_test.dart, widget_test.dart so the existing test ↔ ID
  mapping is discoverable by grep.
- chanora_diagnostics: extends DiagnosticExport with android_audio:
  Option<String> for the SDD-116 evidence schema (requested /
  achieved performance mode + sharing mode + input preset + sample
  rate + frames per burst, per-effect engagement, latency tier).
  The bridge's export_diagnostics() now embeds the Android section
  when current_android_audio_diagnostics() returns Some.

All 93 workspace Rust tests and 26 Dart test/services tests pass.

Trace: SDD-090, SDD-112, SDD-113, SDD-116, SWE4-UV-041, SWE4-UV-042,
SWE4-UV-045, SWE4-UV-047, SWE4-UV-048, SWE4-UV-049, SWE4-UV-051,
SWE4-UV-052, SWE5-IV-019.
2026-05-18 12:48:28 +08:00
EdisonJwa c4145a8727 feat(flutter,android): permission state banner + AndroidPermissionsService + voice-join gate
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.
2026-05-18 12:48:28 +08:00
EdisonJwa 0dc8297568 feat(android,p0): foreground service, permission requester, application class, build automation
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.
2026-05-18 12:35:19 +08:00
EdisonJwa 7966a7c8c6 feat(bridge,android): BridgeEvent::PermissionState + JNI publish hook + c++_shared link
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).
2026-05-18 12:32:01 +08:00
Edison Jwa dc9c5c0a4e feat: multi-platform bug fixes, Android audio path, and build tooling
Flutter UI fixes:
- Fix stale channel badge/speaker when moved by others (derive current
  channel from ownClientId instead of optimistic local state)
- Fix Linux PTT via focused fallback key handler
- Distinguish ServerQuery clients with terminal icon in client list
- Reduce duplicate current-channel badge display
- Prevent PTT key-bind save from permanently closing voice settings
- Fix Linux GTK reopen-after-close (quit app on window destroy)
- Fix focused PTT: consume key events, release held keys on
  disconnect/leave-channel/mode/backend changes, suppress stale errors

Flutter Rust bridge:
- Thread is_server_query flag through protocol→bridge→Dart
- Add own_client_id to BridgeSnapshot DTO
- Add log_file_path_str() for platform log path queries

Rust protocol:
- Add ServerQuery test coverage (query_client_type_maps_to_server_query_flag)
- Split reqwest TLS: native-tls for desktop/iOS, rustls for Android

Rust audio:
- Upgrade cpal 0.16→0.17.3 with API adjustments (SampleRate, description())
- Suppress Android-only dead-code warnings (open_log_file, keyring_account)

Android build tooling:
- tools/build-opus-android.sh: NDK auto-discovery, correct CMake
  Android variables (ANDROID_ABI, ANDROID_PLATFORM), portable baseline
- tools/build-android-rust.sh: build+copy Rust cdylib for arm64-v8a,
  armeabi-v7a, x86_64 into android/app/src/main/jniLibs/
- Add jniLibs/ to .gitignore

Rust bridge:
- Guard open_log_file() on non-Android (Android uses logcat)
2026-05-18 00:13:21 +09:00
Edison Jwa 00692b76a2 fix(macOS): enable Hardened Runtime + outgoing network for all configs
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.
2026-05-17 22:32:30 +09:00
Edison Jwa 72c6e14797 feat: modern macOS window chrome + Linux build script
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/
2026-05-17 22:29:42 +09:00
Edison Jwa 63b102ed27 feat: add NSLocalNetworkUsageDescription to iOS and macOS Info.plist
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.
2026-05-17 22:10:39 +09:00