Commit Graph
100 Commits
Author SHA1 Message Date
Edison Jwa 370dd37a22 docs: mark test tools and platform eval complete (TODO-024,044,047)
TODO-024: leave as-is (single-crate cfg gates correct architecture).
TODO-044: audio-test tool implemented. TODO-047: protocol-probe tool implemented.
2026-06-11 21:59:16 +09:00
Edison Jwa 523eafd4d7 docs: evaluate platform backend extraction — leave as-is (TODO-024)
Single-crate with module-level cfg gates is correct architecture.
Splitting would add dependency complexity with no compilation benefit.
2026-06-11 21:58:32 +09:00
Edison Jwa 6c00fae1cf feat(tools): add protocol probe and audio test binaries (TODO-044,047)
Protocol probe: connects to server, reports capabilities, channels,
clients. Audio test: DSP pipeline benchmark with configurable params.
2026-06-11 21:58:26 +09:00
Edison Jwa 11a2541042 docs: mark TSDNS, file transfer verified complete (TODO-066,067)
Both features already fully implemented. TSDNS in chanora_resolver,
file transfer in chanora_core + chanora_cache.
2026-06-11 21:45:59 +09:00
Edison Jwa fe3da41e41 docs: add TSDNS/file transfer designs, review p256 PR draft (TODO-057,066,067)
TSDNS: verified fully implemented in chanora_resolver (port 41144,
magic bytes, TCP query). File transfer: verified fully implemented
for avatars/icons (ftinitdownload, TCP data, cacache). p256 PR draft
reviewed and corrected (function path, probability figure).
2026-06-11 21:44:31 +09:00
Edison Jwa 602eedc029 fix(protocol,bridge): wire nickname validation to connect (TODO-045)
Add validate_nickname() to ProtocolClient::connect() and bridge
connect(). Trim whitespace, reject empty, truncate to 30 chars.
Re-export validate_nickname from protocol and core.
2026-06-11 21:44:03 +09:00
Edison Jwa 020218a7a1 docs: add badge fetching design, update verified items (TODO-042,058,062,065)
Badge fetching design with Protobuf parsing, 24h cache refresh, cacache
integration. Mark TODO-042 verified, TODO-058 monitoring, TODO-062 complete.
2026-06-11 21:30:27 +09:00
Edison Jwa 1e774035d1 docs(references): backfill YaTQA anti-flood action table (TODO-061)
Fill in 52 anti-flood action point values from category summary.
All entries now have values. Zero-cost through extreme (158) tiers.
2026-06-11 21:30:05 +09:00
Edison Jwa acc1450904 docs: update master todo list with Wave 3-5 status (TODO-026,042)
Mark TODO-026 as leave-as-is with reasoning. Mark TODO-042 as verified
complete. Update all Wave 3-5 items with completion status.
2026-06-11 21:07:29 +09:00
Edison Jwa 72ded4e011 docs: add test tool designs and iOS test plan (TODO-020,043,044,046,047)
Design documents for event replay, audio processing test, audio
loopback test, and protocol probe tools. iOS audio session test
plan with 28 scenarios and 3-phase automation approach.
2026-06-11 21:04:45 +09:00
Edison Jwa c04aaf4a51 docs(flutter): add dart doc comments to core services and widgets (TODO-085)
Add dart doc comments to poke_notification_service, poke_preferences_service,
link_trust_service, voice_settings, snapshot_view covering public API.
2026-06-11 21:03:19 +09:00
Edison Jwa c4b8732bd7 docs: add upstream PR draft, benchmarks, web client assessment (TODO-057,072,073)
p256 PR draft ready for ReSpeak/tsclientlib submission.
Performance benchmarks document with tsclientlib baselines and
Chanora current metrics. Web client assessed as infeasible without
UDP proxy infrastructure.
2026-06-11 20:55:20 +09:00
Edison Jwa 7968f90f7d docs: evaluate tsdeclarations codegen, map permissions, verify encoding (TODO-063,068,069)
tsdeclarations eval: direct codegen redundant (already via tsproto),
but permission ID generation and error code enum have high value.
Permission mapping: 253 TS3 permissions identified, 7 P1 for chat/moderation.
Encoding verification: tsclientlib uses strict UTF-8, UCS-2 is edge case.
2026-06-11 20:54:40 +09:00
Edison Jwa 508fa8b408 ci: add Linux multi-distro matrix and coverage reporting (TODO-052,053)
Add Docker-based Linux build matrix (Ubuntu, Fedora, Arch) for
platform-specific issue detection. Add cargo-llvm-cov coverage
job with artifact upload.
2026-06-11 20:44:56 +09:00
Edison Jwa 00e3fa7ad5 fix(flutter): request notification permission proactively (TODO-041)
When user enables poke notifications, call requestPermission()
immediately so OS prompt appears on first enable, not lazily.
2026-06-11 20:44:17 +09:00
Edison Jwa ef19f4e7ce feat(flutter): persist voice settings and per-user volume (TODO-039,040)
Add SharedPreferences persistence for transmit mode, release tail,
input/output device. Per-user volume/mute persisted across sessions.
2026-06-11 20:43:14 +09:00
Edison Jwa 7ab50a4eb5 refactor(flutter): split main.dart into screens/ directory (TODO-023)
Extract BetaHome (2374 lines) to screens/home_screen.dart and
LiveDiagnosticsDialog (97 lines) to screens/diagnostics_dialog.dart.
main.dart reduced from 2910 to 255 lines. No behavioral changes.
2026-06-11 20:42:38 +09:00
Edison Jwa 0c8efa38f1 fix(protocol): add input validation per SRS-094 (TODO-045)
Add validate_and_truncate(), validate_nickname(), validate_message(),
validate_poke_message(), validate_channel_name() with length limits.
Wire to send_text_message in adapter. 14 new validation tests.
2026-06-11 14:49:24 +09:00
Edison Jwa af823da543 feat(protocol): add client-side anti-flood awareness (TODO-064)
Implement FloodTracker with server-configurable thresholds from
YaTQA §5.1 model: 0.5s tick interval, point costs per operation,
NearLimit/CommandBlocked/IpBlocked risk levels. Defaults from
TeaSpeak (tick_reduce=25, command_block=150, ip_block=300).
2026-06-11 14:49:19 +09:00
Edison Jwa b944bd89d7 refactor(protocol): use lost() helper method in adapter (TODO-025 cleanup)
Replace ProtocolError::Lost(msg.to_string()) with ProtocolError::lost(msg)
for consistent error construction across the protocol crate.
2026-06-11 14:42:39 +09:00
Edison Jwa 475f6e0603 docs: verify DNS/reconnect, revise encoding scope, document protocol traps (TODO-059,062,063,070)
Mark TODO-059 (auto-reconnect) and TODO-062 (DNS chain) as verified
complete with evidence. Revise TODO-063 from implementation to
verification task (tsclientlib handles encoding). Create protocol
implementation traps doc with 13 traps from YaTQA/ReSpeak/TeaSpeak.
2026-06-11 14:42:32 +09:00
Edison Jwa 008128defc docs(audio): improve doc comments on key modules (TODO-031)
Add/improve dart doc comments for frame converters, AudioEngineConfig,
VadOutput variants, WebRtcFallbackVad, SileroOnnxVad, AppleCoreMlVad.
2026-06-11 13:31:12 +09:00
Edison Jwa dd6fa6121e feat(bridge,core): add is_hard_muted and get_output_gain readback (TODO-037)
Add bridge read-back functions for hard-mute state and output gain.
Add core output_gain() getter. Document feature gaps in SRS.
2026-06-11 13:30:52 +09:00
Edison Jwa 8dbe767d4f docs(architecture): update SysDes, SDD, SAD with missing modules and SysRS allocations (TODO-028,034,035)
SysDes: add SysRS-310 allocation, update coverage statement.
SDD: add SDD-MOD-015/016/017 for resolver/prefetch/cache, update engine split.
SAD: add Cache component to architecture table and diagram.
2026-06-11 13:25:21 +09:00
Edison Jwa f509c370b3 docs: add branch management workflow guide (TODO-088)
Document branch naming conventions, git worktree setup, PR stacking
patterns, and local CI mirroring recommendations.
2026-06-11 12:25:28 +09:00
Edison Jwa 2948c029d0 test(flutter): add 34 widget tests for voice_bar, connect, settings (TODO-017)
Voice bar: 12 tests covering modes, PTT badge, talk power, callbacks.
Connect widgets: 12 tests covering form, bookmark list, sanitization.
Voice settings: 10 tests covering result state, clamping, mode coverage.
2026-06-11 12:25:22 +09:00
Edison Jwa 40883c40c8 refactor: reduce map_err boilerplate with context helper methods (TODO-025)
Add per-crate variant_ctx() helper methods to StorageError, BlobCacheError,
ProtocolError, and BridgeError. Replace 78 .map_err(|e| format!(...))
closures with concise context calls. Identical error messages preserved.
2026-06-11 12:25:00 +09:00
Edison Jwa 1efeaac19d refactor(audio): split engine.rs into engine/ module directory (TODO-022)
Split monolithic engine.rs (3,244 lines) into focused modules:
mod.rs (1,106), capture.rs (515), render.rs (176), lifecycle.rs (1,149).
Capture and render are desktop-only (cfg-gated). No behavioral changes.
All public API paths preserved.
2026-06-11 12:05:54 +09:00
Edison Jwa 6d2405f67a test(audio): add poisoned-mutex survival tests (TODO-016)
Verify mutex recovery produces usable inner value after poisoning.
5 tests covering value preservation, mutable recovery, Arc clone,
and snapshot-through-guard patterns.
2026-06-11 12:05:44 +09:00
Edison Jwa 01a4a9ed28 docs: add README files to 9 crates and update verification plan (TODO-030,036)
Add purpose, architecture, and public API summary to each crate
README following chanora_resolver pattern. Update verification master
plan with new evidence sources and entry/exit criteria.
2026-06-11 11:09:12 +09:00
Edison Jwa 93f4608250 ci: add Android/Windows/macOS builds, xcodebuild archive, clippy gate (TODO-048,049,050,051)
Add Android multi-ABI build (arm64-v8a, armeabi-v7a, x86_64) with
cargo-ndk. Add Windows and macOS cargo-check jobs. Add xcodebuild
archive verification to iOS release build. Make clippy a blocking
PR gate by removing continue-on-error.
2026-06-11 11:06:06 +09:00
Edison Jwa e14dd73570 test(protocol,core,bridge): add 83 unit tests for untested modules (TODO-015,018,019,021)
Protocol DTO: 32 serde roundtrip + edge case tests.
Core events: 27 event construction + variant coverage tests.
Bridge: 24 error mapping + DTO roundtrip tests.
Add serde_json dev-dependency to protocol and bridge crates.
2026-06-11 11:05:11 +09:00
Edison Jwa 2ab8d5aae2 docs(references): add Chanora relevance, backfill placeholders, cross-reference (TODO-079,080,081,083,084)
Add TeaSpeak-to-Chanora SRS mapping and divergence points.
Backfill YaTQA variable parameters, anti-flood actions, and notify
events with structured placeholders. Add error code cross-reference
between YaTQA and ReSpeak sources.
2026-06-11 10:12:35 +09:00
Edison Jwa 78ecafcc2c docs: update implementation status and fix traceability coverage (TODO-027,029)
Update implementation-status to 2026-06-11: add poke notifications,
file transfer, cache, adaptive layout, VAD, voice controls coverage.
Correct traceability matrix coverage statement for sections 14-19.
2026-06-11 10:12:27 +09:00
Edison Jwa 8c9eba15c3 chore: convert TODO/FIXME to tracked references (TODO-008)
Convert all remaining raw TODO/FIXME/HACK comments to TRACKED()
references cross-linked to master todo list items. No behavior changes.
2026-06-11 10:05:14 +09:00
Edison Jwa 292617f8e8 refactor(core,storage): deduplicate code patterns (TODO-014)
Extract PttCapability event construction helper in events.rs.
Extract ensure_dir() helper in chanora_storage. Add TODO(refactor)
annotations for patterns requiring shared crate or API changes.
2026-06-11 10:05:06 +09:00
Edison Jwa 484522cbb6 refactor(flutter): extract shared AudioProcessingPanel widget (TODO-013)
Deduplicate audio processing toggle UI from voice_compact.dart and
voice_settings.dart into shared AudioProcessingPanel widget in
voice_settings_controls.dart. Centralize platform detection getters
in voice_platform.dart.
2026-06-11 10:04:58 +09:00
Edison Jwa ecdd68eff2 refactor(flutter): deduplicate AudioProcessingConfig construction (TODO-012)
Extract 4 identical BridgeAudioProcessingConfig constructions into
single shared construction, computing only platform-specific fields
in branches.
2026-06-11 09:47:02 +09:00
Edison Jwa b2937da726 fix(protocol,storage): platform-aware client version and Android stub (TODO-010,011)
pick_client_version() now returns correct version per platform via
cfg(target_os). keyring_load() on unsupported platforms returns
StorageError instead of silently returning Ok(None).
2026-06-11 09:46:57 +09:00
Edison Jwa b20e6b663a fix(audio): replace Mutex unwrap with poisoned-mutex recovery (TODO-006)
Replace 42 .lock().unwrap() calls with .unwrap_or_else(|e| e.into_inner())
across 7 files. Poisoned mutex recovery prevents panics in realtime audio
callbacks. Add SAFETY comment to WebRtcFallbackVad Send impl (TODO-007).
2026-06-11 09:46:51 +09:00
Edison Jwa 13c7648a65 docs: link orphaned superpowers docs and track Flutter bugs (TODO-033,087)
Add docs/superpowers/README.md as index for plans and specs.
Add docs/references/flutter-upstream-bugs.md tracking 4 known
Flutter framework workarounds.
2026-06-11 09:40:08 +09:00
Edison Jwa 328776622f docs(references): fix formatting issues (TODO-074,075,076,077,078,082)
Fix malformed MIFCOM badge table row, replace section numbering
placeholder, split respeak reference into Part I/II, add version
notes for teaspeak contradictions, fix truncated YaTQA error codes.
2026-06-11 09:40:03 +09:00
Edison Jwa 7dbf461262 refactor(audio,state): remove dead code (TODO-001,002,004)
Remove unused AudioFrame10ms/20ms structs, disable_failed_vad_backend(),
and ServerState dead accessors (replace_from_snapshot, channel_count,
client_count). Update tests to use .channels().count()/.clients().count().
2026-06-11 09:39:57 +09:00
Edison Jwa 3b13a7edb4 docs: comprehensive codebase analysis and reference documentation
Add full codebase analysis report, issue history, test environment
requirements, external research (TeaSpeak/ReSpeak/YaTQA), and offline
protocol references (ReSpeak ts3protocol, TeaSpeak, YaTQA). Add master
TODO list with 88 items across 11 categories. Add missing LICENSE files.

Branch: docs/codebase-analysis-v2
2026-06-11 09:23:10 +09:00
Edison Jwa 89bbfa1e18 fix(ios): activate AVAudioSession before connect to fix voice
Root cause: OSStatus 561145187 (!rec) - AVAudioSession was not in
.playAndRecord mode when VoiceProcessingIO tried to start.

Two changes:
1. Activate session BEFORE rust.connect() so it's ready when auto-join runs
2. Defensive activation in Connected handler as backup

This fixes both the !rec error and the subsequent 'voice_in already taken'
error that occurred after auto-join consumed voice_in on failure.
2026-06-10 18:38:11 +09:00
Edison Jwa 0a6ef55937 fix(ios): unblock iOS Debug builds + regenerate FRB for file transfer API (#41)
* fix(ios): preserve Silero VAD symbols in Flutter Debug builds

Flutter Debug builds split user code into a sibling Chanora.debug.dylib
alongside a minimal launcher executable; the six @_cdecl symbols
chanora_silero_vad_* land in the dylib, not the main binary. Two
problems were hiding behind that:

1. Debug.xcconfig only carried -exported_symbol flags, missing the
   load-bearing -u force-undefined flags Release.xcconfig already had.
   Without -u, the linker dropped the Swift @_cdecl symbols (no Swift
   caller exists) before -exported_symbol could re-export them, so the
   dylib shipped without the VAD entry points.

2. verify_silero_exports.sh inspected only ${EXECUTABLE_PATH}, which in
   Debug builds is the launcher stub. Even with the symbols correctly
   landing in the debug dylib, the script falsely reported them missing.

Mirror Release.xcconfig's -u + -exported_symbol pair in Debug.xcconfig
and teach the verify script to prefer Chanora.debug.dylib when it sits
next to the launcher. Release builds are unaffected (single fat binary
remains the inspected target).

* build(flutter): regenerate FRB bindings for file transfer API

Run flutter_rust_bridge_codegen against the post-PR-#40 chanora_bridge
to emit typed Dart bindings for initCache, downloadAvatar, downloadIcon,
clearFileCache, and fileCacheSize. Replaces the dynamic-cast +
NoSuchMethodError-swallowing shims that lib/src/rust/api.dart shipped
as a pre-regen safety net.

Functional change: downloadAvatar/downloadIcon now return Uint8List?
(was List<int>?) directly through the typed dispatch table. Existing
consumers stay source-compatible because Uint8List extends List<int>;
no call sites needed updates.

* fix(ios): set ITSAppUsesNonExemptEncryption to false

App uses only standard/exempt encryption (HTTPS, system-provided crypto);
declaring exempt status removes the App Store export-compliance prompt
at every TestFlight/release upload.
2026-06-10 15:20:59 +09:00
Edison Jwa aa796d7395 feat: file transfer system (avatar/icon download with cacache) (#40)
* docs(architecture): add file transfer design, research, and implementation plan

* feat(cache): add chanora_cache crate with cacache-backed blob cache

- New chanora_cache crate: content-addressed blob store wrapping cacache
- BlobCache API: async put/get/remove/clear/total_size/evict
- Key validation: av_ prefix (32 hex chars), ic_ prefix (decimal digits)
- Cacache provides crash safety, SSRI integrity, content dedup
- Mtime-based eviction via cacache::list_sync + sort by timestamp
- 7 unit tests all passing
- Added to workspace members

* feat(protocol): add file download support for avatars and icons

- Add Request::DownloadFile variant with oneshot reply
- Add ProtocolClient::download_avatar(client_uid) and download_icon(icon_id)
- Track pending file downloads by FiletransferHandle
- Handle StreamItem::FileDownload: read bytes from TCP stream
- Handle StreamItem::FiletransferFailed: map to ProtocolError
- Add ProtocolError::FileTransfer(String) variant
- Add path helper tests for avatar/icon download paths
- No tsclientlib types leak across the adapter boundary

* feat(core): add blob cache wiring and avatar download orchestration

- Add chanora_cache dependency to Cargo.toml
- Add blob_cache field to ChanoraSession (Arc<Mutex<Option<BlobCache>>>)
- Add init_cache() method: creates BlobCache, runs eviction
- Add get_avatar() method: cache-first, download on miss, store in cache
- Add clear_cache() and cache_size() methods for cache management
- Add CoreError::Cache variant for BlobCacheError conversion
- Add avatar_cache integration test

* feat(bridge): add init_cache, download_avatar, and cache management functions

- Add init_cache(dir) bridge function
- Add download_avatar(avatar_hash, client_uid) bridge function
- Add clear_file_cache() and file_cache_size() bridge functions
- Map CoreError::Cache and ProtocolError::FileTransfer in BridgeError

* feat(flutter): add cache initialization wiring and avatar download shims

- Add wireCache() to app_bootstrap using getApplicationCacheDirectory()
- Call wireCache() after wireStorage() in main bootstrap flow
- Add Dart-side initCache and downloadAvatar wrapper shims in api.dart
- Update Cargo.lock for new chanora_cache dependency

* feat(core): FileTransferService with coalescing, throttling, negative cache

- New file_transfer module with FileTransferService struct
- Semaphore(2) throttles concurrent downloads
- In-flight HashMap coalesces duplicate avatar requests
- 5-min negative cache short-circuits ServerRejected misses
- ChanoraSession delegates get_avatar through the service
- connect/disconnect update shared protocol handle
- clear_cache/cache_size delegate to service
- 2 new unit tests (cached hit, negative cache)

* feat(core,bridge): add get_icon with coalescing and negative cache

- FileTransferService::get_icon() mirrors get_avatar pattern
- ChanoraSession::get_icon() delegates through FileTransferService
- Bridge download_icon() exposed for Flutter
- Dart downloadIcon() shim added
- Uses PREFIX_ICON (ic_<crc32u>) cache key format
- 1 new unit test (cached icon hit)

* fix(core,protocol): simplify store_protocol and add download size cap

- store_protocol: always write to shared Arc<Mutex<Option<ProtocolClient>>>;
  the FileTransferService holds the same Arc so it sees updates automatically
- read_download_bytes: reject downloads exceeding 10 MB to prevent
  malicious servers from causing OOM
2026-06-10 11:45:14 +09:00
Edison Jwa 08d7ace25d fix(audio,flutter): restore VAD on iOS and fix Android build
- Add iOS to voiceActivityTransmitAvailable — iOS has CoreML Silero
  VAD pipeline (AppleCoreMlVadWorker) but was excluded by DEC-030
  gating that predated the CoreML integration
- Inline deleted VadWorkerPolicy in android_voice_unit.rs — PR #37
  removed the enum from vad/mod.rs but missed updating Android
2026-06-10 09:18:32 +09:00
Edison Jwa ddd977796f Merge pull request #39 from EdisonJwa/refactor/remove-ios-raw-unit
refactor(audio): remove experimental iOS RemoteIO+WebRTC APM path
2026-06-10 00:53:43 +09:00
Edison Jwa 2b285491d0 refactor(bridge): remove SonoraExperimental from bridge API and regenerate FRB
Remove SonoraExperimental variant from BridgeIosVoiceProcessingMode
and collapse all match arms in the bridge config builder. Regenerate
flutter_rust_bridge bindings and update Podfile.lock.
2026-06-10 00:51:52 +09:00
Edison Jwa 413f247378 fix(audio): inline VAD worker policy for iOS capture callback
PR #37 inlined VadWorkerPolicy into the desktop capture path but missed
the iOS files. Instead of restoring the deleted types, inline the same
direct if-let-Some pattern into ios_voice_unit.rs (the only remaining
iOS backend) and permanently remove VadWorkerPolicy and
callback_vad_worker_policy from vad/mod.rs.

iOS uses Option<AppleCoreMlVadWorker> with &mut self (no Mutex), so the
inline is simpler than the desktop's try_lock pattern.
2026-06-10 00:47:30 +09:00
Edison Jwa 3f9ea4f7b8 refactor(audio): remove experimental iOS RemoteIO+WebRTC APM path
Delete ios_raw_unit.rs (538 lines) and all SonoraExperimental references
from the core audio crate. The experimental RemoteIO path that bypassed
Apple VPIO in favor of software WebRTC APM was never shipped and is no
longer needed. VPIO is the sole production iOS audio backend.

- Delete ios_raw_unit.rs entirely
- Remove Raw variant from IosVoiceBackend enum in engine.rs
- Remove SonoraExperimental from IosVoiceProcessingMode enum
- Simplify validate_for_ios() (single-variant enum, no mode check)
- Remove 2 SonoraExperimental validation tests
- Remove ios_raw_unit module declaration from lib.rs
- Remove include_str!-based debug_wav test for the deleted file
2026-06-10 00:47:09 +09:00
Edison Jwa 2f6d45fb04 feat(audio): desktop Silero ONNX VAD + Windows PTT modernization + MSVC CRT build fix (#37)
* feat(audio): add Silero ONNX VAD with WebRTC fallback

Introduce SileroOnnxVad and SileroOnnxVadWorker for desktop targets. The worker runs Silero v6 ONNX inference on a dedicated thread, accumulating 10 ms frames into the 512-sample 16 kHz input the model expects. Add VadOutput, VoiceActivityDetector trait, and WebRtcFallbackVad to provide a uniform VAD interface with graceful fallback when the ONNX model is unavailable. Wire the new VadBackend variants through AudioProcessingConfig and the snapshot stats so the bridge can report which detector is active.

* feat(audio): integrate desktop VAD worker into capture engine

Wire SileroOnnxVadWorker into the desktop capture path so voice activity can open the transmit gate before encoding. The capture callback now processes all audio through resample, downmix, and VAD unconditionally; transmit_active still gates Opus encoding.

Add new_desktop_audio_processing_state() to construct the config/stats/worker triple, and apply_desktop_vad_backend() to synchronously load or clear the worker on config changes. Override processing_backend to Noop for desktop so bridge diagnostics report the correct backend rather than the iOS-oriented PlatformVoiceProcessing default.

Includes review-driven cleanups: StreamConfig clone to deref per clippy, and a comment explaining why two try_lock calls on silero_vad_worker are structurally necessary (borrow checker requires the policy probe and the fallback path to not share a lock guard because mark_vad_fallback_active takes &mut self).

* fix(audio): modernize Windows PTT to current windows-rs API

Port the Raw Input plus low-level keyboard hook PTT backend to the newer windows-rs patterns: OptionalHandle, Result-returning CreateWindowExW, and None for CallNextHookEx. Replaces the old HHOOK(0) pointer casts. Add deterministic tests for mouse button 4 and 5 press and release driving the gate.

* build(windows): force MSVC release CRT for audiopus cmake builds

audiopus_sys calls cmake::build(opus_path), so downstream Cargo env cannot use cmake-rs Config::define() to override CMake's MSVC Debug CRT defaults. Point cmake-rs at a small wrapper that injects the policy and cache variables during configure while passing cmake --build, --version, and -E through unchanged. Keeps Opus Debug builds on Rust's release dynamic CRT (/MD) instead of CMake's default debug CRT (/MDd), which otherwise pulls in unresolved __imp__CrtDbgReportW symbols at test link.

Document that the iOS deployment target is intentionally absent from this file. It is enforced by tools/build-ios.sh and the Xcode project; setting it globally here would make native macOS cargo check runs try to link iPhone objects against the macOS SDK.

* build(flutter): update pubspec.lock after plugin additions

Regenerated lockfile reflecting the local_notifications and connectivity_plus plugin additions from the poke-notifications feature.

* fix(audio): address PR #37 review findings

Six fixes from independent PR review:

1. BLOCKER: Replace Windows-only cmake .cmd wrapper with cross-platform
   CMake env vars. Setting CMAKE=tools/cmake-msvc-release-crt.cmd
   globally broke non-Windows hosts because cmake-rs would try to
   execute a .cmd file on macOS/Linux. Instead, set
   CMAKE_POLICY_DEFAULT_CMP0091=NEW and CMAKE_MSVC_RUNTIME_LIBRARY=
   MultiThreadedDLL as env vars that CMake reads natively. MSVC-
   specific vars are safely ignored by GCC/Clang toolchains. Delete
   the now-unnecessary wrapper script.

2. IMPORTANT: Join the Silero worker thread in Drop instead of
   detaching it. The old code dropped the JoinHandle which detaches
   the thread; the new code calls handle.join() after closing the
   channel, ensuring the ONNX session is cleaned up before the
   worker is replaced during config changes.

3. IMPORTANT: Single-try_lock refactor of the capture VAD callback.
   The double try_lock (policy probe + send) is replaced by a single
   scoped try_lock that both probes availability and sends the frame.
   The guard is dropped before the fallback path, which needs &mut
   self for mark_vad_fallback_active. This also eliminates the
   VadWorkerPolicy enum and callback_vad_worker_policy function,
   whose behavior is now inlined into the callback.

4. IMPORTANT: Remove tracing from the realtime capture callback.
   mark_vad_fallback_active and sync_vad_backend emitted info!/warn!
   from the audio thread. Replace with silent atomic state
   publishing via SharedAudioProcessingStats; the bridge stats
   stream already exposes vad_fallback_active for diagnostics.

5. IMPORTANT: Defer ONNX model load outside the worker mutex.
   apply_desktop_vad_backend_to_worker now constructs the new worker
   before taking the lock, then swaps it in under a short hold.
   This prevents the realtime callback from being blocked during
   model I/O + thread spawn.

6. MINOR: Remove unused VadBackend import from vad/mod.rs after
   deleting the policy code.

* fix(audio): address PR #37 second-pass review findings

5-agent review found 5 blocking issues. All addressed:

1. BLOCKER: CMake env vars don't reach CMake cache. Restored .cmd wrapper
   but scoped to Windows MSVC targets only via [target.x86_64-pc-windows-msvc]
   and [target.aarch64-pc-windows-msvc] in .cargo/config.toml. Non-Windows
   hosts are unaffected.

2. BLOCKER: processing_backend normalized in set_audio_processing_config
   on desktop (cfg-gated override to Noop), mirroring startup default.

3. BLOCKER: Model-path reload was already wired via reload_audio_processing_config.
   Fixed misleading doc comment in core/lib.rs.

4. BLOCKER: DEC-030 updated to reflect desktop VoiceActivity enablement.
   Traceability docs (SRS, SysDes, SAD, SDD, implementation-status) updated.

5. Silero ONNX cfg narrowed to desktop-only (excludes macOS/Android).
   Cargo.toml ort dependency target cfg narrowed similarly.

6. Realtime callback debt documented as TODO at CaptureState::ingest.

* fix(audio): exclude ort dep on Android target

ort does not provide first-class Android prebuilts in our pin, mirror the
iOS/macOS exclusion so cargo metadata succeeds for android targets.

* test(audio): fix stale select_ptt_backend import in ptt_privacy

The helper moved out of the ptt_backends submodule onto the crate root;
update the integration test imports so the test compiles again.

* build(windows): scope MSVC release CRT cmake wrapper via Cargo [env]

Cargo's [target.<triple>] table only forwards a fixed allowlist
(linker, runner, rustflags, rustdocflags, ar), so setting CMAKE there
was silently dropped and audiopus_sys kept linking the debug CRT,
producing LNK4098 'MSVCRTD conflicts' and __imp__CrtDbgReportW errors
on x86_64-pc-windows-msvc test builds.

Move the override to Cargo's [env] table using cc/cmake-rs's
target-suffixed CMAKE_<triple> lookup (force=true, relative=true) so it
applies to MSVC targets only and not to host tooling. Add stdout
markers to the wrapper so its invocation is provable in cargo -vv logs.

Verified: cargo test -p chanora_audio --target x86_64-pc-windows-msvc
--lib --no-run now links cleanly; CMakeCache.txt records
CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL and CMP0091=NEW.

* fix(flutter): gate VoiceActivity transmit mode by platform support

VoiceActivity relies on the native VAD worker, which is only wired up
on Windows, Linux, and Android. Showing the option on iOS, macOS, or
web let users select a mode that silently never transmitted.

Add voiceActivityTransmitAvailable + transmitModeSegmentsFor() helpers
in voice_settings_controls.dart, hide the VAD row in voice_compact.dart
and drop the VAD segment from the settings dialog when unsupported.
Keep the legacy const transmitModeSegments for the existing widget test
and add two new tests covering the gated helper.
2026-06-09 20:47:16 +09:00
Edison Jwa 5c3dd70bba fix(ios-audio): activate session before voice joins (#38)
* fix(ios-audio): add voice join session coordinator

* fix(ios-audio): activate session before voice joins

* docs(ios-audio): align activation lifecycle comments

* fix(ios-audio): keep session active when already-in-channel

The 'already in channel' server response (code 0x0302) is treated as a
successful join by _onJoinChannel: the user stays in the channel and
local state is updated to reflect the joined target. But the underlying
voiceJoin call still raises BridgeError_ServerRejected, which the
joinVoiceChannelWithIosAudioSession helper used to interpret as a join
failure and deactivate the iOS audio session. Result: the UI shows the
user as joined while the audio session is dead and capture/playback
remain silent.

Add an isJoinSuccess predicate to the ordering helper. When the
predicate matches, the helper rethrows (so the caller can still run its
success-on-already-joined branch) without deactivating the session.
Wire _onJoinChannel to pass _isAlreadyInChannel as the predicate so the
0x0302 path keeps the session active.

Adds two regression tests covering the success-on-rethrow and the
predicate-false-still-deactivates paths.

* docs(security): regenerate license inventories

Cargo inventory: pick up chanora_resolver bump from 0.1.0 to
0.2.0-beta.1 so it matches the workspace; also adds a trailing newline
so 'cargo about generate' is idempotent in CI license-drift checks.

Flutter inventory: pick up flutter_local_notifications (+ platform
interfaces) and timezone pulled in by the prior notification
permission work.
2026-06-09 19:58:19 +09:00
Edison Jwa ef14c22300 Merge pull request #36 from EdisonJwa/docs/poke-without-message-design
Allow pokes without messages
2026-06-09 09:33:58 +09:00
Edison Jwa b841d3f3e4 docs(security): refresh license inventories 2026-06-09 07:30:23 +09:00
Edison Jwa eca77ece81 fix(chat): allow empty poke messages 2026-06-09 01:47:23 +09:00
Edison Jwa 3462de1eee fix(core): allow empty poke dispatch 2026-06-09 01:44:03 +09:00
Edison Jwa f66118f5bb docs: add poke-without-message design 2026-06-09 00:53:14 +09:00
Edison Jwa 3ef540ae37 Merge pull request #35 from EdisonJwa/feat/poke-notifications
feat: add poke notifications
2026-06-08 23:52:13 +09:00
Edison Jwa e0edcc89ac Merge pull request #34 from EdisonJwa/simplify-project-review
Maintainability continuation and Android smoke evidence
2026-06-08 23:46:37 +09:00
Edison Jwa 7922eabcf0 build(android): keep notification icon resource 2026-06-08 23:38:55 +09:00
Edison Jwa 82bfa0ea1f docs: remove trailing whitespace from continuation design 2026-06-08 23:36:05 +09:00
Edison Jwa dc52092654 docs: remove trailing whitespace from continuation design 2026-06-08 23:32:18 +09:00
Edison Jwa b565663645 feat(chat): route pokes through notifications 2026-06-08 22:56:52 +09:00
Edison Jwa 34a5247457 feat(ui): add poke notification settings dialog 2026-06-08 22:56:04 +09:00
Edison Jwa 9a5f82565d feat(l10n): add poke notification settings copy 2026-06-08 22:55:38 +09:00
Edison Jwa 409cd11c21 feat(flutter): persist poke notification preferences 2026-06-08 22:55:12 +09:00
Edison Jwa 4f1b85cf76 feat(flutter): add poke notification service 2026-06-08 22:54:46 +09:00
Edison Jwa b7cc4d2336 build(apple): declare notification permission usage 2026-06-08 22:54:18 +09:00
Edison Jwa 44f91a2ea4 build(android): configure local notifications 2026-06-08 22:53:53 +09:00
Edison Jwa cc6db18199 build(flutter): add local notification plugin 2026-06-08 22:53:29 +09:00
Edison Jwa 6af2ed9ab5 feat(flutter): regenerate poke strength bridge 2026-06-08 22:53:01 +09:00
Edison Jwa cf64274fe6 feat(bridge): expose poke strength to Flutter 2026-06-08 22:52:33 +09:00
Edison Jwa 5c7a4b64c9 feat(core): propagate poke strength events 2026-06-08 22:52:08 +09:00
Edison Jwa 31cf45ce35 feat(protocol): classify poke notification strength 2026-06-08 22:51:41 +09:00
Edison Jwa a644770488 docs: record Android verification evidence 2026-06-08 21:26:20 +09:00
Edison Jwa 5a1d902795 fix(audio): migrate Android JNI paths 2026-06-08 21:24:16 +09:00
Edison Jwa 901369b072 docs: align core split verification notes 2026-06-08 20:20:48 +09:00
Edison Jwa 57a4d9767b refactor: align bridge state resolver metadata 2026-06-08 20:20:18 +09:00
Edison Jwa 8c4f85ee70 refactor: reuse built-ins and shared helpers 2026-06-08 20:19:17 +09:00
Edison Jwa 7c341d42e5 fix(core,protocol): bound disconnect shutdown 2026-06-08 20:15:50 +09:00
Edison Jwa 8487acf167 docs: align review findings and verification gates 2026-06-08 19:52:16 +09:00
Edison Jwa 8606eb48c8 fix(audio): harden realtime callback paths 2026-06-08 19:40:03 +09:00
Edison Jwa d83539436e fix(voice): preserve mute owners and release touch ptt 2026-06-08 18:00:32 +09:00
Edison Jwa e4fdf8414a docs: add maintainability continuation plan 2026-06-08 17:04:29 +09:00
Edison Jwa 0f41993ed0 docs: add maintainability continuation design 2026-06-08 17:01:59 +09:00
Edison Jwa a0ff17b935 fix(voice,ios): scope AVAudioSession VoiceChat to call lifetime (#33)
Adopt a call-scoped VoIP audio session lifecycle so other apps' audio is
not stopped while Chanora is idle and the in-call session does not get
clobbered by media-server resets unrelated to voice.

AppDelegate.swift
- Set .ambient + .mixWithOthers as the idle baseline so the app does not
  hold a VoiceChat session when no call is active.
- Switch to .playAndRecord + .voiceChat + .mixWithOthers + .duckOthers
  on demand via the new chanora/ios_audio_session MethodChannel, and
  revert to .ambient on deactivate.
- Gate the media-services-reset rebuild on voiceSessionActive so a
  stray reset during idle no longer reactivates VoiceChat.

ios_audio_session_controller.dart (new)
- Thin Dart wrapper around chanora/ios_audio_session with activate/
  deactivate; no-op on non-iOS; swallows PlatformException to keep
  audio start/stop resilient to platform-side races.

main.dart
- Activate the iOS audio session on BridgeEvent_AudioStarted, deactivate
  on BridgeEvent_AudioStopped, fire-and-forget via unawaited().

ios_voice_unit.rs
- Add the 10 local bindings required by the render-callback closure
  preamble (wav_recorder_for_render, render_recorder_active,
  render_ref_len, render_ref_accum, cb_count, last_num_frames,
  num_frames_changes, callbacks_with_audio, callbacks_with_silence)
  so the iOS target compiles cleanly with the new lifecycle wiring.

Tests
- 5 unit tests in test/services/ios_audio_session_controller_test.dart
  cover activate/deactivate on iOS, no-op on non-iOS, and graceful
  PlatformException handling.

Docs
- SRS SRS-110 expanded to cover the call-scoped lifecycle invariant.
- SysDes mobile-voice row updated to reflect the MethodChannel and
  .ambient idle baseline.
- implementation-status-2026-05-28 voiceChat row flipped to done.

Verification
- flutter analyze: No issues found (2.8s)
- flutter test: 195 passed / 2 skipped / 0 failed
- cargo build -p chanora_audio --target aarch64-apple-ios: clean
- cargo build -p chanora_audio (macOS host): clean

Device QA matrix (Spotify-keeps-playing-while-idle, mix-during-call,
revert-on-call-end, media-services-reset-during-idle) remains pending
on physical hardware.
2026-06-08 06:02:12 +09:00
Edison Jwa 68a7892e19 fix(macos): address PR #31 review findings
- _showLocalNetworkDeniedSnackBar: wrap Process.run with unawaited()
  and .catchError() so a rejected future (e.g. macOS sandbox refuses
  fork, or 'open' is missing) cannot bubble into the Flutter zone
  as an unhandled exception. The synchronous try/catch was a no-op
  because Process.run only throws asynchronously.
- macos_permissions_service_test.dart: mirror the
  triggerLocalNetworkPrompt error-handling test with one for
  checkLocalNetworkAccess. Probe-path failures (NWConnection probe
  cannot establish, or Swift side throws) must fall back to cached
  state without crashing the caller.

Tests: 186 passed, 2 skipped. Dart analyze clean.
2026-06-07 23:36:31 +09:00
Edison Jwa e048b6b6bd fix(chat): propagate empty draft on target swap and dispose
Oracle re-review on PR #30 flagged that _ChatDetailViewState only
called onDraftChanged when _textCtl.text was non-empty. The empty
case is load-bearing: if the user restored a saved draft, deleted
the text, then switched target (or closed the panel), the parent's
draft map kept the stale entry and resurrected it on the next swap.

Fix: call onDraftChanged unconditionally in both didUpdateWidget
(target change) and dispose (tear-down), so the parent map learns
when a draft is now empty.

Adds a regression test exercising the restore-clear-swap sequence.
2026-06-07 23:30:00 +09:00
Edison Jwa dad633e381 fix(ui): address PR #30 review findings
- ViewportInfo.updateShouldNotify: compare layoutClass only
  (not width/height), avoiding unnecessary rebuilds on every
  resize frame within the same layout class.
- ChatPanel: use BorderDirectional(start:) for RTL support.
- ChatPanel: localize 'Close chat' tooltip via AppL10n.chatCloseAction.
- Inline panel snackbar: localize via AppL10n.chatPanelCollapsedHint.
  New en/zh ARB entries added.
- _saveCurrentDraft(): removed — it was a self-assignment no-op.
  Draft persistence relies on ChatDetailView's didUpdateWidget
  (fires onDraftChanged on target switch) and dispose (fires on
  panel tear-down), both of which already populate _chatDrafts
  correctly without an explicit save call.
- _handleInlineChatViewport layout snackbar: use AppL10n.
- Audio level-meter: switch from callback-count (% 3) to time-based
  gating (std::time::Duration::from_millis(33)), robust to cpal
  buffer-size or sample-rate changes. Remove level_decimation_counter.
- chat_panel_test.dart: add AppL10n.localizationsDelegates so the
  test resolves l10n keys.

Tests: 183 passed, 2 skipped. Dart analyze clean.
cargo test -p chanora_audio --lib: 125 passed.
2026-06-07 23:30:00 +09:00
Edison Jwa e9cd832828 docs(macos): clarify network.server entitlement justification
Oracle re-review nit on PR #27: cite Apple's App Sandbox semantics
explicitly. The macOS sandbox classifies any UDP bind() against a
local port as a 'server' operation (covered by network.server),
even when the socket is only used to sendto() a remote peer. This
is the bind()-then-sendto() pattern tokio's UdpSocket uses
internally for tsclientlib's outbound voice traffic. Correct the
sandbox log line to the actual deny string ('Sandbox: ... deny(1)
network-bind') and reference Apple's entitlement reference wording.
2026-06-07 23:27:46 +09:00
Edison Jwa c40705790a fix(audio,macos): address PR #27 review findings
- ios_voice_unit.rs: add producer_shutdown AtomicBool flag (macOS only).
  The macOS start path spawns a tokio producer task that holds clones
  of Arc<Mutex<AudioHandler>>, Arc<ArrayQueue<f32>>, and the output
  gain/muted atomics, then loops on a 20 ms tokio interval. Without
  a shutdown signal the task runs forever on engine stop/restart and
  leaks all four Arcs every cycle. Drop now stores 'true' on the
  flag; the producer checks it at the top of each tick and exits,
  releasing its clones within at most one 20 ms tick.

- macos/Runner/Release.entitlements: strengthen the existing
  justification comment for com.apple.security.network.server.
  Document the specific failure mode (tokio::net::UdpSocket::bind
  -> sandbox 'network-outbound deny' -> EPERM) and explain why
  network.client alone does not cover bind()-then-sendto. The
  entitlement is required, not over-broad.

cargo check (host + aarch64-apple-darwin): clean
cargo test -p chanora_audio --lib: 133 passed
flutter test: 186 passed, 2 skipped
dart analyze: clean
2026-06-07 23:27:46 +09:00
Edison Jwa 76fe8faa94 fix(audio,ios): gate output_underrun on !muted in refactored render callback
The PR #27 refactor moved the iOS render callback to a direct-fill
path with its own peak_i16 == 0 check, dropping the !muted gate
that PR #28 added to the pre-refactor callback. Without this
gate, every muted callback fires a false-positive
increment_output_underrun() because the downmix helper writes
silence (peak_i16 = 0) by design when muted.

Re-apply PR #28's gate to the refactored iOS path so this PR does
not silently reintroduce the bug PR #28 was opened to fix.

Verified:
- cargo test -p chanora_audio --lib: 133 passed, 0 failed
- cargo build -p chanora_audio --target aarch64-apple-ios: clean
2026-06-07 23:27:46 +09:00
Edison Jwa 5257b1e3ac chore(repo): gitignore .omo/ session directory 2026-06-07 23:27:46 +09:00
Edison Jwa 0d37550d43 chore(licenses): update inventory for windows-core 0.54 → 0.62 bump
Inventory regenerated after refactor(audio): share AudioHandler between iOS and macOS, bump deps (6214139), which dropped windows-core 0.54.0 (and its windows-result 0.1.2 transitive dep). Apache-2.0 crate count drops from 333 to 327. No license-class change.
2026-06-07 23:27:46 +09:00
Edison Jwa fe5dc1cda4 feat(macos): add macOS audio lifecycle MethodChannel
apps/chanora_flutter/macos/Runner/MacOSAudioLifecycle.swift (new): native MethodChannel handler for chanora/macos_audio_lifecycle. Observes Core Audio HAL default-input and default-output device property changes via AudioObjectAddPropertyListener; posts handleDefaultDeviceChange events with {role: input|output} payload. Mirrors the iOS chanora/ios_audio_lifecycle event surface minus the AVAudioSession-specific events (no interruption / no media services reset equivalents on macOS — no AVAudioSession).

apps/chanora_flutter/macos/Runner/MainFlutterWindow.swift: register MacOSAudioLifecycle next to MacOSPermissionsHandler in awakeFromNib. Closes the iOS/macOS asymmetry noted in SysRS-051.

apps/chanora_flutter/lib/services/audio_lifecycle_service.dart: add wireMacosAudioLifecycle() parallel to wireIosAudioLifecycle() / wireAndroidAudioLifecycle(). The current implementation captures and logs the events; the FRB function that triggers a VPIO re-bind on the engine is a follow-up. Event-shape mirrors the iOS side so a future caller can switch on platform without changing the dispatch shape.

apps/chanora_flutter/test/services/audio_lifecycle_service_test.dart: smoke test for wireMacosAudioLifecycle (4 tests pass, including the new one).
2026-06-07 23:27:46 +09:00
Edison Jwa 581353b6d2 docs(sdd): document VoiceActivity gate, macOS render cadence, iOS render cadence
docs/architecture/sdd.md: three new rows in the Audio Detailed Design table. 'VoiceActivity gate (capture-side)' documents voice_activity::VoiceActivityStateMachine — the 10 ms-cadence gate for TransmitMode::VoiceActivity with open-after (40 ms) / hangover (500 ms) / min-tx (200 ms) / weak-hold (30-100 frames) timers, plus live configure() re-clamping behaviour. 'macOS render cadence (producer + ring)' documents the 20 ms tokio producer task + crossbeam ArrayQueue ring with 100 ms prebuffer, the design chosen to decouple ingress quantums (20 ms Opus frames) from egress quantums (whatever VPIO asks for). 'iOS render cadence (direct-fill)' documents the direct-fill callback path with preallocated 4096x2 f32 scratch buffer and try_lock semantics (not blocking lock).

Closes the VoiceActivity / macOS-producer-ring / iOS-direct-fill doc gaps flagged in the PR #27 'Deferred' list.
2026-06-07 23:27:46 +09:00
Edison Jwa 1cf1a8f5a6 fix(macos): add network.server entitlement to release sandbox for UDP bind
apps/chanora_flutter/macos/Runner/Release.entitlements: add com.apple.security.network.server = true. The macOS App Sandbox treats every UDP bind() — including the ephemeral 0.0.0.0:0 that tsclientlib uses for outbound TS3 traffic — as a server operation. Without this entitlement UdpSocket::bind fails with EPERM and the TS3 connect never starts. Debug builds already had this entitlement (needed for flutter run hot-reload); release builds were missing it.

apps/chanora_flutter/macos/Runner/DebugProfile.entitlements: expand the existing network.server comment to document the dual rationale (flutter hot-reload + outbound UDP bind), so the entitlement's purpose is clear without spelunking through tsclientlib.
2026-06-07 23:27:46 +09:00
Edison Jwa d59da05f93 refactor(audio): share AudioHandler between iOS and macOS, bump deps
crates/chanora_audio/src/engine.rs: drop the macOS-specific event-queue producer/consumer path; macOS now uses the iOS-style direct AudioHandler::fill_buffer in the VPIO render callback. The shared AudioHandler is an Arc<Mutex<...>>; the realtime callback uses try_lock so it never blocks on the tokio decode task (see ios_voice_unit.rs render callback).

crates/chanora_audio/src/mobile_voice_backend.rs: update VoiceAudioParams cfg gates — handler is now the iOS/macOS/desktop shape (Arc<Mutex<AudioHandler<SessionAudioId>>>), event_producer is Android-only.

crates/chanora_audio/src/lib.rs: widen the audio_event_queue module visibility to test so the macOS-specific path can be exercised by the unit test suite.

Cargo.toml: bump cpal 0.17.3 -> 0.18.0, jni 0.21 -> 0.22.4, windows 0.54 -> 0.62, criterion 0.5 -> 0.8. Cargo.lock follows.
2026-06-07 23:27:46 +09:00