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

@ -18,6 +18,7 @@ import 'package:halkyon_ui/halkyon_ui.dart';
import 'package:material_ui/material_ui.dart' hide Card;
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_grammar_gateway.dart';
import '../../../support/fake_studyset_gateway.dart';
import '../../../support/loaded_maps.dart';
import '../../../support/pump_app.dart';
@ -90,6 +91,9 @@ void main() {
GeoMapRepository? maps,
Size size = const Size(1400, 1600),
String? frontLanguage,
String? backLanguage,
FakeGrammarGateway? grammar,
List<String>? grammarLanguages,
}) async {
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1;
@ -102,6 +106,7 @@ void main() {
title: 'Japanese',
description: RichContent.empty(),
frontLanguage: frontLanguage,
backLanguage: backLanguage,
),
),
);
@ -111,6 +116,8 @@ void main() {
studysetGateway: studysets,
cardGateway: gateway,
maps: maps,
grammarGateway: grammar,
grammarLanguages: grammarLanguages,
),
location: RoutePaths.studyset('japanese'),
);
@ -162,6 +169,30 @@ void main() {
expect(gateway.written, isEmpty);
});
testWidgets("a card's front is checked as it is written in the "
"studyset's front language, its back in the back's (ADR 0024)", (
tester,
) async {
final grammar = FakeGrammarGateway();
await cards.create('japanese', card('猫', 'cat'));
await showStudyset(
tester,
frontLanguage: 'ja',
backLanguage: 'en-GB',
grammar: grammar,
grammarLanguages: ['ja', 'en'],
);
await open(tester, '猫');
await tester.tap(inField('猫'));
await settle(tester);
expect(grammar.checked, [('猫', 'ja')]);
await tester.tap(inField('cat'));
await settle(tester);
expect(grammar.checked.last, ('cat', 'en-GB'));
});
testWidgets('a card opens in the composer among the others; an edit is '
'saved on leaving it', (tester) async {
await cards.create('japanese', card('猫', 'cat'));