feat(studio): brand-agnostic onboarding hints + clickable checklist + theme-picker clarity
Some checks failed
Security / Security check (push) Failing after 2s

Three changes:

1. Onboarding checklist hints no longer mention DeepWiki,
   Semgrep, text.extract, or extract-summarize.yaml by
   name. Brand- and file-specific copy moved to neutral
   feature pointers ('add an MCP source', 'install a
   module'). Brand names belong on the welcome page only
   if they're load-bearing for understanding, which they
   aren't.

2. Checklist rows are now clickable. Tapping a row navigates
   to the page where the operator can complete it — MCP +
   AI open Settings, Module opens Store, Flow opens Flows.
   StudioShellState gains a public navigateTo(pageId) helper
   so descendants can drive sidebar selection without
   plumbing a controller through props.

3. Theme picker hint copy rewritten. Old hint suggested
   'Custom' was a single-colour tweak; new copy spells out
   that Material 3 derives a full palette (primary,
   secondary, tertiary, surface, …) from one seed colour.
   The Custom tile shows a small tune icon so its
   open-the-picker semantics is visually distinct from the
   apply-immediately plugin tiles.

Editor bumped to 0.12.0 (panel toggle + LabVIEW-style port
colours).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-06-02 11:13:38 +02:00
parent c1c60d5434
commit 3db875c903
10 changed files with 105 additions and 31 deletions

View file

@ -41,11 +41,11 @@
"welcomeChecklistAi": "System-AI konfigurieren", "welcomeChecklistAi": "System-AI konfigurieren",
"welcomeChecklistAiHint": "Einstellungen → System-AI. Wird für die KI-Suche und Fehlererklärungen gebraucht.", "welcomeChecklistAiHint": "Einstellungen → System-AI. Wird für die KI-Suche und Fehlererklärungen gebraucht.",
"welcomeChecklistMcp": "Öffentliche Capability-Quelle hinzufügen", "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", "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", "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.", "welcomeChecklistAllDone": "Alle vier Schritte erledigt.",
"welcomeChecklistDismiss": "Checkliste ausblenden", "welcomeChecklistDismiss": "Checkliste ausblenden",
"welcomeChecklistRefresh": "Aktualisieren", "welcomeChecklistRefresh": "Aktualisieren",
@ -106,7 +106,7 @@
"mcpServerEnLanguageBadge": "Server-Text in Englisch — Studio zeigt ihn wie geliefert.", "mcpServerEnLanguageBadge": "Server-Text in Englisch — Studio zeigt ihn wie geliefert.",
"themePluginHeader": "Theme-Plugin", "themePluginHeader": "Theme-Plugin",
"themePluginNone": "Standard (kein 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.", "themePluginEmpty": "Kein Theme-Plugin installiert. Im Store eines aus der Kategorie studio-plugin auswählen.",
"themePluginCustom": "Eigene Farbe", "themePluginCustom": "Eigene Farbe",
"themePluginCustomDialogTitle": "Eigenes Theme — Seedfarbe wählen", "themePluginCustomDialogTitle": "Eigenes Theme — Seedfarbe wählen",

View file

@ -44,11 +44,11 @@
"welcomeChecklistAi": "Configure System AI", "welcomeChecklistAi": "Configure System AI",
"welcomeChecklistAiHint": "Settings → System AI. Required for the AI search and inline failure explanations.", "welcomeChecklistAiHint": "Settings → System AI. Required for the AI search and inline failure explanations.",
"welcomeChecklistMcp": "Add a public capability source", "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", "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", "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.", "welcomeChecklistAllDone": "All four steps complete.",
"welcomeChecklistDismiss": "Hide checklist", "welcomeChecklistDismiss": "Hide checklist",
"welcomeChecklistRefresh": "Refresh", "welcomeChecklistRefresh": "Refresh",
@ -109,7 +109,7 @@
"mcpServerEnLanguageBadge": "Server text in English — Studio shows it as supplied.", "mcpServerEnLanguageBadge": "Server text in English — Studio shows it as supplied.",
"themePluginHeader": "Theme plugin", "themePluginHeader": "Theme plugin",
"themePluginNone": "Built-in (no 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.", "themePluginEmpty": "No theme plugins installed. Open the Store and pick one in the studio-plugin category.",
"themePluginCustom": "Custom colour", "themePluginCustom": "Custom colour",
"themePluginCustomDialogTitle": "Custom theme — pick a seed colour", "themePluginCustomDialogTitle": "Custom theme — pick a seed colour",

View file

@ -305,7 +305,7 @@ abstract class AppLocalizations {
/// No description provided for @welcomeChecklistMcpHint. /// No description provided for @welcomeChecklistMcpHint.
/// ///
/// In en, this message translates to: /// 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; String get welcomeChecklistMcpHint;
/// No description provided for @welcomeChecklistModule. /// No description provided for @welcomeChecklistModule.
@ -317,7 +317,7 @@ abstract class AppLocalizations {
/// No description provided for @welcomeChecklistModuleHint. /// No description provided for @welcomeChecklistModuleHint.
/// ///
/// In en, this message translates to: /// 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; String get welcomeChecklistModuleHint;
/// No description provided for @welcomeChecklistFlow. /// No description provided for @welcomeChecklistFlow.
@ -329,7 +329,7 @@ abstract class AppLocalizations {
/// No description provided for @welcomeChecklistFlowHint. /// No description provided for @welcomeChecklistFlowHint.
/// ///
/// In en, this message translates to: /// 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; String get welcomeChecklistFlowHint;
/// No description provided for @welcomeChecklistAllDone. /// No description provided for @welcomeChecklistAllDone.
@ -653,7 +653,7 @@ abstract class AppLocalizations {
/// No description provided for @themePluginHint. /// No description provided for @themePluginHint.
/// ///
/// In en, this message translates to: /// 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; String get themePluginHint;
/// No description provided for @themePluginEmpty. /// No description provided for @themePluginEmpty.

View file

@ -128,21 +128,21 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get welcomeChecklistMcpHint => 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 @override
String get welcomeChecklistModule => 'Ein Text-Modul installieren'; String get welcomeChecklistModule => 'Ein Text-Modul installieren';
@override @override
String get welcomeChecklistModuleHint => 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 @override
String get welcomeChecklistFlow => 'Einen gespeicherten Flow starten'; String get welcomeChecklistFlow => 'Einen gespeicherten Flow starten';
@override @override
String get welcomeChecklistFlowHint => 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 @override
String get welcomeChecklistAllDone => 'Alle vier Schritte erledigt.'; String get welcomeChecklistAllDone => 'Alle vier Schritte erledigt.';
@ -320,7 +320,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get themePluginHint => 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 @override
String get themePluginEmpty => String get themePluginEmpty =>

View file

@ -128,21 +128,21 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get welcomeChecklistMcpHint => 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 @override
String get welcomeChecklistModule => 'Install a text module'; String get welcomeChecklistModule => 'Install a text module';
@override @override
String get welcomeChecklistModuleHint => 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 @override
String get welcomeChecklistFlow => 'Run a saved flow'; String get welcomeChecklistFlow => 'Run a saved flow';
@override @override
String get welcomeChecklistFlowHint => 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 @override
String get welcomeChecklistAllDone => 'All four steps complete.'; String get welcomeChecklistAllDone => 'All four steps complete.';
@ -320,7 +320,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get themePluginHint => 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 @override
String get themePluginEmpty => String get themePluginEmpty =>

View file

@ -227,15 +227,31 @@ class StudioShell extends StatefulWidget {
const StudioShell({super.key}); const StudioShell({super.key});
@override @override
State<StudioShell> createState() => _StudioShellState(); State<StudioShell> createState() => StudioShellState();
} }
class _StudioShellState extends State<StudioShell> { class StudioShellState extends State<StudioShell> {
int _selectedIndex = 0; int _selectedIndex = 0;
bool? _connected; // null = unknown, polled on a timer bool? _connected; // null = unknown, polled on a timer
String? _activeChannel; // local / dev / beta / production String? _activeChannel; // local / dev / beta / production
Timer? _healthPoll; 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<StudioShellState>();
static const _pages = <_NavPage>[ static const _pages = <_NavPage>[
_NavPage( _NavPage(
id: 'welcome', id: 'welcome',

View file

@ -16,6 +16,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../data/hub.dart'; import '../data/hub.dart';
import '../data/system_actions.dart'; import '../data/system_actions.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../main.dart' show StudioShellState;
import '../theme/theme.dart'; import '../theme/theme.dart';
import '../theme/tokens.dart'; import '../theme/tokens.dart';
import '../widgets/widgets.dart'; import '../widgets/widgets.dart';
@ -472,11 +473,36 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> {
final theme = Theme.of(context); final theme = Theme.of(context);
final l = AppLocalizations.of(context)!; final l = AppLocalizations.of(context)!;
final allDone = _aiOk && _mcpOk && _moduleOk && _flowOk; final allDone = _aiOk && _mcpOk && _moduleOk && _flowOk;
final items = <(bool, String, String)>[ // Each row gets an `onTap` that takes the operator to
(_aiOk, l.welcomeChecklistAi, l.welcomeChecklistAiHint), // the page where they can actually complete the item.
(_mcpOk, l.welcomeChecklistMcp, l.welcomeChecklistMcpHint), // Replaces the old "read the hint then guess where the
(_moduleOk, l.welcomeChecklistModule, l.welcomeChecklistModuleHint), // feature lives" behaviour with a one-click hop.
(_flowOk, l.welcomeChecklistFlow, l.welcomeChecklistFlowHint), 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( return Padding(
padding: const EdgeInsets.only(bottom: FaiSpace.xxl), padding: const EdgeInsets.only(bottom: FaiSpace.xxl),
@ -533,6 +559,7 @@ class _OnboardingChecklistState extends State<_OnboardingChecklist> {
done: items[i].$1, done: items[i].$1,
title: items[i].$2, title: items[i].$2,
hint: items[i].$3, hint: items[i].$3,
onTap: items[i].$4,
), ),
], ],
], ],
@ -693,18 +720,23 @@ class _ChecklistRow extends StatelessWidget {
final bool done; final bool done;
final String title; final String title;
final String hint; 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({ const _ChecklistRow({
required this.done, required this.done,
required this.title, required this.title,
required this.hint, required this.hint,
this.onTap,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final l = AppLocalizations.of(context)!; final l = AppLocalizations.of(context)!;
return Padding( final row = Padding(
padding: const EdgeInsets.all(FaiSpace.lg), padding: const EdgeInsets.all(FaiSpace.lg),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, 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);
} }
} }

View file

@ -149,6 +149,10 @@ class _ThemePickerGridState extends State<ThemePickerGrid> {
final seed = seedHex != null final seed = seedHex != null
? _parseHexColor(seedHex) ? _parseHexColor(seedHex)
: theme.colorScheme.primary; : 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( final scheme = ColorScheme.fromSeed(
seedColor: seed ?? theme.colorScheme.primary, seedColor: seed ?? theme.colorScheme.primary,
brightness: theme.brightness, brightness: theme.brightness,
@ -157,6 +161,11 @@ class _ThemePickerGridState extends State<ThemePickerGrid> {
label: l.themePluginCustom, label: l.themePluginCustom,
swatches: [scheme.primary, scheme.secondary, scheme.tertiary], swatches: [scheme.primary, scheme.secondary, scheme.tertiary],
selected: isCustom, 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 { onTap: () async {
final picked = await _showColorPickerDialog( final picked = await _showColorPickerDialog(
context, context,
@ -195,6 +204,11 @@ class _Tile extends StatelessWidget {
final List<Color> swatches; final List<Color> swatches;
final bool selected; final bool selected;
final bool loading; 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; final VoidCallback onTap;
const _Tile({ const _Tile({
required this.label, required this.label,
@ -202,6 +216,7 @@ class _Tile extends StatelessWidget {
required this.selected, required this.selected,
required this.onTap, required this.onTap,
this.loading = false, this.loading = false,
this.editable = false,
}); });
@override @override
@ -273,6 +288,12 @@ class _Tile extends StatelessWidget {
), ),
), ),
), ),
if (editable && !selected)
Icon(
Icons.tune,
size: 14,
color: theme.colorScheme.onSurfaceVariant,
),
if (selected) if (selected)
Icon( Icon(
Icons.check_circle, Icons.check_circle,

View file

@ -125,10 +125,10 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "7b256b5e35204771b8eeca3deb1f4071d86a035b" resolved-ref: "1e9968496e7f04c04143a636b0cfe0b4c344dfa0"
url: "https://git.flemming.ai/fai/studio-flow-editor" url: "https://git.flemming.ai/fai/studio-flow-editor"
source: git source: git
version: "0.11.0" version: "0.12.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:

View file

@ -1,7 +1,7 @@
name: fai_studio name: fai_studio
description: "F∆I Studio — desktop GUI for the F∆I hub" description: "F∆I Studio — desktop GUI for the F∆I hub"
publish_to: 'none' publish_to: 'none'
version: 0.58.0 version: 0.59.0
environment: environment:
sdk: ^3.11.0-200.1.beta sdk: ^3.11.0-200.1.beta