fix(studio): Welcome layout exception, drop awkward header, Store at slot 2 (v0.39.1)

Three fixes against the v0.39.0 user feedback.

- Layout exception "BoxConstraints forces an infinite
  height". `_PillarRow`'s wide-window path used
  `Row(crossAxisAlignment: CrossAxisAlignment.stretch)`
  inside a `Column` inside a `SingleChildScrollView` — Row
  inherits unbounded height, can't stretch to anything,
  Flutter throws. Wrapped the Row in `IntrinsicHeight` so
  the Row's height is bounded to the tallest child first.
  Same shape `_DocsRow`'s narrow-window path was triggering
  with `Wrap` + `SizedBox(width: double.infinity)` — replaced
  with a plain `Column` for that path; the wide path keeps
  the Wrap with the proper finite cardWidth.

- Dropped the "WIE ES ZUSAMMENPASST" / "HOW IT FITS
  TOGETHER" section header. Operator-feedback was that the
  label sounded like a question without an answer. The hero
  already establishes the page; the three Hub/Module/Flow
  cards are self-explaining beneath it. Header was visual
  clutter and the underlying ARB keys are gone.

- Reordered the sidebar: Welcome, **Store**, Doctor, Flows,
  Audit, Approvals. Store is the operator's daily-use
  surface and belongs above Doctor (which is a diagnostics
  page). Welcome stays slot 0 as the first-launch landing.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-09 00:42:39 +02:00
parent 930e246649
commit eb447c6ec0
8 changed files with 47 additions and 40 deletions

View file

@ -6,7 +6,6 @@
"navWelcome": "Willkommen", "navWelcome": "Willkommen",
"welcomeHeroTitle": "F∆I Platform", "welcomeHeroTitle": "F∆I Platform",
"welcomeHeroSubtitle": "Deterministische Workflow-Engine für KI-gestützte Dokumentenverarbeitung in regulierten Umgebungen.", "welcomeHeroSubtitle": "Deterministische Workflow-Engine für KI-gestützte Dokumentenverarbeitung in regulierten Umgebungen.",
"welcomePillarsHeader": "WIE ES ZUSAMMENPASST",
"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": "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.",
"welcomePillarModuleTitle": "Modul", "welcomePillarModuleTitle": "Modul",

View file

@ -7,7 +7,6 @@
"navWelcome": "Welcome", "navWelcome": "Welcome",
"welcomeHeroTitle": "F∆I Platform", "welcomeHeroTitle": "F∆I Platform",
"welcomeHeroSubtitle": "Deterministic workflow engine for AI-assisted document processing in regulated environments.", "welcomeHeroSubtitle": "Deterministic workflow engine for AI-assisted document processing in regulated environments.",
"welcomePillarsHeader": "HOW IT FITS TOGETHER",
"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": "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",

View file

@ -122,12 +122,6 @@ abstract class AppLocalizations {
/// **'Deterministic workflow engine for AI-assisted document processing in regulated environments.'** /// **'Deterministic workflow engine for AI-assisted document processing in regulated environments.'**
String get welcomeHeroSubtitle; String get welcomeHeroSubtitle;
/// No description provided for @welcomePillarsHeader.
///
/// In en, this message translates to:
/// **'HOW IT FITS TOGETHER'**
String get welcomePillarsHeader;
/// No description provided for @welcomePillarHubTitle. /// No description provided for @welcomePillarHubTitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -21,9 +21,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get welcomeHeroSubtitle => String get welcomeHeroSubtitle =>
'Deterministische Workflow-Engine für KI-gestützte Dokumentenverarbeitung in regulierten Umgebungen.'; 'Deterministische Workflow-Engine für KI-gestützte Dokumentenverarbeitung in regulierten Umgebungen.';
@override
String get welcomePillarsHeader => 'WIE ES ZUSAMMENPASST';
@override @override
String get welcomePillarHubTitle => 'Hub'; String get welcomePillarHubTitle => 'Hub';

View file

@ -21,9 +21,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get welcomeHeroSubtitle => String get welcomeHeroSubtitle =>
'Deterministic workflow engine for AI-assisted document processing in regulated environments.'; 'Deterministic workflow engine for AI-assisted document processing in regulated environments.';
@override
String get welcomePillarsHeader => 'HOW IT FITS TOGETHER';
@override @override
String get welcomePillarHubTitle => 'Hub'; String get welcomePillarHubTitle => 'Hub';

View file

@ -26,7 +26,7 @@ import 'widgets/widgets.dart';
/// Studio's own build version. Bump on every UI commit so the /// Studio's own build version. Bump on every UI commit so the
/// running app self-identifies visible in the sidebar header /// running app self-identifies visible in the sidebar header
/// and quick-glance proof that you're seeing the current build. /// and quick-glance proof that you're seeing the current build.
const String kStudioVersion = '0.39.0'; const String kStudioVersion = '0.39.1';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -146,6 +146,15 @@ class _StudioShellState extends State<StudioShell> {
selectedIcon: Icons.waving_hand, selectedIcon: Icons.waving_hand,
page: WelcomePage(), page: WelcomePage(),
), ),
// Store moved to slot 2 in v0.39.1 it's the
// operator's daily-use surface and belongs above Doctor,
// which is a diagnostics page.
_NavPage(
id: 'store',
icon: Icons.storefront_outlined,
selectedIcon: Icons.storefront,
page: StorePage(),
),
_NavPage( _NavPage(
id: 'doctor', id: 'doctor',
icon: Icons.health_and_safety_outlined, icon: Icons.health_and_safety_outlined,
@ -157,12 +166,6 @@ class _StudioShellState extends State<StudioShell> {
// the Store's detail sheet, and the Store with the // the Store's detail sheet, and the Store with the
// "Installed" filter covers the listing role. Cmd+K still // "Installed" filter covers the listing role. Cmd+K still
// opens FaiModuleSheet for quick info. // opens FaiModuleSheet for quick info.
_NavPage(
id: 'store',
icon: Icons.storefront_outlined,
selectedIcon: Icons.storefront,
page: StorePage(),
),
_NavPage( _NavPage(
id: 'flows', id: 'flows',
icon: Icons.account_tree_outlined, icon: Icons.account_tree_outlined,

View file

@ -52,8 +52,6 @@ class WelcomePage extends StatelessWidget {
// path beats educational content. Auto-hides // path beats educational content. Auto-hides
// once the operator dismisses it. // once the operator dismisses it.
_OnboardingChecklist(), _OnboardingChecklist(),
_SectionLabel(textKey: _SectionLabelKey.pillars),
SizedBox(height: FaiSpace.md),
_PillarRow(), _PillarRow(),
SizedBox(height: FaiSpace.xxl), SizedBox(height: FaiSpace.xxl),
_SectionLabel(textKey: _SectionLabelKey.trust), _SectionLabel(textKey: _SectionLabelKey.trust),
@ -143,7 +141,7 @@ class _Hero extends StatelessWidget {
} }
} }
enum _SectionLabelKey { pillars, trust, docs } enum _SectionLabelKey { trust, docs }
class _SectionLabel extends StatelessWidget { class _SectionLabel extends StatelessWidget {
final _SectionLabelKey textKey; final _SectionLabelKey textKey;
@ -154,7 +152,6 @@ class _SectionLabel extends StatelessWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
final l = AppLocalizations.of(context)!; final l = AppLocalizations.of(context)!;
final text = switch (textKey) { final text = switch (textKey) {
_SectionLabelKey.pillars => l.welcomePillarsHeader,
_SectionLabelKey.trust => l.welcomeTrustHeader, _SectionLabelKey.trust => l.welcomeTrustHeader,
_SectionLabelKey.docs => l.welcomeDocsHeader, _SectionLabelKey.docs => l.welcomeDocsHeader,
}; };
@ -210,20 +207,28 @@ class _PillarRow extends StatelessWidget {
], ],
); );
} }
return Row( // IntrinsicHeight bounds the Row's height so
crossAxisAlignment: CrossAxisAlignment.stretch, // `crossAxisAlignment: stretch` has something to
children: [ // stretch against. Without it the Row inherits the
for (var i = 0; i < pillars.length; i++) ...[ // unbounded height of the surrounding
if (i > 0) const SizedBox(width: FaiSpace.md), // `SingleChildScrollView` and Flutter throws
Expanded( // "BoxConstraints forces an infinite height".
child: _Pillar( return IntrinsicHeight(
icon: pillars[i].$1, child: Row(
title: pillars[i].$2, crossAxisAlignment: CrossAxisAlignment.stretch,
body: pillars[i].$3, children: [
for (var i = 0; i < pillars.length; i++) ...[
if (i > 0) const SizedBox(width: FaiSpace.md),
Expanded(
child: _Pillar(
icon: pillars[i].$1,
title: pillars[i].$2,
body: pillars[i].$3,
),
), ),
), ],
], ],
], ),
); );
}, },
); );
@ -683,8 +688,21 @@ class _DocsRow extends StatelessWidget {
LayoutBuilder( LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final twoCols = constraints.maxWidth >= 640; final twoCols = constraints.maxWidth >= 640;
final cardWidth = // Narrow windows stack to a single column. Putting
twoCols ? (constraints.maxWidth - FaiSpace.md) / 2 : double.infinity; // `width: double.infinity` SizedBoxes into a Wrap
// breaks Wrap's intrinsic-width math; a Column with
// unconstrained child widths is the correct fit.
if (!twoCols) {
return Column(
children: [
for (var i = 0; i < _kDocs.length; i++) ...[
if (i > 0) const SizedBox(height: FaiSpace.md),
_DocCard(entry: _kDocs[i]),
],
],
);
}
final cardWidth = (constraints.maxWidth - FaiSpace.md) / 2;
return Wrap( return Wrap(
spacing: FaiSpace.md, spacing: FaiSpace.md,
runSpacing: FaiSpace.md, runSpacing: FaiSpace.md,

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.39.0 version: 0.39.1
environment: environment:
sdk: ^3.11.0-200.1.beta sdk: ^3.11.0-200.1.beta