feat(learn): spelling and grammar checked as people write (ADR 0024)

Where the instance's LanguageTool checks the language, the editors
underline what it finds after a pause, a paragraph at a time, a tap
offering its suggestions (GrammarHost, GrammarCheckController,
GrammarOverlay; nothing written into the document). A card's front is
checked in the studyset's front language, its back and answers in the
back's, everything else in the language recognised.

In a private studyset nothing is sent until the person agrees for it,
asked once in a banner above the editor and kept in their settings
(an agreement under privateAgreements, a refusal in grammarDeclined,
settings schema 14), taken back in the studyset's privacy dialog.
Settings > Writing turns it off on the device.

The field's Check asks LanguageTool first where it checks the
studyset's language, in a sheet offering to ask the assistant why;
elsewhere it asks the assistant as before (amends ADR 0021, D54).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 23:51:45 +01:00
commit d2eaef255e
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -261,6 +261,53 @@ void main() {
expect(store.writes.last.pomodoroWhileStudying, isFalse);
});
testWidgets('the Writing card turns off checking what is written (ADR '
'0024), and says where the instance checks nothing yet', (tester) async {
Future<void> show(
List<String>? languages,
MemoryDeviceSettingsStore store,
) => pumpApp(
tester,
testDependencies(
deviceSettingsStore: store,
grammarLanguages: languages,
),
location: RoutePaths.settings,
);
const title = 'Check grammar while writing';
const none =
'This instance has no spelling and grammar check set up yet, so '
'nothing is underlined. Left on, checking starts once it has one.';
final store = MemoryDeviceSettingsStore();
await show(['en', 'de'], store);
final toggle = find.widgetWithText(SwitchListTile, title);
await tester.scrollUntilVisible(
toggle,
200,
scrollable: find.byType(Scrollable).first,
);
await tester.pumpAndSettle();
expect(find.text('Writing'), findsOneWidget);
expect(tester.widget<SwitchListTile>(toggle).value, isTrue);
expect(find.text(none), findsNothing);
await tester.tap(
find.descendant(of: toggle, matching: find.byType(Switch)),
);
await tester.pumpAndSettle();
expect(store.writes.last.checkGrammar, isFalse);
await tester.pumpWidget(const SizedBox());
await show(null, MemoryDeviceSettingsStore());
await tester.scrollUntilVisible(
find.text(none),
200,
scrollable: find.byType(Scrollable).first,
);
expect(find.text(none), findsOneWidget);
});
testWidgets('starts with the appearance the device kept', (tester) async {
tester.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
addTearDown(tester.platformDispatcher.clearPlatformBrightnessTestValue);