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",
"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.",

View file

@ -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.",

View file

@ -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:

View file

@ -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';

View file

@ -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';

View file

@ -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,
),
),
],
],
),
);