fix(welcome): concept cards speak everyday language first
Some checks are pending
Security / Security check (push) Waiting to run

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 <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-07-17 23:24:46 +02:00
parent cc1618f512
commit 3e1ce6e1fd
6 changed files with 163 additions and 28 deletions

View file

@ -22,11 +22,15 @@
"welcomeHeroTitle": "Ch∆In Platform", "welcomeHeroTitle": "Ch∆In Platform",
"welcomeHeroSubtitle": "Deterministische Workflow-Engine für KI-gestützte Datenverarbeitung in regulierten Umgebungen.", "welcomeHeroSubtitle": "Deterministische Workflow-Engine für KI-gestützte Datenverarbeitung in regulierten Umgebungen.",
"welcomePillarHubTitle": "Hub", "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", "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", "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", "welcomeTrustHeader": "TRUST-POSTURE",
"welcomeTrustSandboxTitle": "Sandbox von Anfang an", "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.", "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.",

View file

@ -30,11 +30,15 @@
"welcomeHeroTitle": "Ch∆In Platform", "welcomeHeroTitle": "Ch∆In Platform",
"welcomeHeroSubtitle": "Deterministic workflow engine for AI-assisted data processing in regulated environments.", "welcomeHeroSubtitle": "Deterministic workflow engine for AI-assisted data processing in regulated environments.",
"welcomePillarHubTitle": "Hub", "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", "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", "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", "welcomeTrustHeader": "TRUST POSTURE",
"welcomeTrustSandboxTitle": "Sandbox by default", "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.", "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.",

View file

@ -233,9 +233,15 @@ abstract class AppLocalizations {
/// No description provided for @welcomePillarHubBody. /// No description provided for @welcomePillarHubBody.
/// ///
/// In en, this message translates to: /// 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; 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. /// No description provided for @welcomePillarModuleTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -245,9 +251,15 @@ abstract class AppLocalizations {
/// No description provided for @welcomePillarModuleBody. /// No description provided for @welcomePillarModuleBody.
/// ///
/// In en, this message translates to: /// 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; 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. /// No description provided for @welcomePillarFlowTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -257,9 +269,21 @@ abstract class AppLocalizations {
/// No description provided for @welcomePillarFlowBody. /// No description provided for @welcomePillarFlowBody.
/// ///
/// In en, this message translates to: /// 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; 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. /// No description provided for @welcomeTrustHeader.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -5056,15 +5080,21 @@ abstract class AppLocalizations {
/// No description provided for @runsEmptyTitle. /// No description provided for @runsEmptyTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'No detached runs'** /// **'No background runs'**
String get runsEmptyTitle; String get runsEmptyTitle;
/// No description provided for @runsEmptyHint. /// No description provided for @runsEmptyHint.
/// ///
/// In en, this message translates to: /// 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; 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. /// No description provided for @runsCancelButton.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -83,21 +83,36 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get welcomePillarHubBody => 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 @override
String get welcomePillarModuleTitle => 'Modul'; String get welcomePillarModuleTitle => 'Modul';
@override @override
String get welcomePillarModuleBody => 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 @override
String get welcomePillarFlowTitle => 'Flow'; String get welcomePillarFlowTitle => 'Flow';
@override @override
String get welcomePillarFlowBody => 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 @override
String get welcomeTrustHeader => 'TRUST-POSTURE'; String get welcomeTrustHeader => 'TRUST-POSTURE';
@ -2980,11 +2995,14 @@ class AppLocalizationsDe extends AppLocalizations {
String get runsReloadTooltip => 'Lauf-Liste neu laden'; String get runsReloadTooltip => 'Lauf-Liste neu laden';
@override @override
String get runsEmptyTitle => 'Keine abgekoppelten Läufe'; String get runsEmptyTitle => 'Keine Läufe im Hintergrund';
@override @override
String get runsEmptyHint => 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 @override
String get runsCancelButton => 'Abbrechen'; String get runsCancelButton => 'Abbrechen';

View file

@ -84,6 +84,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get welcomePillarHubBody => 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.'; 'One Rust binary that loads modules and runs flows. SQLite for state. No Docker, no message broker, no background services to babysit.';
@override @override
@ -91,14 +95,25 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get welcomePillarModuleBody => 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 @override
String get welcomePillarFlowTitle => 'Flow'; String get welcomePillarFlowTitle => 'Flow';
@override @override
String get welcomePillarFlowBody => 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 @override
String get welcomeTrustHeader => 'TRUST POSTURE'; String get welcomeTrustHeader => 'TRUST POSTURE';
@ -2981,11 +2996,14 @@ class AppLocalizationsEn extends AppLocalizations {
String get runsReloadTooltip => 'Reload the runs list'; String get runsReloadTooltip => 'Reload the runs list';
@override @override
String get runsEmptyTitle => 'No detached runs'; String get runsEmptyTitle => 'No background runs';
@override @override
String get runsEmptyHint => 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 @override
String get runsCancelButton => 'Cancel'; String get runsCancelButton => 'Cancel';

View file

@ -310,17 +310,24 @@ class _PillarRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = AppLocalizations.of(context)!; final l = AppLocalizations.of(context)!;
final pillars = <(IconData, String, String)>[ final pillars = <(IconData, String, String, String)>[
(Icons.hub_outlined, l.welcomePillarHubTitle, l.welcomePillarHubBody), (
Icons.hub_outlined,
l.welcomePillarHubTitle,
l.welcomePillarHubBody,
l.welcomePillarHubTech,
),
( (
Icons.extension_outlined, Icons.extension_outlined,
l.welcomePillarModuleTitle, l.welcomePillarModuleTitle,
l.welcomePillarModuleBody, l.welcomePillarModuleBody,
l.welcomePillarModuleTech,
), ),
( (
Icons.account_tree_outlined, Icons.account_tree_outlined,
l.welcomePillarFlowTitle, l.welcomePillarFlowTitle,
l.welcomePillarFlowBody, l.welcomePillarFlowBody,
l.welcomePillarFlowTech,
), ),
]; ];
return LayoutBuilder( return LayoutBuilder(
@ -333,7 +340,7 @@ class _PillarRow extends StatelessWidget {
return Column( return Column(
children: [ children: [
for (final p in pillars) ...[ 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), const SizedBox(height: ChainSpace.md),
], ],
], ],
@ -356,6 +363,7 @@ class _PillarRow extends StatelessWidget {
icon: pillars[i].$1, icon: pillars[i].$1,
title: pillars[i].$2, title: pillars[i].$2,
body: pillars[i].$3, 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 IconData icon;
final String title; final String title;
final String body; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final l = AppLocalizations.of(context)!;
return Container( return Container(
padding: const EdgeInsets.all(ChainSpace.lg), padding: const EdgeInsets.all(ChainSpace.lg),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -387,22 +413,57 @@ class _Pillar extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon(icon, size: 28, color: theme.colorScheme.primary), Icon(widget.icon, size: 28, color: theme.colorScheme.primary),
const SizedBox(height: ChainSpace.md), const SizedBox(height: ChainSpace.md),
Text( Text(
title, widget.title,
style: theme.textTheme.titleLarge?.copyWith( style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
), ),
const SizedBox(height: ChainSpace.sm), const SizedBox(height: ChainSpace.sm),
Text( Text(
body, widget.body,
style: theme.textTheme.bodyMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant, color: theme.colorScheme.onSurfaceVariant,
height: 1.45, 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,
),
),
],
], ],
), ),
); );