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
This commit is contained in:
Edison Jwa
2026-05-25 11:55:10 +09:00
parent 5c8c6df16b
commit b8df25a195
16 changed files with 1635 additions and 391 deletions
@@ -0,0 +1,133 @@
import 'dart:ffi';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:chanora_flutter/services/startup_dependency_check.dart';
void main() {
tearDown(() {
debugResetStartupDependencyCheck();
});
test('non-linux hosts skip startup dependency issues', () async {
debugResetStartupDependencyCheck(platformIsLinux: () => false);
final result = await checkStartupDependencies();
expect(result.issues, isEmpty);
});
test('missing SDL2 is reported as required on Debian-like systems', () async {
debugResetStartupDependencyCheck(
platformIsLinux: () => true,
libraryProbe: (candidate) => false,
fileExists: (_) async => false,
osReleaseProvider: () async => 'ID=ubuntu\nID_LIKE=debian\n',
resolvedExecutableProvider: () => '/opt/chanora/chanora_flutter',
currentDirectoryProvider: () => '/tmp',
);
final result = await checkStartupDependencies();
expect(result.hasIssues, isTrue);
final sdl = result.issues.firstWhere(
(issue) => issue.id == 'linux-sdl2-runtime',
);
expect(sdl.isRequired, isTrue);
expect(sdl.installHints.single.command, 'sudo apt install libsdl2-2.0-0');
});
test(
'missing ONNX runtime is reported as recommended when SDL2 is present',
() async {
debugResetStartupDependencyCheck(
platformIsLinux: () => true,
libraryProbe: (candidate) => candidate.contains('SDL2'),
fileExists: (_) async => false,
osReleaseProvider: () async => 'ID=fedora\n',
resolvedExecutableProvider: () => '/opt/chanora/chanora_flutter',
currentDirectoryProvider: () => '/tmp',
currentAbiProvider: () => Abi.linuxX64,
);
final result = await checkStartupDependencies();
expect(result.issues, hasLength(1));
final ort = result.issues.single;
expect(ort.id, 'linux-onnxruntime');
expect(ort.isRequired, isFalse);
expect(
ort.installHints.any(
(hint) =>
hint.command ==
'https://github.com/microsoft/onnxruntime/releases',
),
isTrue,
);
expect(
ort.installHints.any(
(hint) => hint.command == 'onnxruntime-linux-x64-<version>.tgz',
),
isTrue,
);
expect(
ort.installHints.any((hint) => hint.command.contains('ORT_DYLIB_PATH')),
isTrue,
);
expect(
ort.details,
contains(
'This machine needs the Linux x64 CPU archive (onnxruntime-linux-x64-<version>.tgz).',
),
);
},
);
test('bundle-local ONNX runtime clears the recommendation', () async {
debugResetStartupDependencyCheck(
platformIsLinux: () => true,
libraryProbe: (candidate) => candidate.contains('SDL2'),
fileExists: (path) async => path == '/opt/chanora/lib/libonnxruntime.so',
resolvedExecutableProvider: () => '/opt/chanora/chanora_flutter',
currentDirectoryProvider: () => '/tmp',
);
final result = await checkStartupDependencies();
expect(result.issues, isEmpty);
});
test('logging missing startup dependencies appends a log line', () async {
final tempDir = await Directory.systemTemp.createTemp(
'chanora-startup-log',
);
addTearDown(() => tempDir.delete(recursive: true));
final logFile = File('${tempDir.path}/chanora.log');
debugResetStartupDependencyCheck(logFilePathProvider: () => logFile.path);
const result = StartupDependencyCheckResult(
platformLabel: 'Fedora',
issues: [
StartupDependencyIssue(
id: 'linux-sdl2-runtime',
title: 'SDL2 runtime is missing',
summary: 'Audio playback needs SDL2.',
details: ['Install SDL2 and recheck.'],
severity: StartupDependencySeverity.required,
),
],
);
await logStartupDependencyIssues(result);
final text = await logFile.readAsString();
expect(text, contains('[startup_dependency_check]'));
expect(text, contains('platform="Fedora"'));
expect(
text,
contains('issues=[linux-sdl2-runtime:required:SDL2 runtime is missing]'),
);
});
}
@@ -31,14 +31,14 @@ void main() {
expect(state.agcEnabled, isTrue);
});
test('normalizes desktop VAD backend to Silero', () {
test('desktop VAD normalization keeps explicit WebRTC selections', () {
expect(
normalizedVadBackend(
rust.BridgeVadBackend.webrtcVad,
isWindows: true,
isLinux: false,
),
rust.BridgeVadBackend.sileroOnnx,
rust.BridgeVadBackend.webrtcVad,
);
expect(
normalizedVadBackend(
@@ -46,6 +46,14 @@ void main() {
isWindows: false,
isLinux: true,
),
rust.BridgeVadBackend.webrtcVad,
);
expect(
normalizedVadBackend(
rust.BridgeVadBackend.energyDebug,
isWindows: true,
isLinux: false,
),
rust.BridgeVadBackend.sileroOnnx,
);
});
@@ -103,6 +111,7 @@ void main() {
test('builds Windows/Linux software WebRTC APM config consistently', () {
final state = AudioProcessingConfigState.fromConfig(baseConfig)
..vadBackend = rust.BridgeVadBackend.webrtcVad
..nsEnabled = true
..aecEnabled = false
..agcEnabled = true;
@@ -127,7 +136,7 @@ void main() {
for (final config in [windowsConfig, linuxConfig]) {
expect(config.processingBackend, rust.BridgeAudioBackend.webrtcApm);
expect(config.vadBackend, rust.BridgeVadBackend.sileroOnnx);
expect(config.vadBackend, rust.BridgeVadBackend.webrtcVad);
expect(config.aec, rust.BridgeEffectOwner.off);
expect(config.ns, rust.BridgeEffectOwner.webrtcApm);
expect(config.agc, rust.BridgeEffectOwner.webrtcApm);
@@ -0,0 +1,41 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:chanora_flutter/widgets/input_dialogs.dart';
void main() {
test('browser back and forward logical keys map to mouse side bindings', () {
expect(
pttMouseSideButtonPlatformKeyForLogicalKey(
LogicalKeyboardKey.browserBack,
),
'mouse-side-button:8',
);
expect(
pttMouseSideButtonPlatformKeyForLogicalKey(LogicalKeyboardKey.goBack),
'mouse-side-button:8',
);
expect(
pttMouseSideButtonPlatformKeyForLogicalKey(
LogicalKeyboardKey.browserForward,
),
'mouse-side-button:16',
);
});
test('pointer button bitmasks map to mouse side bindings', () {
expect(
pttMouseSideButtonPlatformKeyForButtons(0x08),
'mouse-side-button:8',
);
expect(
pttMouseSideButtonPlatformKeyForButtons(0x10),
'mouse-side-button:16',
);
expect(
pttMouseSideButtonPlatformKeyForButtons(0x18),
'mouse-side-button:8',
);
expect(pttMouseSideButtonPlatformKeyForButtons(0x00), isNull);
});
}
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:chanora_flutter/services/startup_dependency_check.dart';
import 'package:chanora_flutter/widgets/startup_dependency_screen.dart';
void main() {
testWidgets('startup gate shows install-help screen and can continue', (
tester,
) async {
tester.view.physicalSize = const Size(1200, 1800);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final result = StartupDependencyCheckResult(
platformLabel: 'Fedora',
issues: const [
StartupDependencyIssue(
id: 'linux-sdl2-runtime',
title: 'SDL2 runtime is missing',
summary: 'Audio playback needs SDL2.',
details: ['Install SDL2 and recheck.'],
severity: StartupDependencySeverity.required,
installHints: [
StartupInstallHint(
label: 'Release downloads',
command: 'https://github.com/microsoft/onnxruntime/releases',
),
StartupInstallHint(
label: 'Fedora',
command: 'sudo dnf install SDL2',
),
],
),
],
);
final loggedResults = <StartupDependencyCheckResult>[];
await tester.pumpWidget(
MaterialApp(
home: StartupDependencyGate(
checker: () async => result,
logger: (value) async {
loggedResults.add(value);
},
child: const Scaffold(body: Text('ready')),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Finish Linux setup'), findsOneWidget);
expect(find.text('SDL2 runtime is missing'), findsOneWidget);
expect(find.text('Continue with limited mode'), findsOneWidget);
expect(find.text('Open'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(loggedResults, [result]);
await tester.ensureVisible(find.text('Continue with limited mode'));
await tester.tap(find.text('Continue with limited mode'));
await tester.pumpAndSettle();
expect(find.text('ready'), findsOneWidget);
});
}
@@ -24,8 +24,9 @@ void main() {
]);
});
test('desktop VAD segments expose Silero as the primary backend', () {
test('desktop VAD segments expose both Silero and WebRTC', () {
expect(desktopVadBackendSegments.map((s) => s.value), [
rust.BridgeVadBackend.webrtcVad,
rust.BridgeVadBackend.sileroOnnx,
]);
});