From 2be7d241bff529b8b8b6c0356a7dde5484a6639e Mon Sep 17 00:00:00 2001 From: flemming-it Date: Tue, 9 Jun 2026 01:36:54 +0200 Subject: [PATCH] feat(studio): localised + explainer-rich Add module source dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous Add-source dialog was English-only and operator-hostile — answered 'where do I install from?' without explaining what a private module *is*. New version, DE + EN: - Title + intro + button labels routed through AppLocalizations ("Modul-Quelle hinzufügen" / "Installation fehlgeschlagen" instead of raw English strings). - 'How private modules work' explainer block (3 sentences): what a module is (`module.yaml` + WASM), how to package it (`fai pack ` → .fai bundle), where to host it (any URL: own Forgejo / GitHub / S3), what verification the hub does (sha256 + signature against trust store). - Honest about the Studio gap: the dialog can install URLs + packed bundles, but unpacked source directories still require `fai install --link ` on the CLI. The example command lives in its own code-style box, selectable. - Layout: SingleChildScrollView'd so the explainer doesn't push the buttons off short viewports. Studio bumped to 0.66.0; editor path-override pulls in 0.20.0. Signed-off-by: flemming-it --- lib/l10n/app_de.arb | 22 ++++- lib/l10n/app_en.arb | 22 ++++- lib/l10n/app_localizations.dart | 60 ++++++++++++ lib/l10n/app_localizations_de.dart | 36 ++++++++ lib/l10n/app_localizations_en.dart | 36 ++++++++ lib/main.dart | 2 +- lib/pages/flows.dart | 143 +++++++++++++++++------------ pubspec.lock | 2 +- pubspec.yaml | 2 +- 9 files changed, 259 insertions(+), 66 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6773789..6acd43f 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1448,5 +1448,25 @@ "count": {"type": "int"} } }, - "doctorPathStudioErrors": "Studio-Fehler" + "doctorPathStudioErrors": "Studio-Fehler", + "addSourceTitle": "Modul-Quelle hinzufügen", + "addSourceIntro": "`{capability}` ist nicht im öffentlichen Store. Zeig dem Hub eine `.fai`-Bundle-URL oder einen lokalen Bundle-Pfad — der Hub lädt es herunter, prüft (sha256 + Signatur) und installiert.", + "@addSourceIntro": { + "placeholders": { + "capability": {"type": "String"} + } + }, + "addSourceField": "URL oder Pfad zum .fai-Bundle", + "addSourceHint": "https://git.flemming.ai/deine-org/dein-modul/releases/download/v0.1.0/foo-0.1.0.fai", + "addSourceHowItWorksTitle": "Wie private Module funktionieren", + "addSourceHowItWorksBody": "Ein Modul ist ein Verzeichnis mit module.yaml + WASM-Artefakt. Zum Teilen: `fai pack ` baut ein `.fai`-Bundle, das du beliebig hosten kannst (eigene Forgejo / GitHub / S3). Der Hub installiert per URL und prüft die Signatur gegen seinen Trust-Store.\n\nLokal entwickeln? Nimm das CLI — Studio installiert (noch) nicht aus einem unverpackten Verzeichnis:", + "addSourceCliExample": "fai install --link /pfad/zum/modul", + "addSourceInstallButton": "Installieren", + "addSourceCancel": "Abbrechen", + "installFailed": "Installation fehlgeschlagen: {error}", + "@installFailed": { + "placeholders": { + "error": {"type": "String"} + } + } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3a14c55..2226206 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1451,5 +1451,25 @@ "count": {"type": "int"} } }, - "doctorPathStudioErrors": "Studio errors" + "doctorPathStudioErrors": "Studio errors", + "addSourceTitle": "Add module source", + "addSourceIntro": "`{capability}` is not in the public store. Point the hub at a `.fai` bundle URL or a local bundle path; the hub downloads, verifies (sha256 + signature) and installs it.", + "@addSourceIntro": { + "placeholders": { + "capability": {"type": "String"} + } + }, + "addSourceField": "URL or path to .fai bundle", + "addSourceHint": "https://git.flemming.ai/your-org/your-module/releases/download/v0.1.0/foo-0.1.0.fai", + "addSourceHowItWorksTitle": "How private modules work", + "addSourceHowItWorksBody": "A module is a directory with a module.yaml + the WASM artifact. To share it: pack it (`fai pack `) and host the resulting `.fai` bundle anywhere (your own Forgejo / GitHub / S3). The hub installs by URL and verifies the signature against its trust store.\n\nDeveloping locally? Use the CLI — Studio cannot install from an unpacked directory (yet):", + "addSourceCliExample": "fai install --link /path/to/module", + "addSourceInstallButton": "Install", + "addSourceCancel": "Cancel", + "installFailed": "Install failed: {error}", + "@installFailed": { + "placeholders": { + "error": {"type": "String"} + } + } } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 677b091..d70ac45 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3967,6 +3967,66 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Studio errors'** String get doctorPathStudioErrors; + + /// No description provided for @addSourceTitle. + /// + /// In en, this message translates to: + /// **'Add module source'** + String get addSourceTitle; + + /// No description provided for @addSourceIntro. + /// + /// In en, this message translates to: + /// **'`{capability}` is not in the public store. Point the hub at a `.fai` bundle URL or a local bundle path; the hub downloads, verifies (sha256 + signature) and installs it.'** + String addSourceIntro(String capability); + + /// No description provided for @addSourceField. + /// + /// In en, this message translates to: + /// **'URL or path to .fai bundle'** + String get addSourceField; + + /// No description provided for @addSourceHint. + /// + /// In en, this message translates to: + /// **'https://git.flemming.ai/your-org/your-module/releases/download/v0.1.0/foo-0.1.0.fai'** + String get addSourceHint; + + /// No description provided for @addSourceHowItWorksTitle. + /// + /// In en, this message translates to: + /// **'How private modules work'** + String get addSourceHowItWorksTitle; + + /// No description provided for @addSourceHowItWorksBody. + /// + /// In en, this message translates to: + /// **'A module is a directory with a module.yaml + the WASM artifact. To share it: pack it (`fai pack `) and host the resulting `.fai` bundle anywhere (your own Forgejo / GitHub / S3). The hub installs by URL and verifies the signature against its trust store.\n\nDeveloping locally? Use the CLI — Studio cannot install from an unpacked directory (yet):'** + String get addSourceHowItWorksBody; + + /// No description provided for @addSourceCliExample. + /// + /// In en, this message translates to: + /// **'fai install --link /path/to/module'** + String get addSourceCliExample; + + /// No description provided for @addSourceInstallButton. + /// + /// In en, this message translates to: + /// **'Install'** + String get addSourceInstallButton; + + /// No description provided for @addSourceCancel. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get addSourceCancel; + + /// No description provided for @installFailed. + /// + /// In en, this message translates to: + /// **'Install failed: {error}'** + String installFailed(String error); } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index e7d74af..87806fe 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2322,4 +2322,40 @@ class AppLocalizationsDe extends AppLocalizations { @override String get doctorPathStudioErrors => 'Studio-Fehler'; + + @override + String get addSourceTitle => 'Modul-Quelle hinzufügen'; + + @override + String addSourceIntro(String capability) { + return '`$capability` ist nicht im öffentlichen Store. Zeig dem Hub eine `.fai`-Bundle-URL oder einen lokalen Bundle-Pfad — der Hub lädt es herunter, prüft (sha256 + Signatur) und installiert.'; + } + + @override + String get addSourceField => 'URL oder Pfad zum .fai-Bundle'; + + @override + String get addSourceHint => + 'https://git.flemming.ai/deine-org/dein-modul/releases/download/v0.1.0/foo-0.1.0.fai'; + + @override + String get addSourceHowItWorksTitle => 'Wie private Module funktionieren'; + + @override + String get addSourceHowItWorksBody => + 'Ein Modul ist ein Verzeichnis mit module.yaml + WASM-Artefakt. Zum Teilen: `fai pack ` baut ein `.fai`-Bundle, das du beliebig hosten kannst (eigene Forgejo / GitHub / S3). Der Hub installiert per URL und prüft die Signatur gegen seinen Trust-Store.\n\nLokal entwickeln? Nimm das CLI — Studio installiert (noch) nicht aus einem unverpackten Verzeichnis:'; + + @override + String get addSourceCliExample => 'fai install --link /pfad/zum/modul'; + + @override + String get addSourceInstallButton => 'Installieren'; + + @override + String get addSourceCancel => 'Abbrechen'; + + @override + String installFailed(String error) { + return 'Installation fehlgeschlagen: $error'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 9c2441e..a8c4c9e 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2327,4 +2327,40 @@ class AppLocalizationsEn extends AppLocalizations { @override String get doctorPathStudioErrors => 'Studio errors'; + + @override + String get addSourceTitle => 'Add module source'; + + @override + String addSourceIntro(String capability) { + return '`$capability` is not in the public store. Point the hub at a `.fai` bundle URL or a local bundle path; the hub downloads, verifies (sha256 + signature) and installs it.'; + } + + @override + String get addSourceField => 'URL or path to .fai bundle'; + + @override + String get addSourceHint => + 'https://git.flemming.ai/your-org/your-module/releases/download/v0.1.0/foo-0.1.0.fai'; + + @override + String get addSourceHowItWorksTitle => 'How private modules work'; + + @override + String get addSourceHowItWorksBody => + 'A module is a directory with a module.yaml + the WASM artifact. To share it: pack it (`fai pack `) and host the resulting `.fai` bundle anywhere (your own Forgejo / GitHub / S3). The hub installs by URL and verifies the signature against its trust store.\n\nDeveloping locally? Use the CLI — Studio cannot install from an unpacked directory (yet):'; + + @override + String get addSourceCliExample => 'fai install --link /path/to/module'; + + @override + String get addSourceInstallButton => 'Install'; + + @override + String get addSourceCancel => 'Cancel'; + + @override + String installFailed(String error) { + return 'Install failed: $error'; + } } diff --git a/lib/main.dart b/lib/main.dart index fb4ee64..d5f8869 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,7 +27,7 @@ import 'widgets/widgets.dart'; /// Studio's own build version. Bump on every UI commit so the /// running app self-identifies — visible in the sidebar header /// and quick-glance proof that you're seeing the current build. -const String kStudioVersion = '0.65.1'; +const String kStudioVersion = '0.66.0'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/pages/flows.dart b/lib/pages/flows.dart index 378a467..396d4c3 100644 --- a/lib/pages/flows.dart +++ b/lib/pages/flows.dart @@ -15,6 +15,7 @@ import 'package:flutter/material.dart'; import '../data/flow_run_driver.dart'; import '../data/hub.dart'; +import '../l10n/app_localizations.dart'; class FlowsPage extends StatefulWidget { /// Pre-load this flow when the editor first builds. Studio @@ -121,10 +122,14 @@ class _FlowsPageState extends State { return updated; } catch (e) { if (mounted) { + final l = AppLocalizations.of(context); + final msg = l != null + ? l.installFailed(e.toString()) + : 'Install failed: $e'; ScaffoldMessenger.of(context).removeCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Install failed: $e'), + content: Text(msg), behavior: SnackBarBehavior.floating, ), ); @@ -189,83 +194,99 @@ class _AddModuleSourceDialogState extends State<_AddModuleSourceDialog> { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final l = AppLocalizations.of(context)!; return AlertDialog( - title: const Text('Add module source'), - content: SizedBox( - width: 480, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '`${widget.capability}` is not in the public store. ' - 'Point the hub at a `.fai` bundle URL or a local bundle path ' - 'and the hub will install + verify it.', - style: theme.textTheme.bodySmall?.copyWith( - color: theme.colorScheme.onSurfaceVariant, + title: Text(l.addSourceTitle), + content: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 520), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l.addSourceIntro(widget.capability), + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), ), - ), - const SizedBox(height: 16), - TextField( - controller: _ctrl, - autofocus: true, - onChanged: (v) => setState(() => _source = v.trim()), - decoration: const InputDecoration( - labelText: 'URL or path to .fai bundle', - hintText: 'https://example.com/foo-0.1.0.fai', - border: OutlineInputBorder(), - isDense: true, + const SizedBox(height: 16), + TextField( + controller: _ctrl, + autofocus: true, + onChanged: (v) => setState(() => _source = v.trim()), + decoration: InputDecoration( + labelText: l.addSourceField, + hintText: l.addSourceHint, + border: const OutlineInputBorder(), + isDense: true, + ), ), - ), - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(6), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Developing a local module?', - style: theme.textTheme.labelMedium?.copyWith( - fontWeight: FontWeight.w600, + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(6), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l.addSourceHowItWorksTitle, + style: theme.textTheme.labelMedium?.copyWith( + fontWeight: FontWeight.w600, + ), ), - ), - const SizedBox(height: 4), - Text( - 'Studio installs from URLs + packed bundles only. For an ' - 'unpacked source directory, use the CLI:', - style: theme.textTheme.bodySmall?.copyWith( - color: theme.colorScheme.onSurfaceVariant, + const SizedBox(height: 6), + Text( + l.addSourceHowItWorksBody, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), ), - ), - const SizedBox(height: 6), - SelectableText( - 'fai install --link /path/to/module', - style: theme.textTheme.bodySmall?.copyWith( - fontFamily: 'JetBrains Mono', - fontSize: 11, - color: theme.colorScheme.onSurface, + const SizedBox(height: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: BorderRadius.circular(4), + ), + child: SelectableText( + l.addSourceCliExample, + style: TextStyle( + fontFamily: 'JetBrains Mono', + fontFamilyFallback: const [ + 'Menlo', + 'Consolas', + 'Courier New', + 'monospace', + ], + fontSize: 11, + color: theme.colorScheme.onSurface, + ), + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), + child: Text(l.addSourceCancel), ), FilledButton( onPressed: _source.isEmpty ? null : () => Navigator.of(context).pop(_source), - child: const Text('Install'), + child: Text(l.addSourceInstallButton), ), ], ); diff --git a/pubspec.lock b/pubspec.lock index 253450d..aecae24 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -126,7 +126,7 @@ packages: path: "../fai_studio_flow_editor" relative: true source: path - version: "0.18.0" + version: "0.19.0" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 799c2a7..d96f714 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fai_studio description: "F∆I Studio — desktop GUI for the F∆I hub" publish_to: 'none' -version: 0.65.1 +version: 0.66.0 environment: sdk: ^3.11.0-200.1.beta