Desktop: no stray browser window from extensions; keyboard stays down after backing out of a note
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-10-23 12:00:26 +02:00
parent 341a943277
commit c71b98c08b
6 changed files with 70 additions and 2 deletions

View File

@ -29,8 +29,10 @@ echo "==> Compiling the desktop wrapper..."
# `dart build cli`, not `dart compile exe`: the package pulls in dependencies # `dart build cli`, not `dart compile exe`: the package pulls in dependencies
# with native build hooks (sqlite3, objective_c), which `dart compile` refuses # with native build hooks (sqlite3, objective_c), which `dart compile` refuses
# to handle even though the wrapper itself uses none of them. # 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" 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..." echo "==> Generating PKGBUILD..."
# pkgname is 'todo-flutter', NOT 'todo': the AUR already ships an unrelated # pkgname is 'todo-flutter', NOT 'todo': the AUR already ships an unrelated

View File

@ -59,6 +59,11 @@ List<String> browserArgs(String home) => [
// blank grey. Nothing in this profile needs OS-level encryption (the // blank grey. Nothing in this profile needs OS-level encryption (the
// GitHub token is in localStorage, not the password store). // GitHub token is in localStorage, not the password store).
'--password-store=basic', '--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 /// Launches the app in a Chrome-family browser with a **stable** profile

View File

@ -20,6 +20,9 @@ Future<void> openNote(
NoteRepository repository, NoteRepository repository,
ValueNotifier<AppSettings> appSettings, ValueNotifier<AppSettings> appSettings,
) async { ) 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<void>( await Navigator.of(context).push<void>(
MaterialPageRoute( MaterialPageRoute(
builder: (_) => NoteDetailScreen( builder: (_) => NoteDetailScreen(

View File

@ -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 # 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 # 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. # 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: environment:
sdk: ^3.12.2 sdk: ^3.12.2

View File

@ -19,6 +19,10 @@ void main() {
test('keeps Chrome off the keyring, whose lock blanks the window', () { test('keeps Chrome off the keyring, whose lock blanks the window', () {
expect(args, contains('--password-store=basic')); 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', () { group('findBrowser', () {

View File

@ -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<EditableText>() !=
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<void> 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);
});
}