feat(studio): localise Settings sidebar + add inline help docs + Approvals doc
Some checks failed
Security / Security check (push) Failing after 1s

Three operator-UX gaps closed:

  - Settings dialog's six-category sidebar (General / Appearance /
    System AI / Integrations / Security / Maintenance) was
    hardcoded English; now flows through AppLocalizations
    ("Allgemein" / "Darstellung" / "System-KI" / "Integrationen" /
    "Sicherheit" / "Wartung"). Same for the per-panel title +
    description.
  - Per-channel daemon-action labels ('enable autostart' /
    'disable autostart' / 'daemon restart' etc.) and the
    OK / Failed result line in the toast also moved to l10n,
    so the system-action feedback reads as one language.
  - New 'Approvals' doc bundle (en + de) under assets/docs/,
    registered as a fifth doc card on Welcome plus exposed via
    the new public  helper. The
    Approvals, Audit and Doctor app-bars grow a Help icon button
    next to Refresh that opens the matching doc in the existing
    bottom-sheet reader — no extra screen, no learning curve.

Studio bumped to 0.67.0; editor path-override pulls in 0.20.1
(flow-list row polish).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-06-09 01:57:46 +02:00
parent 2be7d241bf
commit 28fafce7dc
15 changed files with 681 additions and 36 deletions

View file

@ -311,11 +311,12 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
});
final r = await action();
if (!mounted) return;
final l = AppLocalizations.of(context)!;
setState(() {
_saving = false;
_channelToast = r.ok
? 'OK · $label\n${r.stdout.trim()}'
: 'Failed · $label\n${(r.stderr.isEmpty ? r.stdout : r.stderr).trim()}';
? '${l.daemonActionResultOk(label)}\n${r.stdout.trim()}'
: '${l.daemonActionResultFailed(label)}\n${(r.stderr.isEmpty ? r.stdout : r.stderr).trim()}';
});
}
@ -528,13 +529,13 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
onEnableAutostart: _saving
? null
: () => _runDaemon(
'enable autostart',
l.daemonActionEnableAutostart,
() => SystemActions.faiDaemonEnable(ch.name),
),
onDisableAutostart: _saving
? null
: () => _runDaemon(
'disable autostart',
l.daemonActionDisableAutostart,
() => SystemActions.faiDaemonDisable(ch.name),
),
),
@ -562,10 +563,8 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
List<Widget> _aiPanel(AppLocalizations l, ThemeData theme) {
return [
_panelTitle(
'System AI',
'Operator-managed LLM endpoint used by Studio for the AI '
'helpers (system-ai-edit, install-suggest, doctor-summary). '
'No flow ever calls this; modules speak to their own LLM.',
l.settingsAiPanelTitle,
l.settingsAiPanelBody,
theme,
),
if (_aiStatus == null)
@ -586,10 +585,8 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
List<Widget> _integrationsPanel(AppLocalizations l, ThemeData theme) {
return [
_panelTitle(
'Integrations',
'External tool servers the hub federates capabilities from. '
'MCP servers expose tool-shaped endpoints; n8n endpoints '
'expose hosted workflows.',
l.settingsIntegrationsPanelTitle,
l.settingsIntegrationsPanelBody,
theme,
),
if (_mcpClients == null)
@ -618,9 +615,8 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
List<Widget> _securityPanel(AppLocalizations l, ThemeData theme) {
return [
_panelTitle(
'Security & credentials',
'Tokens the hub holds on the operator\'s behalf. Stored in '
'~/.fai/ with mode 0600; never sent in telemetry.',
l.settingsSecurityPanelTitle,
l.settingsSecurityPanelBody,
theme,
),
_RegistryCredentialsPanel(
@ -640,9 +636,8 @@ class _FaiSettingsDialogState extends State<FaiSettingsDialog> {
List<Widget> _maintenancePanel(AppLocalizations l, ThemeData theme) {
return [
_panelTitle(
'Maintenance',
'Destructive operations gated behind explicit confirm '
'dialogs. Use when re-pilot-testing or debugging.',
l.settingsMaintenancePanelTitle,
l.settingsMaintenancePanelBody,
theme,
),
_MaintenancePanel(
@ -2525,19 +2520,19 @@ class _ScopeRow extends StatelessWidget {
),
IconButton(
icon: const Icon(Icons.arrow_upward, size: 16),
tooltip: 'Move up',
tooltip: AppLocalizations.of(context)!.tooltipMoveUp,
onPressed: busy || index == 0 ? null : onUp,
visualDensity: VisualDensity.compact,
),
IconButton(
icon: const Icon(Icons.arrow_downward, size: 16),
tooltip: 'Move down',
tooltip: AppLocalizations.of(context)!.tooltipMoveDown,
onPressed: busy || index == total - 1 ? null : onDown,
visualDensity: VisualDensity.compact,
),
IconButton(
icon: const Icon(Icons.delete_outline, size: 16),
tooltip: 'Remove',
tooltip: AppLocalizations.of(context)!.tooltipRemove,
onPressed: busy ? null : onRemove,
visualDensity: VisualDensity.compact,
),
@ -2559,13 +2554,26 @@ class _Sidebar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final l = AppLocalizations.of(context)!;
final items = <(_Category, IconData, String)>[
(_Category.general, Icons.tune, 'General'),
(_Category.appearance, Icons.palette_outlined, 'Appearance'),
(_Category.ai, Icons.psychology_outlined, 'System AI'),
(_Category.integrations, Icons.hub_outlined, 'Integrations'),
(_Category.security, Icons.shield_outlined, 'Security'),
(_Category.maintenance, Icons.build_outlined, 'Maintenance'),
(_Category.general, Icons.tune, l.settingsCategoryGeneral),
(
_Category.appearance,
Icons.palette_outlined,
l.settingsCategoryAppearance,
),
(_Category.ai, Icons.psychology_outlined, l.settingsCategoryAi),
(
_Category.integrations,
Icons.hub_outlined,
l.settingsCategoryIntegrations,
),
(_Category.security, Icons.shield_outlined, l.settingsCategorySecurity),
(
_Category.maintenance,
Icons.build_outlined,
l.settingsCategoryMaintenance,
),
];
return SizedBox(
width: 220,