From 98a6414eaaa41d1a4d4ada6e7097717628c96b72 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Sun, 24 May 2026 22:07:40 +0200 Subject: [PATCH] fix(store): localised docs, debounced typing, explicit-submit always AI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three Stefan-found bugs in one pass: 1. Documentation rendered in English even when Studio was set to German. _loadDocs now passes Localizations.localeOf(...) to fetchModuleDocs(); the hub tries README.de.md before README.md when locale='de'. 2. 'Keine Treffer' flickered on every keystroke. _onAskTyping used to call _runSearch synchronously per character — a short 2-3-letter substring matched nothing, so the empty- state showed for a frame, then the next character revealed results. Add a 250ms typing debounce (Timer + cancel on dispose) so the search only re-runs once typing pauses. 3. Submit button did substring search even when System-AI was enabled — the _looksLikeQuestion heuristic only routed 4+-word / question-mark queries through the LLM. The user reasonably expected an explicit submit (with the AI-icon showing on the button) to actually use AI. New behaviour: when system-AI is enabled, the submit button always calls _runAiQuery; live-typing keeps substring filtering. The heuristic helper was removed (now unused). Signed-off-by: flemming-it --- lib/data/hub.dart | 4 ++-- lib/pages/store.dart | 48 ++++++++++++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/lib/data/hub.dart b/lib/data/hub.dart index 003a2ad..61d4f92 100644 --- a/lib/data/hub.dart +++ b/lib/data/hub.dart @@ -350,8 +350,8 @@ class HubService { /// back as `errorKind` strings (not exceptions) so callers can /// render fallback UI without try/catch ceremony. Future<({String errorKind, String text, String sourceUrl})> - fetchModuleDocs(String name) async { - final r = await _client.fetchModuleDocs(name); + fetchModuleDocs(String name, {String locale = ''}) async { + final r = await _client.fetchModuleDocs(name, locale: locale); return ( errorKind: r.errorKind, text: r.text, diff --git a/lib/pages/store.dart b/lib/pages/store.dart index 1db988e..05ce0e0 100644 --- a/lib/pages/store.dart +++ b/lib/pages/store.dart @@ -5,6 +5,7 @@ // renders the full bilingual description, requires-list, and // repository link. +import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; @@ -74,6 +75,11 @@ class _StorePageState extends State { List<_AiMatch>? _aiMatches; Set? _aiMatchedNames; String? _aiError; + /// Debounce-Timer für Such-Filterung beim Tippen. Ohne + /// Debounce flackert "Keine Treffer" zwischen Buchstaben + /// (für 2-3 Buchstaben matched z.B. nichts, das nächste + /// dann doch). 250ms ist comfortable für Tippen. + Timer? _typingDebounce; /// Whether the operator has a System-AI configured. Drives /// the Ask-bar hint copy and disables the AI path when off. bool _systemAiEnabled = false; @@ -105,6 +111,7 @@ class _StorePageState extends State { @override void dispose() { + _typingDebounce?.cancel(); _queryCtrl.dispose(); super.dispose(); } @@ -442,7 +449,6 @@ class _StorePageState extends State { /// operator submits — otherwise typing "ein Modul, das …" /// wipes the visible result set with every space. void _onAskTyping(String value) { - final trimmed = value.trim(); if (_aiAnswer != null || _aiMatches != null) { setState(() { _aiAnswer = null; @@ -451,17 +457,35 @@ class _StorePageState extends State { _aiError = null; }); } - if (_systemAiEnabled && _looksLikeQuestion(trimmed)) return; - _runSearch(); + // Tippen läuft IMMER nur durch die lokale Substring-Suche. + // Die KI-Suche kostet einen LLM-Roundtrip und gehört hinter + // den expliziten Submit-Knopf — sonst feuern wir pro + // Keystroke einen Prompt. Debounce verhindert "Keine + // Treffer" zwischen Buchstaben. + _typingDebounce?.cancel(); + _typingDebounce = Timer(const Duration(milliseconds: 250), () { + if (!mounted) return; + _runSearch(); + }); } Future _onAskSubmit() async { + // Pending typing-debounce abbrechen — wir submitten jetzt + // explizit und wollen nicht hinterher nochmal substring- + // filter laufen lassen. + _typingDebounce?.cancel(); final q = _queryCtrl.text.trim(); if (q.isEmpty) { _clearQuery(); return; } - if (_systemAiEnabled && _looksLikeQuestion(q)) { + // Wenn System-AI verfügbar ist, geht der explizite Submit + // IMMER über die KI-Suche — egal ob die Eingabe wie eine + // Frage aussieht. Das war der Bug: Nutzer tippt "tabellen" + // + klick Suchen, erwartet KI, bekommt Substring-Filter. + // Substring-Filter passiert weiter live beim Tippen + // (siehe _onAskTyping). + if (_systemAiEnabled) { await _runAiQuery(q); } else { _runSearch(); @@ -479,16 +503,6 @@ class _StorePageState extends State { _runSearch(); } - /// Heuristic question detector — kept generous so a sentence - /// "modul für tabellen lesen" triggers the LLM path even - /// without a question mark. Single-word and two-word inputs - /// stay on substring search where they belong. - bool _looksLikeQuestion(String q) { - if (q.endsWith('?')) return true; - final words = q.split(RegExp(r'\s+')); - return words.length >= 4; - } - Future _runAiQuery(String query) async { setState(() { _aiThinking = true; @@ -1922,10 +1936,14 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> { void _loadDocs() { if (_docsLoaded) return; + // Sprache vom aktuellen Studio-Locale ans Hub weitergeben, + // damit `README.de.md` zuerst probiert wird wenn Studio auf + // Deutsch steht. + final locale = Localizations.localeOf(context).languageCode; setState(() { _docsLoaded = true; _docsFuture = - HubService.instance.fetchModuleDocs(widget.item.name); + HubService.instance.fetchModuleDocs(widget.item.name, locale: locale); }); }