From 3e1ce6e1fde2ea778e8b548f4510a1188889bb30 Mon Sep 17 00:00:00 2001 From: flemming-it Date: Fri, 17 Jul 2026 23:24:46 +0200 Subject: [PATCH] fix(welcome): concept cards speak everyday language first The Hub/Module/Flow cards explained the basics in the very vocabulary they were supposed to introduce (Rust binary, WASM, YAML, hash-chained). Each card now leads with a plain-language explanation; the technical wording moved behind a per-card 'for the technically curious' toggle. DE and EN. Signed-off-by: flemming-it --- lib/l10n/app_de.arb | 10 ++-- lib/l10n/app_en.arb | 10 ++-- lib/l10n/app_localizations.dart | 40 ++++++++++++++-- lib/l10n/app_localizations_de.dart | 28 +++++++++-- lib/l10n/app_localizations_en.dart | 26 ++++++++-- lib/pages/welcome.dart | 77 ++++++++++++++++++++++++++---- 6 files changed, 163 insertions(+), 28 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 1d4169e..5947d39 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -22,11 +22,15 @@ "welcomeHeroTitle": "Ch∆In Platform", "welcomeHeroSubtitle": "Deterministische Workflow-Engine für KI-gestützte Datenverarbeitung in regulierten Umgebungen.", "welcomePillarHubTitle": "Hub", - "welcomePillarHubBody": "Ein Rust-Binary, das Module lädt und Flows ausführt. SQLite für den State. Kein Docker, kein Message-Broker, keine Hintergrunddienste, die gepflegt werden müssen.", + "welcomePillarHubBody": "Das Programm, das alles ausführt. Es läuft direkt auf Ihrem Rechner, braucht keine Zusatzsoftware und speichert alles Wichtige in einer einzigen Datei.", + "welcomePillarHubTech": "Ein Rust-Binary, das Module lädt und Flows ausführt. SQLite für den Zustand. Kein Docker, kein Message-Broker, keine Hintergrunddienste, die gepflegt werden müssen.", "welcomePillarModuleTitle": "Modul", - "welcomePillarModuleBody": "Eine kleine WebAssembly-Komponente, die eine Sache macht. Deklariert Netzwerk-Endpunkte, Dateien und Umgebungsvariablen, auf die sie zugreift; der Hub setzt die Liste durch.", + "welcomePillarModuleBody": "Ein Baustein, der genau eine Aufgabe erledigt — zum Beispiel Text aus Dokumenten lesen. Jedes Modul sagt vorab, worauf es zugreifen will; mehr erlaubt der Hub nicht.", + "welcomePillarModuleTech": "Eine kleine WebAssembly-Komponente in einer Sandbox. Deklariert Netzwerk-Endpunkte, Dateien und Umgebungsvariablen, auf die sie zugreift; der Hub setzt die Liste durch.", "welcomePillarFlowTitle": "Flow", - "welcomePillarFlowBody": "Eine YAML-Datei, die Module verkettet. Eingaben rein, Module laufen in der deklarierten Reihenfolge, Ausgaben raus — jeder Schritt in einem hash-verketteten Audit-Log festgehalten.", + "welcomePillarFlowBody": "Ein Ablaufplan, der Module zu festen Schritten verbindet. Eingaben rein, Schritte laufen der Reihe nach, Ergebnis raus — und jeder Schritt wird fälschungssicher protokolliert.", + "welcomePillarFlowTech": "Eine YAML-Datei, die Module verkettet; jeder Schritt wird in einem hash-verketteten Audit-Log festgehalten, das die Diagnose-Seite komplett verifiziert.", + "welcomePillarTechToggle": "Für Technik-Interessierte", "welcomeTrustHeader": "TRUST-POSTURE", "welcomeTrustSandboxTitle": "Sandbox von Anfang an", "welcomeTrustSandboxBody": "Jedes Modul bringt eine explizite Berechtigungsliste mit — Netzwerk-Endpunkte, Dateien, Umgebungsvariablen. Der Hub setzt sie durch; ohne Operator-Freigabe verlässt nichts die Sandbox.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 834ca33..ec0593e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -30,11 +30,15 @@ "welcomeHeroTitle": "Ch∆In Platform", "welcomeHeroSubtitle": "Deterministic workflow engine for AI-assisted data processing in regulated environments.", "welcomePillarHubTitle": "Hub", - "welcomePillarHubBody": "One Rust binary that loads modules and runs flows. SQLite for state. No Docker, no message broker, no background services to babysit.", + "welcomePillarHubBody": "The program that runs everything. It works right on your machine, needs no extra software, and keeps everything important in a single file.", + "welcomePillarHubTech": "One Rust binary that loads modules and runs flows. SQLite for state. No Docker, no message broker, no background services to babysit.", "welcomePillarModuleTitle": "Module", - "welcomePillarModuleBody": "A small WebAssembly component that does one thing. Declares the network endpoints, files, and environment variables it touches; the hub enforces the list.", + "welcomePillarModuleBody": "A building block that does exactly one job — reading text out of documents, say. Every module states up front what it wants to touch; the hub allows nothing more.", + "welcomePillarModuleTech": "A small, sandboxed WebAssembly component. Declares the network endpoints, files, and environment variables it touches; the hub enforces the list.", "welcomePillarFlowTitle": "Flow", - "welcomePillarFlowBody": "A YAML file that chains modules together. Inputs go in, modules run in declared order, outputs come out — every step recorded in a hash-chained audit log.", + "welcomePillarFlowBody": "A recipe that connects modules into fixed steps. Inputs go in, steps run in order, the result comes out — and every step is recorded tamper-evidently.", + "welcomePillarFlowTech": "A YAML file that chains modules together; every step is recorded in a hash-chained audit log that the Diagnostics page verifies in full.", + "welcomePillarTechToggle": "For the technically curious", "welcomeTrustHeader": "TRUST POSTURE", "welcomeTrustSandboxTitle": "Sandbox by default", "welcomeTrustSandboxBody": "Every module ships with an explicit permission list — network endpoints, files, environment variables. The hub enforces it; nothing reaches outside the sandbox without operator approval.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index c794973..f34b5cc 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -233,9 +233,15 @@ abstract class AppLocalizations { /// No description provided for @welcomePillarHubBody. /// /// In en, this message translates to: - /// **'One Rust binary that loads modules and runs flows. SQLite for state. No Docker, no message broker, no background services to babysit.'** + /// **'The program that runs everything. It works right on your machine, needs no extra software, and keeps everything important in a single file.'** String get welcomePillarHubBody; + /// No description provided for @welcomePillarHubTech. + /// + /// In en, this message translates to: + /// **'One Rust binary that loads modules and runs flows. SQLite for state. No Docker, no message broker, no background services to babysit.'** + String get welcomePillarHubTech; + /// No description provided for @welcomePillarModuleTitle. /// /// In en, this message translates to: @@ -245,9 +251,15 @@ abstract class AppLocalizations { /// No description provided for @welcomePillarModuleBody. /// /// In en, this message translates to: - /// **'A small WebAssembly component that does one thing. Declares the network endpoints, files, and environment variables it touches; the hub enforces the list.'** + /// **'A building block that does exactly one job — reading text out of documents, say. Every module states up front what it wants to touch; the hub allows nothing more.'** String get welcomePillarModuleBody; + /// No description provided for @welcomePillarModuleTech. + /// + /// In en, this message translates to: + /// **'A small, sandboxed WebAssembly component. Declares the network endpoints, files, and environment variables it touches; the hub enforces the list.'** + String get welcomePillarModuleTech; + /// No description provided for @welcomePillarFlowTitle. /// /// In en, this message translates to: @@ -257,9 +269,21 @@ abstract class AppLocalizations { /// No description provided for @welcomePillarFlowBody. /// /// In en, this message translates to: - /// **'A YAML file that chains modules together. Inputs go in, modules run in declared order, outputs come out — every step recorded in a hash-chained audit log.'** + /// **'A recipe that connects modules into fixed steps. Inputs go in, steps run in order, the result comes out — and every step is recorded tamper-evidently.'** String get welcomePillarFlowBody; + /// No description provided for @welcomePillarFlowTech. + /// + /// In en, this message translates to: + /// **'A YAML file that chains modules together; every step is recorded in a hash-chained audit log that the Diagnostics page verifies in full.'** + String get welcomePillarFlowTech; + + /// No description provided for @welcomePillarTechToggle. + /// + /// In en, this message translates to: + /// **'For the technically curious'** + String get welcomePillarTechToggle; + /// No description provided for @welcomeTrustHeader. /// /// In en, this message translates to: @@ -5056,15 +5080,21 @@ abstract class AppLocalizations { /// No description provided for @runsEmptyTitle. /// /// In en, this message translates to: - /// **'No detached runs'** + /// **'No background runs'** String get runsEmptyTitle; /// No description provided for @runsEmptyHint. /// /// In en, this message translates to: - /// **'Runs submitted with detach stay here until they finish. Detached invocations are opt-in — enable detached.enabled in the operator config to use them.'** + /// **'Runs that keep working in the background while you do something else appear here. The feature is optional and off by default — the guide shows step by step how to turn it on.'** String get runsEmptyHint; + /// No description provided for @runsEmptyGuideButton. + /// + /// In en, this message translates to: + /// **'Open the guide'** + String get runsEmptyGuideButton; + /// No description provided for @runsCancelButton. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 4a4a454..d5b65a8 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -83,21 +83,36 @@ class AppLocalizationsDe extends AppLocalizations { @override String get welcomePillarHubBody => - 'Ein Rust-Binary, das Module lädt und Flows ausführt. SQLite für den State. Kein Docker, kein Message-Broker, keine Hintergrunddienste, die gepflegt werden müssen.'; + 'Das Programm, das alles ausführt. Es läuft direkt auf Ihrem Rechner, braucht keine Zusatzsoftware und speichert alles Wichtige in einer einzigen Datei.'; + + @override + String get welcomePillarHubTech => + 'Ein Rust-Binary, das Module lädt und Flows ausführt. SQLite für den Zustand. Kein Docker, kein Message-Broker, keine Hintergrunddienste, die gepflegt werden müssen.'; @override String get welcomePillarModuleTitle => 'Modul'; @override String get welcomePillarModuleBody => - 'Eine kleine WebAssembly-Komponente, die eine Sache macht. Deklariert Netzwerk-Endpunkte, Dateien und Umgebungsvariablen, auf die sie zugreift; der Hub setzt die Liste durch.'; + 'Ein Baustein, der genau eine Aufgabe erledigt — zum Beispiel Text aus Dokumenten lesen. Jedes Modul sagt vorab, worauf es zugreifen will; mehr erlaubt der Hub nicht.'; + + @override + String get welcomePillarModuleTech => + 'Eine kleine WebAssembly-Komponente in einer Sandbox. Deklariert Netzwerk-Endpunkte, Dateien und Umgebungsvariablen, auf die sie zugreift; der Hub setzt die Liste durch.'; @override String get welcomePillarFlowTitle => 'Flow'; @override String get welcomePillarFlowBody => - 'Eine YAML-Datei, die Module verkettet. Eingaben rein, Module laufen in der deklarierten Reihenfolge, Ausgaben raus — jeder Schritt in einem hash-verketteten Audit-Log festgehalten.'; + 'Ein Ablaufplan, der Module zu festen Schritten verbindet. Eingaben rein, Schritte laufen der Reihe nach, Ergebnis raus — und jeder Schritt wird fälschungssicher protokolliert.'; + + @override + String get welcomePillarFlowTech => + 'Eine YAML-Datei, die Module verkettet; jeder Schritt wird in einem hash-verketteten Audit-Log festgehalten, das die Diagnose-Seite komplett verifiziert.'; + + @override + String get welcomePillarTechToggle => 'Für Technik-Interessierte'; @override String get welcomeTrustHeader => 'TRUST-POSTURE'; @@ -2980,11 +2995,14 @@ class AppLocalizationsDe extends AppLocalizations { String get runsReloadTooltip => 'Lauf-Liste neu laden'; @override - String get runsEmptyTitle => 'Keine abgekoppelten Läufe'; + String get runsEmptyTitle => 'Keine Läufe im Hintergrund'; @override String get runsEmptyHint => - 'Mit detach gestartete Läufe stehen hier, bis sie fertig sind. Abgekoppelte Aufrufe sind optional — dazu detached.enabled in der Betreiber-Konfiguration aktivieren.'; + 'Hier erscheinen Läufe, die im Hintergrund weiterlaufen, während Sie anderes tun. Diese Funktion ist optional und standardmäßig ausgeschaltet — die Anleitung zeigt Schritt für Schritt, wie sie eingeschaltet wird.'; + + @override + String get runsEmptyGuideButton => 'Anleitung öffnen'; @override String get runsCancelButton => 'Abbrechen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 18289f3..86b1c66 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -84,6 +84,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get welcomePillarHubBody => + 'The program that runs everything. It works right on your machine, needs no extra software, and keeps everything important in a single file.'; + + @override + String get welcomePillarHubTech => 'One Rust binary that loads modules and runs flows. SQLite for state. No Docker, no message broker, no background services to babysit.'; @override @@ -91,14 +95,25 @@ class AppLocalizationsEn extends AppLocalizations { @override String get welcomePillarModuleBody => - 'A small WebAssembly component that does one thing. Declares the network endpoints, files, and environment variables it touches; the hub enforces the list.'; + 'A building block that does exactly one job — reading text out of documents, say. Every module states up front what it wants to touch; the hub allows nothing more.'; + + @override + String get welcomePillarModuleTech => + 'A small, sandboxed WebAssembly component. Declares the network endpoints, files, and environment variables it touches; the hub enforces the list.'; @override String get welcomePillarFlowTitle => 'Flow'; @override String get welcomePillarFlowBody => - 'A YAML file that chains modules together. Inputs go in, modules run in declared order, outputs come out — every step recorded in a hash-chained audit log.'; + 'A recipe that connects modules into fixed steps. Inputs go in, steps run in order, the result comes out — and every step is recorded tamper-evidently.'; + + @override + String get welcomePillarFlowTech => + 'A YAML file that chains modules together; every step is recorded in a hash-chained audit log that the Diagnostics page verifies in full.'; + + @override + String get welcomePillarTechToggle => 'For the technically curious'; @override String get welcomeTrustHeader => 'TRUST POSTURE'; @@ -2981,11 +2996,14 @@ class AppLocalizationsEn extends AppLocalizations { String get runsReloadTooltip => 'Reload the runs list'; @override - String get runsEmptyTitle => 'No detached runs'; + String get runsEmptyTitle => 'No background runs'; @override String get runsEmptyHint => - 'Runs submitted with detach stay here until they finish. Detached invocations are opt-in — enable detached.enabled in the operator config to use them.'; + 'Runs that keep working in the background while you do something else appear here. The feature is optional and off by default — the guide shows step by step how to turn it on.'; + + @override + String get runsEmptyGuideButton => 'Open the guide'; @override String get runsCancelButton => 'Cancel'; diff --git a/lib/pages/welcome.dart b/lib/pages/welcome.dart index 52bcaaf..0c2f6f8 100644 --- a/lib/pages/welcome.dart +++ b/lib/pages/welcome.dart @@ -310,17 +310,24 @@ class _PillarRow extends StatelessWidget { @override Widget build(BuildContext context) { final l = AppLocalizations.of(context)!; - final pillars = <(IconData, String, String)>[ - (Icons.hub_outlined, l.welcomePillarHubTitle, l.welcomePillarHubBody), + final pillars = <(IconData, String, String, String)>[ + ( + Icons.hub_outlined, + l.welcomePillarHubTitle, + l.welcomePillarHubBody, + l.welcomePillarHubTech, + ), ( Icons.extension_outlined, l.welcomePillarModuleTitle, l.welcomePillarModuleBody, + l.welcomePillarModuleTech, ), ( Icons.account_tree_outlined, l.welcomePillarFlowTitle, l.welcomePillarFlowBody, + l.welcomePillarFlowTech, ), ]; return LayoutBuilder( @@ -333,7 +340,7 @@ class _PillarRow extends StatelessWidget { return Column( children: [ for (final p in pillars) ...[ - _Pillar(icon: p.$1, title: p.$2, body: p.$3), + _Pillar(icon: p.$1, title: p.$2, body: p.$3, tech: p.$4), const SizedBox(height: ChainSpace.md), ], ], @@ -356,6 +363,7 @@ class _PillarRow extends StatelessWidget { icon: pillars[i].$1, title: pillars[i].$2, body: pillars[i].$3, + tech: pillars[i].$4, ), ), ], @@ -367,16 +375,34 @@ class _PillarRow extends StatelessWidget { } } -class _Pillar extends StatelessWidget { +/// Concept card in everyday language; the technical wording +/// (Rust, WASM, YAML, hash chain) lives behind a per-card +/// "for the technically curious" toggle so the first read +/// never requires developer vocabulary. +class _Pillar extends StatefulWidget { final IconData icon; final String title; final String body; + final String tech; - const _Pillar({required this.icon, required this.title, required this.body}); + const _Pillar({ + required this.icon, + required this.title, + required this.body, + required this.tech, + }); + + @override + State<_Pillar> createState() => _PillarState(); +} + +class _PillarState extends State<_Pillar> { + bool _techOpen = false; @override Widget build(BuildContext context) { final theme = Theme.of(context); + final l = AppLocalizations.of(context)!; return Container( padding: const EdgeInsets.all(ChainSpace.lg), decoration: BoxDecoration( @@ -387,22 +413,57 @@ class _Pillar extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(icon, size: 28, color: theme.colorScheme.primary), + Icon(widget.icon, size: 28, color: theme.colorScheme.primary), const SizedBox(height: ChainSpace.md), Text( - title, + widget.title, style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(height: ChainSpace.sm), Text( - body, + widget.body, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, height: 1.45, ), ), + const SizedBox(height: ChainSpace.sm), + InkWell( + onTap: () => setState(() => _techOpen = !_techOpen), + borderRadius: BorderRadius.circular(ChainRadius.sm), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _techOpen ? Icons.expand_less : Icons.expand_more, + size: 16, + color: theme.colorScheme.primary, + ), + const SizedBox(width: 4), + Text( + l.welcomePillarTechToggle, + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.primary, + ), + ), + ], + ), + ), + ), + if (_techOpen) ...[ + const SizedBox(height: ChainSpace.xs), + Text( + widget.tech, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + height: 1.45, + ), + ), + ], ], ), );