feat(studio): plain-language store, bottom-anchored ask bar (v0.33.0)

The store had three structural problems flagged in user
feedback:

1. "Lernkurve zu hoch — alle bridge, debug, alpha, planned,
   keiner weiß was das heißt." Wire-level enum values bled
   through into the UI: `bridge`, `debug`, `published`,
   `alpha`, `planned`. Operators don't share the data model.

2. "Filter / Kategorien sollten oben in die Leiste neben Store
   zum ausklappen, der Body soll übersichtlicher werden." A
   chip-row plus category strip plus result-count line ate a
   full row of viewport on every screen.

3. "Suche passt besser an den unteren Bildschirmrand." The
   chat-style ask bar belongs at the bottom — Claude / ChatGPT
   / Slack pattern — not at the top where it competes with
   the Today hero.

Plus: "Es gibt noch ganz viele overflows in den Beschreibungen."

Changes:

- Plain-language display names. New ARB keys for category
  labels (Connectors / Sample modules / AI models / Storage /
  Channels / Authentication / Orchestration / …) and updated
  status values (stable / experimental / coming soon). Wire
  ids stay in StoreEntry, RPCs, seed.yaml — only the rendered
  pills and dropdowns get translated. Helpers
  `_categoryDisplayName` and `_statusDisplayName` keep the
  mapping in one place.

- Toolbar moves into the AppBar. The new `_CategoryDropdown`
  hosts the category picker as a popup-menu with localised
  labels; the existing `_FilterButton` lives next to it; the
  reload icon stays where it always was. The body no longer
  carries any chip strip or result-count line.

- Bottom-anchored `_AskBar`. The Scaffold body becomes a
  Column of `Expanded(scrollable content)` plus a pinned
  composer row at the foot of the viewport, with a top
  divider matching the chat-input pattern of modern AI
  assistants. AI answers now render at the top of the scroll
  (above the Today-Hero) so the operator sees them right
  after submitting the question at the bottom.

- Overflow sweep on descriptions. Today-Hero header row
  becomes a Wrap (badge + deck flow naturally on narrow
  windows), the title is bound to 3 lines, the body to 6.
  Card category text gets `maxLines: 1, ellipsis` in both
  StoreCard and FeaturedTile. Recommended-source card title
  gets the same treatment.

- Better toast for the "added but zero capabilities" path.
  When MCP discovery succeeds but returns no tools (typical
  Streamable-HTTP servers without Mcp-Session-Id support, or
  servers that need `notifications/initialized`), the toast
  now explains the situation in plain language and points
  the operator at Settings → MCP Clients to retry. Old
  pluralised toast still fires when N > 0.

Hub-side `notifications/initialized` for HTTP MCP and
Mcp-Session-Id support are out of scope for this commit;
tracked as a separate fai_hub follow-up.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-08 13:37:35 +02:00
parent c7869870c4
commit de22791e86
8 changed files with 434 additions and 143 deletions

View file

@ -127,10 +127,22 @@
"storeNoMatchesHint": "Filter anpassen oder Suche leeren, um alle Einträge zu sehen.",
"storeClearFilters": "Filter zurücksetzen",
"storeStatusAll": "Alle",
"storeStatusPublished": "Veröffentlicht",
"storeStatusAlpha": "Alpha",
"storeStatusPlanned": "Geplant",
"storeStatusPublished": "stabil",
"storeStatusAlpha": "experimentell",
"storeStatusPlanned": "geplant",
"storeInstalledOnly": "Installiert",
"storeCategoryLabel": "Kategorie",
"storeCategoryAll": "Alle Kategorien",
"storeCategoryBridge": "Verbindungen",
"storeCategoryDebug": "Beispiele",
"storeCategoryText": "Text",
"storeCategoryLlm": "KI-Modelle",
"storeCategorySystem": "System",
"storeCategoryAuth": "Authentifizierung",
"storeCategoryStorage": "Speicher",
"storeCategoryChannel": "Kanäle",
"storeCategoryWeb": "Web",
"storeCategoryOrchestrator": "Orchestrierung",
"storeNResults": "{n} Treffer",
"@storeNResults": {
"placeholders": { "n": { "type": "int" } }
@ -221,6 +233,10 @@
"n": { "type": "int" }
}
},
"storeProviderAddedZeroToast": "{name} ist eingetragen, aber der Server hat noch keine Capabilities geliefert. Öffne Einstellungen → MCP-Clients, um Discovery erneut zu versuchen oder den Server zu prüfen.",
"@storeProviderAddedZeroToast": {
"placeholders": { "name": { "type": "String" } }
},
"storeProviderAddFailed": "{name} konnte nicht hinzugefügt werden: {error}",
"@storeProviderAddFailed": {
"placeholders": {

View file

@ -128,10 +128,22 @@
"storeNoMatchesHint": "Adjust the filters or clear the search to see all entries.",
"storeClearFilters": "Clear filters",
"storeStatusAll": "All",
"storeStatusPublished": "Published",
"storeStatusAlpha": "Alpha",
"storeStatusPlanned": "Planned",
"storeStatusPublished": "stable",
"storeStatusAlpha": "experimental",
"storeStatusPlanned": "coming soon",
"storeInstalledOnly": "Installed",
"storeCategoryLabel": "Category",
"storeCategoryAll": "All categories",
"storeCategoryBridge": "Connectors",
"storeCategoryDebug": "Sample modules",
"storeCategoryText": "Text",
"storeCategoryLlm": "AI models",
"storeCategorySystem": "System",
"storeCategoryAuth": "Authentication",
"storeCategoryStorage": "Storage",
"storeCategoryChannel": "Channels",
"storeCategoryWeb": "Web",
"storeCategoryOrchestrator": "Orchestration",
"storeNResults": "{n} result{n, plural, =1{} other{s}}",
"@storeNResults": {
"placeholders": { "n": { "type": "int" } }
@ -222,6 +234,10 @@
"n": { "type": "int" }
}
},
"storeProviderAddedZeroToast": "{name} registered, but the server returned no capabilities yet. Open Settings → MCP Clients to retry discovery or check the server.",
"@storeProviderAddedZeroToast": {
"placeholders": { "name": { "type": "String" } }
},
"storeProviderAddFailed": "Could not add {name}: {error}",
"@storeProviderAddFailed": {
"placeholders": {

View file

@ -761,19 +761,19 @@ abstract class AppLocalizations {
/// No description provided for @storeStatusPublished.
///
/// In en, this message translates to:
/// **'Published'**
/// **'stable'**
String get storeStatusPublished;
/// No description provided for @storeStatusAlpha.
///
/// In en, this message translates to:
/// **'Alpha'**
/// **'experimental'**
String get storeStatusAlpha;
/// No description provided for @storeStatusPlanned.
///
/// In en, this message translates to:
/// **'Planned'**
/// **'coming soon'**
String get storeStatusPlanned;
/// No description provided for @storeInstalledOnly.
@ -782,6 +782,78 @@ abstract class AppLocalizations {
/// **'Installed'**
String get storeInstalledOnly;
/// No description provided for @storeCategoryLabel.
///
/// In en, this message translates to:
/// **'Category'**
String get storeCategoryLabel;
/// No description provided for @storeCategoryAll.
///
/// In en, this message translates to:
/// **'All categories'**
String get storeCategoryAll;
/// No description provided for @storeCategoryBridge.
///
/// In en, this message translates to:
/// **'Connectors'**
String get storeCategoryBridge;
/// No description provided for @storeCategoryDebug.
///
/// In en, this message translates to:
/// **'Sample modules'**
String get storeCategoryDebug;
/// No description provided for @storeCategoryText.
///
/// In en, this message translates to:
/// **'Text'**
String get storeCategoryText;
/// No description provided for @storeCategoryLlm.
///
/// In en, this message translates to:
/// **'AI models'**
String get storeCategoryLlm;
/// No description provided for @storeCategorySystem.
///
/// In en, this message translates to:
/// **'System'**
String get storeCategorySystem;
/// No description provided for @storeCategoryAuth.
///
/// In en, this message translates to:
/// **'Authentication'**
String get storeCategoryAuth;
/// No description provided for @storeCategoryStorage.
///
/// In en, this message translates to:
/// **'Storage'**
String get storeCategoryStorage;
/// No description provided for @storeCategoryChannel.
///
/// In en, this message translates to:
/// **'Channels'**
String get storeCategoryChannel;
/// No description provided for @storeCategoryWeb.
///
/// In en, this message translates to:
/// **'Web'**
String get storeCategoryWeb;
/// No description provided for @storeCategoryOrchestrator.
///
/// In en, this message translates to:
/// **'Orchestration'**
String get storeCategoryOrchestrator;
/// No description provided for @storeNResults.
///
/// In en, this message translates to:
@ -1100,6 +1172,12 @@ abstract class AppLocalizations {
/// **'Added {name} — {n} federated {n, plural, =1{capability} other{capabilities}}.'**
String storeProviderAddedToast(String name, int n);
/// No description provided for @storeProviderAddedZeroToast.
///
/// In en, this message translates to:
/// **'{name} registered, but the server returned no capabilities yet. Open Settings → MCP Clients to retry discovery or check the server.'**
String storeProviderAddedZeroToast(String name);
/// No description provided for @storeProviderAddFailed.
///
/// In en, this message translates to:

View file

@ -382,17 +382,53 @@ class AppLocalizationsDe extends AppLocalizations {
String get storeStatusAll => 'Alle';
@override
String get storeStatusPublished => 'Veröffentlicht';
String get storeStatusPublished => 'stabil';
@override
String get storeStatusAlpha => 'Alpha';
String get storeStatusAlpha => 'experimentell';
@override
String get storeStatusPlanned => 'Geplant';
String get storeStatusPlanned => 'geplant';
@override
String get storeInstalledOnly => 'Installiert';
@override
String get storeCategoryLabel => 'Kategorie';
@override
String get storeCategoryAll => 'Alle Kategorien';
@override
String get storeCategoryBridge => 'Verbindungen';
@override
String get storeCategoryDebug => 'Beispiele';
@override
String get storeCategoryText => 'Text';
@override
String get storeCategoryLlm => 'KI-Modelle';
@override
String get storeCategorySystem => 'System';
@override
String get storeCategoryAuth => 'Authentifizierung';
@override
String get storeCategoryStorage => 'Speicher';
@override
String get storeCategoryChannel => 'Kanäle';
@override
String get storeCategoryWeb => 'Web';
@override
String get storeCategoryOrchestrator => 'Orchestrierung';
@override
String storeNResults(int n) {
return '$n Treffer';
@ -596,6 +632,11 @@ class AppLocalizationsDe extends AppLocalizations {
return '$name hinzugefügt — $n $_temp0.';
}
@override
String storeProviderAddedZeroToast(String name) {
return '$name ist eingetragen, aber der Server hat noch keine Capabilities geliefert. Öffne Einstellungen → MCP-Clients, um Discovery erneut zu versuchen oder den Server zu prüfen.';
}
@override
String storeProviderAddFailed(String name, String error) {
return '$name konnte nicht hinzugefügt werden: $error';

View file

@ -394,17 +394,53 @@ class AppLocalizationsEn extends AppLocalizations {
String get storeStatusAll => 'All';
@override
String get storeStatusPublished => 'Published';
String get storeStatusPublished => 'stable';
@override
String get storeStatusAlpha => 'Alpha';
String get storeStatusAlpha => 'experimental';
@override
String get storeStatusPlanned => 'Planned';
String get storeStatusPlanned => 'coming soon';
@override
String get storeInstalledOnly => 'Installed';
@override
String get storeCategoryLabel => 'Category';
@override
String get storeCategoryAll => 'All categories';
@override
String get storeCategoryBridge => 'Connectors';
@override
String get storeCategoryDebug => 'Sample modules';
@override
String get storeCategoryText => 'Text';
@override
String get storeCategoryLlm => 'AI models';
@override
String get storeCategorySystem => 'System';
@override
String get storeCategoryAuth => 'Authentication';
@override
String get storeCategoryStorage => 'Storage';
@override
String get storeCategoryChannel => 'Channels';
@override
String get storeCategoryWeb => 'Web';
@override
String get storeCategoryOrchestrator => 'Orchestration';
@override
String storeNResults(int n) {
String _temp0 = intl.Intl.pluralLogic(
@ -613,6 +649,11 @@ class AppLocalizationsEn extends AppLocalizations {
return 'Added $name$n federated $_temp0.';
}
@override
String storeProviderAddedZeroToast(String name) {
return '$name registered, but the server returned no capabilities yet. Open Settings → MCP Clients to retry discovery or check the server.';
}
@override
String storeProviderAddFailed(String name, String error) {
return 'Could not add $name: $error';