From c71b98c08be1e57e7a5d7eb40429ef77b97af47b Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 23 Oct 2026 12:00:26 +0200 Subject: [PATCH] Desktop: no stray browser window from extensions; keyboard stays down after backing out of a note Co-Authored-By: Claude Opus 5.5 (1M context) --- install_arch.sh | 4 +- lib/desktop/browser_launcher.dart | 5 +++ lib/ui/notes_list_navigation.dart | 3 ++ pubspec.yaml | 2 +- test/desktop/browser_launcher_test.dart | 4 ++ test/notes_list_focus_test.dart | 54 +++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 test/notes_list_focus_test.dart diff --git a/install_arch.sh b/install_arch.sh index fb252b6..452dc37 100755 --- a/install_arch.sh +++ b/install_arch.sh @@ -29,8 +29,10 @@ echo "==> Compiling the desktop wrapper..." # `dart build cli`, not `dart compile exe`: the package pulls in dependencies # with native build hooks (sqlite3, objective_c), which `dart compile` refuses # to handle even though the wrapper itself uses none of them. +# --target is required since bin/ also holds the todo_note CLI: with two +# entry points `dart build cli` refuses to guess and the install aborts. rm -rf "$SCRIPT_DIR/build/cli" -dart build cli -o "$SCRIPT_DIR/build/cli" +dart build cli -t "$SCRIPT_DIR/bin/todo_desktop.dart" -o "$SCRIPT_DIR/build/cli" echo "==> Generating PKGBUILD..." # pkgname is 'todo-flutter', NOT 'todo': the AUR already ships an unrelated diff --git a/lib/desktop/browser_launcher.dart b/lib/desktop/browser_launcher.dart index b98f316..1768c77 100644 --- a/lib/desktop/browser_launcher.dart +++ b/lib/desktop/browser_launcher.dart @@ -59,6 +59,11 @@ List browserArgs(String home) => [ // blank grey. Nothing in this profile needs OS-level encryption (the // GitHub token is in localStorage, not the password store). '--password-store=basic', + // The distro wrapper appends ~/.config/chromium-flags.conf to every launch, + // which here carries `--load-extension` for Claude in Chrome. Logged out in + // this profile, it opened a claude.ai login tab in a second, normal browser + // window on every start. This flag also drops command-line extensions. + '--disable-extensions', ]; /// Launches the app in a Chrome-family browser with a **stable** profile diff --git a/lib/ui/notes_list_navigation.dart b/lib/ui/notes_list_navigation.dart index c55dc3a..9f9a953 100644 --- a/lib/ui/notes_list_navigation.dart +++ b/lib/ui/notes_list_navigation.dart @@ -20,6 +20,9 @@ Future openNote( NoteRepository repository, ValueNotifier appSettings, ) async { + // Drop the search box's focus first: a route remembers its focused field and + // re-focuses it on return, so backing out of a note would pop the keyboard. + FocusManager.instance.primaryFocus?.unfocus(); await Navigator.of(context).push( MaterialPageRoute( builder: (_) => NoteDetailScreen( diff --git a/pubspec.yaml b/pubspec.yaml index 9aabd4d..6c4ed66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.45+1 +version: 1.0.46+1 environment: sdk: ^3.12.2 diff --git a/test/desktop/browser_launcher_test.dart b/test/desktop/browser_launcher_test.dart index 84ca47a..969d488 100644 --- a/test/desktop/browser_launcher_test.dart +++ b/test/desktop/browser_launcher_test.dart @@ -19,6 +19,10 @@ void main() { test('keeps Chrome off the keyring, whose lock blanks the window', () { expect(args, contains('--password-store=basic')); }); + + test('loads no extension, so none can open a second browser window', () { + expect(args, contains('--disable-extensions')); + }); }); group('findBrowser', () { diff --git a/test/notes_list_focus_test.dart b/test/notes_list_focus_test.dart new file mode 100644 index 0000000..8482b1d --- /dev/null +++ b/test/notes_list_focus_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:todo/ui/note_detail_screen.dart'; + +import 'notes_list_harness.dart'; + +/// Whether any text field holds focus, i.e. Android would show the keyboard. +bool textFieldFocused() => + FocusManager.instance.primaryFocus?.context + ?.findAncestorWidgetOfExactType() != + null; + +/// Opens the first note, types into its editor, then backs out the way the +/// Android back swipe does (a system pop, not the app-bar arrow). +Future editNoteAndSwipeBack(WidgetTester tester) async { + await tester.tap(find.text('Buy milk')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + await tester.tap( + find + .descendant( + of: find.byType(NoteDetailScreen), + matching: find.byType(EditableText), + ) + .first, + ); + await tester.pump(); + expect(textFieldFocused(), isTrue); + await tester.binding.handlePopRoute(); + await tester.pump(const Duration(milliseconds: 500)); +} + +void main() { + testWidgets('backing out of a note leaves no text field focused', ( + tester, + ) async { + await pumpList(tester, seed: [note('a', 'Buy milk')]); + await editNoteAndSwipeBack(tester); + expect(find.text('Search notes…'), findsOneWidget); + expect(textFieldFocused(), isFalse); + }); + + testWidgets('backing out does not re-focus a search box used earlier', ( + tester, + ) async { + await pumpList(tester, seed: [note('a', 'Buy milk')]); + await tester.tap(find.byType(TextField).first); + await tester.pump(); + await editNoteAndSwipeBack(tester); + expect(find.text('Search notes…'), findsOneWidget); + expect(textFieldFocused(), isFalse); + }); +}