feat(learn): LanguageTool as a sidecar, reached through the server

compose.languagetool.yaml runs LanguageTool's open-source server
(meyay/languagetool 6.8-13, pinned by digest) on `edge` with no site and
no published port, as user 61081 on a read-only filesystem, without a
log. The server alone reaches it (LANGUAGETOOL_URL): grammar.check is
for those signed in, takes 20,000 characters, 60 checks a minute each
(counted in memory), maps a studyset's language to LanguageTool's code
and keeps nothing. instance.info() names the languages it checks; the
privacy page says what goes there. sidecars/README.md has the steps.

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:13:46 +01:00
commit f766a880ac
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -6,6 +6,7 @@ import 'package:halkyon_learn_server/src/auth/learn_base.dart';
import 'package:halkyon_learn_server/src/dictionary/device_dictionaries.dart';
import 'package:halkyon_learn_server/src/dictionary/dictionary_loader.dart';
import 'package:halkyon_learn_server/src/generated/serverpod.dart';
import 'package:halkyon_learn_server/src/grammar/grammar_checking.dart';
import 'package:halkyon_learn_server/src/instance/jellyfin_suggestion.dart';
import 'package:serverpod_auth_idp_server/core.dart';
@ -53,6 +54,8 @@ Future<void> run(List<String> args) async {
// Whether a Jellyfin server answers where the instance would suggest one
// (decided 2026-10-13), so instance.info() can name it from the first ask.
unawaited(JellyfinSuggestion.of(pod).look());
// Which languages the instance's LanguageTool checks (ADR 0024), likewise.
unawaited(GrammarChecking.of(pod).look());
// The accounts nobody signs into any more, daily (decided 2026-09-30;
// every anonymous account left since ADR 0020, D25). Scheduling always