Commit Graph
100 Commits
Author SHA1 Message Date
Edison Jwa 1813bbaa0c fix(ci): keep benchmark advisory non-blocking 2026-06-02 20:01:58 +09:00
Edison Jwa da174806ac fix(ci): install SDL2 for Rust tests 2026-06-02 19:52:07 +09:00
Edison Jwa 80c2ed46bc fix(ci): align Flutter inventory with stable SDK 2026-06-02 19:44:45 +09:00
Edison Jwa 7510bdca73 fix(ci): refresh Flutter license inventory 2026-06-02 19:41:50 +09:00
Edison Jwa ddf858cc6c fix(audio): address CoreML VAD review feedback 2026-06-02 19:39:11 +09:00
Edison Jwa 966afd2b53 ci: fix Apple CoreML VAD checks 2026-06-02 19:33:02 +09:00
Edison Jwa 112a563de5 build(macos): link local SileroCoreML package 2026-06-02 01:54:05 +09:00
Edison Jwa cac178f4af build(ios): link local SileroCoreML package 2026-06-02 01:53:21 +09:00
Edison Jwa 813a38e92b feat(audio): add Apple CoreML Silero VAD 2026-06-02 01:52:08 +09:00
Edison Jwa 71b7502ab7 Merge pull request #11 from EdisonJwa/fix/reduce-voice-snapshot-polling
perf(ui): stop polling snapshots from audio stats
2026-05-31 23:44:35 +09:00
Edison Jwa 25e6a16eb1 Merge pull request #10 from EdisonJwa/fix/ios-audio-lifecycle-lag
fix(ios): stabilize audio lifecycle startup
2026-05-31 23:43:32 +09:00
Edison Jwa 7fb89c3dc7 Merge pull request #8 from EdisonJwa/fix/storage-test-isolation
Isolate storage test temp directories
2026-05-31 23:42:50 +09:00
Edison Jwa a4f6d66aec Merge pull request #7 from EdisonJwa/salvage/app-snack-bar
Add shared app snackbar styling
2026-05-31 23:42:18 +09:00
Edison Jwa 714a6b758c perf(ui): stop polling snapshots from audio stats 2026-05-31 23:03:19 +09:00
Edison Jwa ecb9ae9636 fix(audio): restart iOS voice unit in place 2026-05-31 22:30:56 +09:00
Edison Jwa e8e9fa8ccf build(ios): remove onnxruntime pod wiring 2026-05-31 22:30:46 +09:00
Edison Jwa 6f7063971d fix(audio): use WebRTC VAD on iOS 2026-05-31 22:30:28 +09:00
Edison Jwa c02d4e6df3 fix(bridge): serialize iOS audio lifecycle events 2026-05-31 22:29:38 +09:00
Edison Jwa d39d6c78d2 fix(core): unblock iOS connect audio startup 2026-05-31 22:29:30 +09:00
Edison Jwa 681f3b636f test(storage): isolate temp directories in tests 2026-05-29 19:19:20 +09:00
Edison Jwa b320f24c93 feat(ui): add shared app snackbar styling 2026-05-29 18:40:57 +09:00
Edison Jwa 0e04dac064 Restore product scaffold to rollback baseline
Merge reset-style baseline PR after local verification. GitHub Actions did not start because of the account billing/spending-limit blocker documented in the PR body.
2026-05-29 16:13:46 +09:00
Edison Jwa fe6e07353e chore: restore product scaffold to rollback baseline 2026-05-29 14:02:04 +09:00
Edison Jwa 2896f14ec9 fix: show one linux audio backend 2026-05-25 18:31:20 +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 cd14aa7b98 build: bundle onnxruntime in linux releases 2026-05-25 18:26:40 +09:00
Edison Jwa a2d686d9d0 feat: promote linux native audio path 2026-05-25 17:42:06 +09:00
Edison Jwa c19de3a370 chore: ignore local third party experiments 2026-05-25 16:20:50 +09:00
Edison Jwa c502a4dd00 chore: add local dev toolchain environment 2026-05-25 16:11:32 +09:00
Edison Jwa afd8e525f9 fix: use matching flutter linux bundle arch 2026-05-25 16:04:37 +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 5c8c6df16b Make beta release artifacts reproducible on Linux and Android
Constraint: SRS-118 and SRS-119 require a Linux release package and an Android release AAB, and the current workspace also needs the sibling oboe-rs checkout for Cargo manifest loading.
Rejected: Keep release packaging as ad-hoc local knowledge | CI and contributors would still miss the required artifacts and hit the missing oboe-rs prerequisite.
Confidence: medium
Scope-risk: moderate
Directive: If the oboe-rs fork path changes or is vendored, update the helper scripts and workflow checkout steps together.
Tested: bash -n tools/build-linux-deb.sh tools/build-android-aab.sh; python3 YAML parse for .github/workflows/ci.yml, .github/workflows/bench-advisory.yml, .github/workflows/bench-baseline-update.yml; git diff --check
Not-tested: End-to-end flutter build linux --release; end-to-end flutter build appbundle --release; GitHub Actions runtime execution
2026-05-25 03:21:21 +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 0648203100 build: lock resolver dependencies 2026-05-25 02:58:53 +09:00
Edison Jwa 6f64062fd8 chore: clean Android voice build warnings 2026-05-25 01:27:28 +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 eb9014cd81 feat: add TeamSpeak address resolver 2026-05-25 01:12:50 +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 9b8d812f9c chore: ignore local workspace metadata 2026-05-23 06:52:48 +09:00
Edison Jwa 3200312b0d docs: add baseline references and workspace tasks 2026-05-23 06:52:24 +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 9a2c882ab0 fix(core): keep speaker mute local 2026-05-20 16:26:19 +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 2382c2b569 fix(security): update vulnerable rust dependencies 2026-05-20 14:52:34 +09:00
Edison Jwa 256874997b fix(android): request microphone permission on startup 2026-05-20 14:52:34 +09:00
Edison Jwa b65cedfa95 fix(android): recover from input stream failures 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 7473878136 fix(core): emit voice state from snapshots 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 7254f81c65 fix(core): complete voice joins on server accept 2026-05-20 14:52:33 +09:00
Edison Jwa 2861f5b010 fix(core): normalize channel passwords before joins 2026-05-20 14:52:33 +09:00
Edison Jwa 26b06ce786 fix(android): expose voice diagnostics safely 2026-05-20 14:52:33 +09:00
Edison Jwa d6763787aa feat(android): initialise Oboe runtime context 2026-05-20 14:52:33 +09:00
Edison Jwa c87b47f064 feat(audio): prefer native voice backends 2026-05-20 14:52:33 +09:00
Edison Jwa 7d6d56e330 docs(p0): compact MVP spec for Android Oboe focus 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 29a553d4e1 docs(traceability): add Deferred Work Watchlist (DW-001..DW-012); matrix v0.9.11
Convert prose-only deferrals scattered across SDD-120 §10, SDD-119 §3, DEC-032 resolution prose, and researcher Wave 4 Tier B notes into a single durable governance register. Watchlist is governance-layer only; no engineering-chain coverage rule relaxed.

Entries:

- DW-001 Dimension 3 production telemetry export (SDD-120 §10 / SysRS-308; P1; systems-requirements)

- DW-002 Build-failing hard CI gate vs. current advisory-only (SDD-120 §10; P1; systems-requirements)

- DW-003 Multi-host benchmarking (macOS Apple Silicon + Windows x86_64; SDD-120 §10 / SysDes-157; P2; system-architect)

- DW-004 IDE integration for cargo bench (SDD-120 §10; P3; builder)

- DW-005 Dart-side flutter_rust_bridge round-trip bench (SDD-120 §10 / Tier B4; P2; detailed-designer)

- DW-006 iOS deployment-target SoT consolidation (SDD-119 §3 v0.9.17; macOS half already closed by b23b46c; P2; detailed-designer)

- DW-007 Opus encode/decode latency under varied conditions (Tier B; P2; verification-engineer)

- DW-008 Resampler throughput at non-canonical rate pairs (Tier B; P3; verification-engineer)

- DW-009 Protocol forwarder loop tracing benchmark (Tier B; P3; verification-engineer)

- DW-010 Remove [patch.crates-io] cmake-rs pin once PR #257 merges and a release lands (DEC-032 resolution prose; P2; builder)

- DW-011 Linux SIGABRT root cause + graceful shutdown SRS/SDD chain (no source SDD yet; P0; debugger then systems-requirements)

- DW-012 Manual bench-baseline-update.yml workflow_dispatch to seed first real baseline (SDD-120 §6 / SAD-091; P1; operator action)

DW-010 and DW-006 had partial prose precedents in DEC-032 and SDD-119 respectively; neither was tracked in a watchlist. No duplicates introduced. ID convention (monotonic DW-NNN, retired-not-reused) established in the section intro. Doc-only change; no code touched.
2026-05-18 14:52:01 +08: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 167aef5760 docs(sysrs): SysRS-310 — macOS minimum runtime baseline (v0.9.11)
Author SysRS-310 ratifying the macOS minimum runtime baseline at 10.15
(Catalina) at the SysRS layer, parallel to SysRS-286 (iOS 13.0) and
SysRS-288 (Android API 28).

Closes the Wave 1.5 traceability-audit deferred-but-optional follow-up.
ID allocation: SysRS-310 (not SysRS-290, which is already allocated to
MVP single-active-server-connection scope); monotonic numbering
preserved.

Cross-references SAD-087, SDD-119, SysRS-286, SysRS-288. Verification:
Review + Platform Test on a macOS 10.15 system. Raising the baseline
(e.g., to 11.0 / Big Sur) requires a DEC entry.

Trace: SysRS v0.9.11, SAD-087, SDD-119.
2026-05-18 14:41:55 +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 92087d066a docs(verification,traceability): SWE4-UV-058..062 for SDD-120 benches + matrix absorption
Closes the SDD-120 §11 verification-engineer follow-up and refreshes the
traceability matrix to incorporate the full benchmark-infrastructure
chain landed in commits 3a7750a / 8e95972 / 75b04f0.

swe4-unit-verification-plan.md v0.9.14 → v0.9.15:
- SWE4-UV-058: bench_capture_alloc_count (SDD-120 §3; SRS-216 metric 1;
  SRS-219 clause a zero-tolerance).
- SWE4-UV-059: bench_capture_callback_wall_clock (metric 2; SRS-219 b
  +20% p95).
- SWE4-UV-060: bench_opus_encode_latency (metric 3; SRS-219 c +15% mean).
- SWE4-UV-061: bench_opus_decode_latency (metric 4; SRS-219 c +15% mean).
- SWE4-UV-062: bench_resampler_throughput (metric 5; SRS-219 d -10%
  samples/sec).
All five status PENDING_BASELINE until the first manual
bench-baseline-update.yml dispatch establishes baselines.

traceability-matrix.md v0.9.9 → v0.9.10:
- 9 new chain rows binding SysRS-307..309 → SysDes-156..158 →
  SRS-216..219 → SAD-088..091 → SDD-120 sections → code anchors →
  SWE4-UV-058..062.
- 10 new code-anchor rows for the benchmark files (benches/{common,
  realtime_capture,opus_codec,resampler}.rs + examples/{emit,compare}
  _baseline.rs + Cargo.toml + bench-advisory.yml +
  bench-baseline-update.yml + .gitignore).
- 5 §A markers reconciled to PENDING_BASELINE (the SWE.4 IDs are now
  authored; what remains pending is the baseline measurement).
- DEC-032 (abiFilters reduction) status unchanged; exit criteria
  pending.
- All 5 prior open issues from earlier wave audits confirmed closed.

Verdict: TRACEABILITY_OK (engineering chain closed end-to-end). Only
remaining gap: PENDING_BASELINE, gated on CI minutes return + manual
workflow_dispatch on bench-baseline-update.yml.
2026-05-18 14:15:04 +08:00
EdisonJwa ed3c01bcb9 chore: ignore SDD-120 bench-harness root outputs (current.json, report.md)
Canonical baseline lives at crates/chanora_audio/benches/baselines/x86_64-unknown-linux-gnu.json
and is updated only via the bench-baseline-update.yml workflow.
2026-05-18 14:05:11 +08:00
EdisonJwa b4cfb8174d docs(sdd-120): correct post-processor binary placement to examples/ (v0.9.16)
SDD-120 amendment v0.9.15 → v0.9.16 reflecting commit 3a7750a discovery.

The two post-processor tools (emit_baseline, compare_baseline) were originally
specified in SDD-120 §2 as residing in crates/chanora_audio/benches/, with
[[bin]] declarations in Cargo.toml. The implementation discovered that Cargo's
dependency resolver only routes [dev-dependencies] to [[test]], [[bench]], and
[[example]] targets — NOT to [[bin]] targets under src/bin/ or to ad-hoc paths.

Routing serde_json (required by both binaries) as a regular [dependencies] entry
to support [[bin]] placement would force it into the production cdylib build,
contradicting SDD-120 §10 release-artifact isolation: "no production telemetry
export" and "no benchmark surface in shipping artifacts." Confirmed by the user's
question about whether benchmark crates ship in the release artifact (they must
not).

The correct Cargo-idiomatic placement is examples/. Cargo auto-discovers files
under examples/ as example targets; they receive [dev-dependencies] routing; and
they are excluded from cargo build --release and from flutter build --release
artifacts by Cargo design.

Amendment scope (SDD-120 only):
- §1 item 4: [[bin]] → [[example]]; cargo run --bin → cargo run --example.
- §2: added item 5 with the rationale clause + Cargo dependency resolver
  explanation.
- §5 item 1: path benches/ → examples/; invocation flag --bin → --example.
- §5 item 4: dev-dependencies routing clause updated.
- §6 step 6/8: --bin → --example.
- §7 step 5: --bin → --example.
- §10 item 1: release-artifact-isolation bullet extended to cite the
  [dev-dependencies] Cargo design mechanism that enforces it.
- §11 verification matrix unchanged.
- "Allocated to" line + "Software units" list updated to reference examples/
  paths.

No semantic change to SDD-120: same harness, same metrics, same workflows, same
out-of-scope deferrals. The implementation at 3a7750a already lives at the
corrected paths; this amendment brings the SDD text into agreement with the
code.
2026-05-18 14:03:18 +08:00
EdisonJwa 7188a5a69d feat(perf,benchmark-infra): criterion bench harness + advisory CI workflows (SDD-120)
Implementation of SDD-120 §1-§8:

Bench harness (crates/chanora_audio/benches/):
- common.rs: deterministic synthetic audio (440 Hz sine, no RNG).
- realtime_capture.rs: bench_capture_alloc_count (dhat) +
  bench_capture_callback_wall_clock (criterion).
- opus_codec.rs: bench_opus_encode_latency + bench_opus_decode_latency
  (direct audiopus, not AudioHandler — SDD-120 §3 item 4).
- resampler.rs: bench_resampler_throughput across 44.1->48 /
  16->48 / 48->48 passthrough.

CI tooling (crates/chanora_audio/examples/):
- emit_baseline.rs: aggregates criterion estimates.json outputs
  into the SRS-217 baseline schema.
- compare_baseline.rs: applies SRS-219 tolerance, renders markdown
  table with 🟢/🟡/🔴 markers + yellow simpler-form realization per
  SDD-120 §8.

  Deviation from SDD-120 §2 / §5 / §7 placement: these tools live
  under examples/, not benches/ or src/bin/. Rationale: they must
  consume serde_json (a dev-only dep — production builds must not
  pull it). Cargo only resolves dev-dependencies for [[test]],
  [[bench]], and [[example]] targets; [[bin]] targets under
  src/bin/ see only regular [dependencies]. examples/ keeps the
  binaries out of the production dep tree while still giving them
  cargo run --example invocation. An SDD-120 amendment should
  reflect this.

Workflows (.github/workflows/):
- bench-advisory.yml: PR + push triggers; runs benches; posts a
  sticky PR comment via actions/github-script@v7; job status is
  always success (SRS-218 clause 4 — non-blocking).
- bench-baseline-update.yml: workflow_dispatch only; runs benches;
  opens PR via peter-evans/create-pull-request@v6 (sole writer of
  the SAD-089 baseline JSON).

Cargo.toml additions ([dev-dependencies] only — verified excluded
from --release builds): criterion 0.5, dhat 0.3, serde_json 1.

Source-code seam: minimal pub-but-#[doc(hidden)] bench_seam module
in chanora_audio (engine.rs + lib.rs re-export) so the criterion
bench harness can construct a CaptureState and drive
CaptureState::ingest without re-implementing the engine (SDD-120
§3). Non-iOS targets only — CaptureState itself is iOS-gated.

Initial baseline seed: crates/chanora_audio/benches/baselines/
x86_64-unknown-linux-gnu.json = {}. compare_baseline handles the
missing-baseline case gracefully and emits a 'no red markers'
report; the first manual dispatch of bench-baseline-update.yml
after merge establishes the real values.

Out of scope per SDD-120 §10: production telemetry export,
build-failing hard CI gate, multi-host benchmarking, IDE
integration, Dart-side bridge round-trip bench.

Verification:
- cargo check --workspace --all-targets: PASS.
- cargo bench --bench realtime_capture --no-run: PASS.
- cargo bench --bench opus_codec --no-run: PASS.
- cargo bench --bench resampler --no-run: PASS.
- cargo build --example emit_baseline --example compare_baseline
  -p chanora_audio: PASS.
- cargo test --workspace: 106 passed, 0 failed, 3 ignored — no
  regression from prior count.
2026-05-18 13:52:15 +08:00
EdisonJwa 575a6cbc5c docs(perf,benchmark-infra): authorize realtime audio benchmark + advisory CI (SysRS-307..309 / SysDes-156..158 / SRS-216..219 / SAD-088..091 / SDD-120)
Author the full SysRS -> SysDes -> SRS -> SAD -> SDD chain for the benchmark infrastructure authorized by the Option B product decision (Dimensions 1 + 2-advisory; Dimension 3 telemetry export deferred to P1; build-failing hard CI gate deferred until baseline maturity).

SysRS v0.9.10 adds:
- SysRS-307: maintained numeric performance baselines for the realtime audio path (allocations per callback after warmup, callback wall-clock, Opus encode/decode latency, resampler throughput).
- SysRS-308: advisory CI regression reporting on PR + merge to default; non-blocking semantics.
- SysRS-309: explicit declared tolerance window.

SysDes v0.9.8 adds:
- SysDes-156: benchmark coverage allocated to SE-13 (Audio Subsystem).
- SysDes-157: CI advisory-reporting integration allocated to SE-18 (Deployment).
- SysDes-158: per-metric tolerance table (zero / +20% p95 / +15% mean / -10% throughput).

SRS v0.9.9 adds:
- SRS-216: realtime audio benchmark instrumentation in chanora_audio/benches/.
- SRS-217: baseline storage format (JSON with metric/value/unit/host_arch/toolchain/git_sha/timestamp).
- SRS-218: CI advisory workflow with non-blocking semantics.
- SRS-219: tolerance window binding + merge-base comparison methodology.

SAD v0.9.9 adds:
- SAD-088: chanora_audio criterion bench harness (extends SAD-034).
- SAD-089: baseline JSON path pinned to crates/chanora_audio/benches/baselines/x86_64-unknown-linux-gnu.json.
- SAD-090: advisory CI workflow file (.github/workflows/bench-advisory.yml).
- SAD-091: manual-trigger baseline-update workflow (sole writer of SAD-089).
- Yellow-marker semantics pinned at SAD: 50%-of-tolerance trending detection.

SDD v0.9.15 adds:
- SDD-120: criterion 0.5 + dhat 0.3 dev-deps; three bench files (realtime_capture, opus_codec, resampler) + common.rs; two post-processor binaries (emit_baseline, compare_baseline); two GitHub Actions workflow YAMLs; simpler-form yellow-marker realization (baseline-only comparator).

Out of scope (deferred):
- Dimension 3 production telemetry export (P1).
- Build-failing hard CI gate (post-baseline-maturity).
- Multi-host benchmarking (Linux x86_64 only).
- Dart-side flutter_rust_bridge round-trip benchmark.

Implementation follows in a separate commit per the no-huge-commit guideline.
2026-05-18 13:36:01 +08:00
EdisonJwa d13b56d379 perf(audio): pre-allocate capture scratch buffers to avoid realtime-thread Vec allocs (SDD-094)
The capture cpal callback (CaptureState::ingest) ran two heap
allocations per callback on the realtime audio thread:

  1. engine.rs:1196-1202 — fresh `mono: Vec<f32>` for the downmix
     output, once per cpal callback (50–100 Hz).
  2. engine.rs:1217-1218 — `pcm_accum.drain(..FRAME_SAMPLES).collect()`
     building a fresh Vec<f32> of 960 samples per Opus frame.

Both sites mirror the pattern already fixed for the output side at
engine.rs:1389-1397, where allocating per callback on glibc malloc
was correlated with user-perceptible audio popping. The output-side
fix replaced the per-callback allocation with a pre-allocated
`scratch` Vec that is cleared and resized in place; this commit
applies the same template to the capture side.

Changes:
- Add `mono_scratch: Vec<f32>` and `frame_scratch: Vec<f32>` to
  CaptureState. Initialised with Vec::with_capacity(4096) and
  Vec::with_capacity(FRAME_SAMPLES=960) respectively in
  CaptureState::new.
- Replace the downmix Vec construction with in-place push into
  `self.mono_scratch`; `clear()` retains capacity across callbacks.
- Replace the drain().collect() with `self.frame_scratch.extend(
  self.pcm_accum.drain(..FRAME_SAMPLES))`; same capacity-retention.
- The resampler call uses std::mem::take to swap the scratch buffer
  out for the duration of the &mut self call, then moves it back —
  the backing allocation is preserved across callbacks.

Algorithm semantics are unchanged: same downmix arithmetic, same
clamp loop, same Opus encode call sequence. Only the storage
strategy differs.

Out of scope (intentionally not touched):
- Android audio path (android_voice_unit.rs, mobile_voice_backend.rs):
  researcher constraint C-4 — the Android cpal data path is mid-
  migration and being replaced.
- Output callback at engine.rs:1409+: the only obvious per-callback
  allocation there (`scratch`) was already fixed; a fuller audit
  is a separate scope decision.
- The `scratch` buffer at engine.rs:1389-1397 — already correct.

Verification:
- cargo check --workspace --all-targets: passes.
- cargo test --workspace: 106 passed / 0 failed / 3 ignored.
- cargo clippy --workspace --all-targets: no new lints introduced;
  the one warning inside the edited region (clamp-like pattern at
  line 1266) was pre-existing on the copied clamp loop.
2026-05-18 13:01:52 +08:00
EdisonJwa 5aa51c310f docs(p0): SysRS/SysDes/SRS/SAD/SDD/Verification + traceability for Android P0 reconciliation
Full P0 Android documentation chain:

- SysRS: API 24 → API 28 reconciliation per DEC-004 (SysRS-288);
  add SysRS-305 (Android in-call audio mode), SysRS-306
  (RECORD_AUDIO runtime timing).
- SysDes: SysDes-152 (in-call audio mode subsystem), SysDes-153
  (RECORD_AUDIO permission flow), SysDes-154 (Android voice audio
  backend), SysDes-155 (macOS runtime baseline).
- SRS: SRS-187 → API 28; add SRS-208 (in-call audio mode), SRS-209
  (RECORD_AUDIO + listen-only fallback), SRS-210..215 (Android voice
  audio backend latency/preset/AEC/usage/sharing/foreground service);
  retarget SysDes anchors from generic SysDes-135 to SysDes-152/153/154.
- SAD: SAD-063 refreshed (API 28); add SAD-084 (audio mode
  controller), SAD-085 (permission adapter with listen-only),
  SAD-086 (foreground service), SAD-087 (macOS runtime baseline);
  formalize cross-cutting + platform-specific allocation pattern
  in §24.1.
- SDD: expand SDD-028 (BackIntentService); refresh SDD-073 (build
  config); add SDD-105 (JNI bootstrap), SDD-106 (permission
  requester), SDD-107 (foreground service), SDD-108 (audio mode
  controller), SDD-109 (AAB pipeline), SDD-110 (PTT capability),
  SDD-111..116 (Android voice audio backend), SDD-118 (Android
  bridge build automation), SDD-119 (iOS/macOS bridge build
  automation back-fill).
- Verification: create android-p0-acceptance.md TC-1..TC-18; add
  SWE4-UV-040..052, SWE5-IV-016..026, SWE6-SV-018..030,
  SYS4-SIV-015 strengthened + SYS4-SIV-017/018.
- Governance: traceability matrix v0.9.9 with end-to-end chain
  closure; DEC-032 documents the temporary abiFilters reduction to
  arm64-v8a only and its restore-by gate.

Trace: full chain SysRS → SysDes → SRS → SAD → SDD → Code → Verification.
2026-05-18 12:48:28 +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
EdisonJwa 56222d190e feat(audio): clamp transmit selector on RECORD_AUDIO permission state (SDD-106 §6)
Per SDD-106 §6 add a permission-state clamp to TransmitModeSelector.
When RECORD_AUDIO is Denied or PermanentlyDenied the transmit gate
is forced false regardless of PTT or voice-activity state; on
Granted the clamp releases and normal transmit decisions resume.
The clamp takes precedence over PTT and hard_mute in the decision
ordering documented inline.

Three new tests cover the clamp behavior, the release-on-grant
transition, and the non-RECORD_AUDIO ignore path.

Trace: SDD-106 §6, SRS-209.
2026-05-18 10:56:17 +08:00
EdisonJwa 78190c0694 feat(audio,android): wire engine + AudioManager JNI through ModeStack (SDD-108 §2)
Replace the prior one-shot android_engage_voice_communication call
with a ModeStack-mediated acquire/release pair. AudioEngine snapshots
the system audio mode on first acquire via android_get_audio_mode()
and restores it on last release via android_set_audio_mode(prior).
MODE_IN_COMMUNICATION (3) is engaged across the voice-session lifetime
per SDD-108.

Includes the Android AudioManager getMode/setMode JNI helpers
(placed in chanora_audio::engine alongside the existing JNI surface)
and the small ptt.rs touch needed for the SDD-108 ID-tag on the
existing tests.

Trace: SDD-108, SDD-115.
2026-05-18 10:53:18 +08:00
EdisonJwa 76c6d1d40c feat(audio,android): add MobileVoiceAudioBackend + AndroidVoiceUnit (oboe-rs)
Add the cross-platform MobileVoiceAudioBackend trait, plus the
Android implementation AndroidVoiceUnit backed by oboe-rs 0.6.x.
AndroidVoiceUnit owns AAudio stream setup with VoiceCommunication
usage/preset, performance-mode LowLatency request, sharing-mode
Exclusive best-effort, hardware AEC/NS/AGC engagement via JNI, and
the diagnostics snapshot publish path used by SDD-116 evidence
collection.

Cargo.toml: adds oboe = "0.6" under the Android target.

Trace: SDD-111, SDD-112, SDD-113, SRS-210, SRS-211, SRS-212, SRS-213,
SRS-214.
2026-05-18 10:38:19 +08:00
EdisonJwa da0b208075 feat(audio): add ModeStack pure refcount helper (SDD-108)
Introduce ModeStack, a pure-Rust refcount-composable wrapper for
Android audio-mode acquire/release with prior-mode snapshot. Per
SDD-108 §1/§2 the engine snapshots the system audio mode on first
acquire and restores it on last release; composed acquires are
no-ops while the mode is held.

ModeStack is panic-free; release-on-zero returns AlreadyReleased
rather than panicking. Six SWE4-UV-045-tagged unit tests cover the
acquire/release semantics on the host target.

Trace: SDD-108, SWE4-UV-045.
2026-05-18 10:30:25 +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