diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 7b86571..cb44682 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -41,11 +41,11 @@ "welcomeChecklistAi": "System-AI konfigurieren", "welcomeChecklistAiHint": "Einstellungen → System-AI. Wird für die KI-Suche und Fehlererklärungen gebraucht.", "welcomeChecklistMcp": "Öffentliche Capability-Quelle hinzufügen", - "welcomeChecklistMcpHint": "Store → Today-Hero, oder Einstellungen → MCP-Clients. DeepWiki und Semgrep sind je ein Klick.", + "welcomeChecklistMcpHint": "Im Store eine MCP-Quelle hinzufügen oder unter Einstellungen → MCP-Clients konfigurieren.", "welcomeChecklistModule": "Ein Text-Modul installieren", - "welcomeChecklistModuleHint": "Store → nach „text\" suchen → Installieren. text.extract für PDFs und DOCX ist ein guter Start.", + "welcomeChecklistModuleHint": "Im Store ein Modul aus der Kategorie deiner Wahl installieren. Module sind sandboxed F∆I-Komponenten.", "welcomeChecklistFlow": "Einen gespeicherten Flow starten", - "welcomeChecklistFlowHint": "Flows → einen wählen → Starten. Der mitgelieferte extract-summarize.yaml ist ein guter erster Lauf.", + "welcomeChecklistFlowHint": "Flows → einen wählen → Starten. Studio bringt einige Demo-Flows mit; auch ein eigener tut's.", "welcomeChecklistAllDone": "Alle vier Schritte erledigt.", "welcomeChecklistDismiss": "Checkliste ausblenden", "welcomeChecklistRefresh": "Aktualisieren", @@ -106,7 +106,7 @@ "mcpServerEnLanguageBadge": "Server-Text in Englisch — Studio zeigt ihn wie geliefert.", "themePluginHeader": "Theme-Plugin", "themePluginNone": "Standard (kein Plugin)", - "themePluginHint": "Installiertes studio.theme.*-Plugin auswählen, um das ganze Studio umzustylen. Erst eines aus dem Store installieren.", + "themePluginHint": "Built-in- und Plugin-Kacheln bringen handkuratierte Paletten mit. Die Kachel „Eigene Farbe\" leitet aus einer gewählten Seedfarbe eine vollständige Material-3-Palette ab (primary, secondary, tertiary, surface, …) — das ist ein vollwertiges Theme, kein Einzelfarb-Tweak.", "themePluginEmpty": "Kein Theme-Plugin installiert. Im Store eines aus der Kategorie studio-plugin auswählen.", "themePluginCustom": "Eigene Farbe", "themePluginCustomDialogTitle": "Eigenes Theme — Seedfarbe wählen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 285652a..f839a6b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -44,11 +44,11 @@ "welcomeChecklistAi": "Configure System AI", "welcomeChecklistAiHint": "Settings → System AI. Required for the AI search and inline failure explanations.", "welcomeChecklistMcp": "Add a public capability source", - "welcomeChecklistMcpHint": "Store → Today hero, or Settings → MCP Clients. DeepWiki and Semgrep are one click each.", + "welcomeChecklistMcpHint": "Add an MCP source from the Store, or configure one under Settings → MCP Clients.", "welcomeChecklistModule": "Install a text module", - "welcomeChecklistModuleHint": "Store → search \"text\" → Install. Try text.extract for PDFs and DOCX.", + "welcomeChecklistModuleHint": "Install a module from the Store — pick whichever category fits your work. Modules are sandboxed F∆I components.", "welcomeChecklistFlow": "Run a saved flow", - "welcomeChecklistFlowHint": "Flows → pick one → Run. The bundled extract-summarize.yaml is a good first run.", + "welcomeChecklistFlowHint": "Flows → pick one → Run. Studio ships a few demo flows; one of your own works too.", "welcomeChecklistAllDone": "All four steps complete.", "welcomeChecklistDismiss": "Hide checklist", "welcomeChecklistRefresh": "Refresh", @@ -109,7 +109,7 @@ "mcpServerEnLanguageBadge": "Server text in English — Studio shows it as supplied.", "themePluginHeader": "Theme plugin", "themePluginNone": "Built-in (no plugin)", - "themePluginHint": "Pick an installed studio.theme.* plugin to re-skin the whole app. Install one from the Store first.", + "themePluginHint": "Built-in and plugin tiles ship hand-curated palettes. The Custom tile derives a full Material 3 palette (primary, secondary, tertiary, surface, …) from one seed colour you pick — it's a complete theme, not a single-colour tweak.", "themePluginEmpty": "No theme plugins installed. Open the Store and pick one in the studio-plugin category.", "themePluginCustom": "Custom colour", "themePluginCustomDialogTitle": "Custom theme — pick a seed colour", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e19ae51..87bcf10 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -305,7 +305,7 @@ abstract class AppLocalizations { /// No description provided for @welcomeChecklistMcpHint. /// /// In en, this message translates to: - /// **'Store → Today hero, or Settings → MCP Clients. DeepWiki and Semgrep are one click each.'** + /// **'Add an MCP source from the Store, or configure one under Settings → MCP Clients.'** String get welcomeChecklistMcpHint; /// No description provided for @welcomeChecklistModule. @@ -317,7 +317,7 @@ abstract class AppLocalizations { /// No description provided for @welcomeChecklistModuleHint. /// /// In en, this message translates to: - /// **'Store → search \"text\" → Install. Try text.extract for PDFs and DOCX.'** + /// **'Install a module from the Store — pick whichever category fits your work. Modules are sandboxed F∆I components.'** String get welcomeChecklistModuleHint; /// No description provided for @welcomeChecklistFlow. @@ -329,7 +329,7 @@ abstract class AppLocalizations { /// No description provided for @welcomeChecklistFlowHint. /// /// In en, this message translates to: - /// **'Flows → pick one → Run. The bundled extract-summarize.yaml is a good first run.'** + /// **'Flows → pick one → Run. Studio ships a few demo flows; one of your own works too.'** String get welcomeChecklistFlowHint; /// No description provided for @welcomeChecklistAllDone. @@ -653,7 +653,7 @@ abstract class AppLocalizations { /// No description provided for @themePluginHint. /// /// In en, this message translates to: - /// **'Pick an installed studio.theme.* plugin to re-skin the whole app. Install one from the Store first.'** + /// **'Built-in and plugin tiles ship hand-curated palettes. The Custom tile derives a full Material 3 palette (primary, secondary, tertiary, surface, …) from one seed colour you pick — it\'s a complete theme, not a single-colour tweak.'** String get themePluginHint; /// No description provided for @themePluginEmpty. diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index ffd8c5e..e76038f 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -128,21 +128,21 @@ class AppLocalizationsDe extends AppLocalizations { @override String get welcomeChecklistMcpHint => - 'Store → Today-Hero, oder Einstellungen → MCP-Clients. DeepWiki und Semgrep sind je ein Klick.'; + 'Im Store eine MCP-Quelle hinzufügen oder unter Einstellungen → MCP-Clients konfigurieren.'; @override String get welcomeChecklistModule => 'Ein Text-Modul installieren'; @override String get welcomeChecklistModuleHint => - 'Store → nach „text\" suchen → Installieren. text.extract für PDFs und DOCX ist ein guter Start.'; + 'Im Store ein Modul aus der Kategorie deiner Wahl installieren. Module sind sandboxed F∆I-Komponenten.'; @override String get welcomeChecklistFlow => 'Einen gespeicherten Flow starten'; @override String get welcomeChecklistFlowHint => - 'Flows → einen wählen → Starten. Der mitgelieferte extract-summarize.yaml ist ein guter erster Lauf.'; + 'Flows → einen wählen → Starten. Studio bringt einige Demo-Flows mit; auch ein eigener tut\'s.'; @override String get welcomeChecklistAllDone => 'Alle vier Schritte erledigt.'; @@ -320,7 +320,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get themePluginHint => - 'Installiertes studio.theme.*-Plugin auswählen, um das ganze Studio umzustylen. Erst eines aus dem Store installieren.'; + 'Built-in- und Plugin-Kacheln bringen handkuratierte Paletten mit. Die Kachel „Eigene Farbe\" leitet aus einer gewählten Seedfarbe eine vollständige Material-3-Palette ab (primary, secondary, tertiary, surface, …) — das ist ein vollwertiges Theme, kein Einzelfarb-Tweak.'; @override String get themePluginEmpty => diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fb9da6a..f21b233 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -128,21 +128,21 @@ class AppLocalizationsEn extends AppLocalizations { @override String get welcomeChecklistMcpHint => - 'Store → Today hero, or Settings → MCP Clients. DeepWiki and Semgrep are one click each.'; + 'Add an MCP source from the Store, or configure one under Settings → MCP Clients.'; @override String get welcomeChecklistModule => 'Install a text module'; @override String get welcomeChecklistModuleHint => - 'Store → search \"text\" → Install. Try text.extract for PDFs and DOCX.'; + 'Install a module from the Store — pick whichever category fits your work. Modules are sandboxed F∆I components.'; @override String get welcomeChecklistFlow => 'Run a saved flow'; @override String get welcomeChecklistFlowHint => - 'Flows → pick one → Run. The bundled extract-summarize.yaml is a good first run.'; + 'Flows → pick one → Run. Studio ships a few demo flows; one of your own works too.'; @override String get welcomeChecklistAllDone => 'All four steps complete.'; @@ -320,7 +320,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get themePluginHint => - 'Pick an installed studio.theme.* plugin to re-skin the whole app. Install one from the Store first.'; + 'Built-in and plugin tiles ship hand-curated palettes. The Custom tile derives a full Material 3 palette (primary, secondary, tertiary, surface, …) from one seed colour you pick — it\'s a complete theme, not a single-colour tweak.'; @override String get themePluginEmpty => diff --git a/lib/main.dart b/lib/main.dart index 6ed6b07..ca6f4d0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -227,15 +227,31 @@ class StudioShell extends StatefulWidget { const StudioShell({super.key}); @override - State createState() => _StudioShellState(); + State createState() => StudioShellState(); } -class _StudioShellState extends State { +class StudioShellState extends State { int _selectedIndex = 0; bool? _connected; // null = unknown, polled on a timer String? _activeChannel; // local / dev / beta / production Timer? _healthPoll; + /// Switch the sidebar's selected page by its registered id + /// (`store`, `flows`, `audit`, etc.). Used by the welcome + /// page's onboarding checklist to make each item a real + /// navigation hop instead of a static hint. Silently + /// ignores unknown ids. + void navigateTo(String pageId) { + final idx = _pages.indexWhere((p) => p.id == pageId); + if (idx < 0) return; + setState(() => _selectedIndex = idx); + } + + /// Lookup helper — descendants call this to drive nav + /// without holding a controller reference. + static StudioShellState? of(BuildContext context) => + context.findAncestorStateOfType(); + static const _pages = <_NavPage>[ _NavPage( id: 'welcome', diff --git a/lib/pages/welcome.dart b/lib/pages/welcome.dart index f3201af..b92c138 100644 --- a/lib/pages/welcome.dart +++ b/lib/pages/welcome.dart @@ -16,6 +16,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../data/hub.dart'; import '../data/system_actions.dart'; import '../l10n/app_localizations.dart'; +import '../main.dart' show StudioShellState; import '../theme/theme.dart'; import '../theme/tokens.dart'; import '../widgets/widgets.dart'; @@ -472,11 +473,36 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> { final theme = Theme.of(context); final l = AppLocalizations.of(context)!; final allDone = _aiOk && _mcpOk && _moduleOk && _flowOk; - final items = <(bool, String, String)>[ - (_aiOk, l.welcomeChecklistAi, l.welcomeChecklistAiHint), - (_mcpOk, l.welcomeChecklistMcp, l.welcomeChecklistMcpHint), - (_moduleOk, l.welcomeChecklistModule, l.welcomeChecklistModuleHint), - (_flowOk, l.welcomeChecklistFlow, l.welcomeChecklistFlowHint), + // Each row gets an `onTap` that takes the operator to + // the page where they can actually complete the item. + // Replaces the old "read the hint then guess where the + // feature lives" behaviour with a one-click hop. + final shell = StudioShellState.of(context); + final items = <(bool, String, String, VoidCallback?)>[ + ( + _aiOk, + l.welcomeChecklistAi, + l.welcomeChecklistAiHint, + () => FaiSettingsDialog.show(context), + ), + ( + _mcpOk, + l.welcomeChecklistMcp, + l.welcomeChecklistMcpHint, + () => FaiSettingsDialog.show(context), + ), + ( + _moduleOk, + l.welcomeChecklistModule, + l.welcomeChecklistModuleHint, + shell == null ? null : () => shell.navigateTo('store'), + ), + ( + _flowOk, + l.welcomeChecklistFlow, + l.welcomeChecklistFlowHint, + shell == null ? null : () => shell.navigateTo('flows'), + ), ]; return Padding( padding: const EdgeInsets.only(bottom: FaiSpace.xxl), @@ -533,6 +559,7 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> { done: items[i].$1, title: items[i].$2, hint: items[i].$3, + onTap: items[i].$4, ), ], ], @@ -693,18 +720,23 @@ class _ChecklistRow extends StatelessWidget { final bool done; final String title; final String hint; + /// Tap handler — takes the operator to the page where + /// they can actually complete the item. null = no-op + /// (e.g. AI item when System AI was already configured). + final VoidCallback? onTap; const _ChecklistRow({ required this.done, required this.title, required this.hint, + this.onTap, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); final l = AppLocalizations.of(context)!; - return Padding( + final row = Padding( padding: const EdgeInsets.all(FaiSpace.lg), child: Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -747,6 +779,11 @@ class _ChecklistRow extends StatelessWidget { ], ), ); + // Wrap in InkWell only when a tap target exists so done + // rows (no action needed) don't shimmer hover-feedback + // they can't act on. + if (onTap == null) return row; + return InkWell(onTap: onTap, child: row); } } diff --git a/lib/widgets/theme_picker_grid.dart b/lib/widgets/theme_picker_grid.dart index 7be766a..1956a51 100644 --- a/lib/widgets/theme_picker_grid.dart +++ b/lib/widgets/theme_picker_grid.dart @@ -149,6 +149,10 @@ class _ThemePickerGridState extends State { final seed = seedHex != null ? _parseHexColor(seedHex) : theme.colorScheme.primary; + // Material 3 derives a full ColorScheme from one seed — + // primary, secondary, tertiary, surface, outline, on-*. + // The custom tile previews that full palette so it reads + // visually as a complete theme, NOT a single colour. final scheme = ColorScheme.fromSeed( seedColor: seed ?? theme.colorScheme.primary, brightness: theme.brightness, @@ -157,6 +161,11 @@ class _ThemePickerGridState extends State { label: l.themePluginCustom, swatches: [scheme.primary, scheme.secondary, scheme.tertiary], selected: isCustom, + // Slight visual cue that this tile is a different + // *source* of theme (seed-derived) compared to the + // plugin tiles (hand-curated palette). Same widget, the + // dashed border separates the concept. + editable: true, onTap: () async { final picked = await _showColorPickerDialog( context, @@ -195,6 +204,11 @@ class _Tile extends StatelessWidget { final List swatches; final bool selected; final bool loading; + /// Adds a small tune icon at the top-right, telegraphing + /// "this tile opens an editor instead of applying + /// immediately". Used for the Custom tile so the operator + /// knows clicking it will open the colour-picker dialog. + final bool editable; final VoidCallback onTap; const _Tile({ required this.label, @@ -202,6 +216,7 @@ class _Tile extends StatelessWidget { required this.selected, required this.onTap, this.loading = false, + this.editable = false, }); @override @@ -273,6 +288,12 @@ class _Tile extends StatelessWidget { ), ), ), + if (editable && !selected) + Icon( + Icons.tune, + size: 14, + color: theme.colorScheme.onSurfaceVariant, + ), if (selected) Icon( Icons.check_circle, diff --git a/pubspec.lock b/pubspec.lock index 4bd2e3a..ab9a847 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -125,10 +125,10 @@ packages: description: path: "." ref: main - resolved-ref: "7b256b5e35204771b8eeca3deb1f4071d86a035b" + resolved-ref: "1e9968496e7f04c04143a636b0cfe0b4c344dfa0" url: "https://git.flemming.ai/fai/studio-flow-editor" source: git - version: "0.11.0" + version: "0.12.0" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c9b75f5..8ce49d7 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.58.0 +version: 0.59.0 environment: sdk: ^3.11.0-200.1.beta