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

View file

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

View file

@ -122,12 +122,6 @@ abstract class AppLocalizations {
/// **'Deterministic workflow engine for AI-assisted document processing in regulated environments.'**
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.
///
/// In en, this message translates to:

View file

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

View file

@ -21,9 +21,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get welcomeHeroSubtitle =>
'Deterministic workflow engine for AI-assisted document processing in regulated environments.';
@override
String get welcomePillarsHeader => 'HOW IT FITS TOGETHER';
@override
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
/// running app self-identifies visible in the sidebar header
/// 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 {
WidgetsFlutterBinding.ensureInitialized();
@ -146,6 +146,15 @@ class _StudioShellState extends State<StudioShell> {
selectedIcon: Icons.waving_hand,
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(
id: 'doctor',
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
// "Installed" filter covers the listing role. Cmd+K still
// opens FaiModuleSheet for quick info.
_NavPage(
id: 'store',
icon: Icons.storefront_outlined,
selectedIcon: Icons.storefront,
page: StorePage(),
),
_NavPage(
id: 'flows',
icon: Icons.account_tree_outlined,

View file

@ -52,8 +52,6 @@ class WelcomePage extends StatelessWidget {
// path beats educational content. Auto-hides
// once the operator dismisses it.
_OnboardingChecklist(),
_SectionLabel(textKey: _SectionLabelKey.pillars),
SizedBox(height: FaiSpace.md),
_PillarRow(),
SizedBox(height: FaiSpace.xxl),
_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 {
final _SectionLabelKey textKey;
@ -154,7 +152,6 @@ class _SectionLabel extends StatelessWidget {
final theme = Theme.of(context);
final l = AppLocalizations.of(context)!;
final text = switch (textKey) {
_SectionLabelKey.pillars => l.welcomePillarsHeader,
_SectionLabelKey.trust => l.welcomeTrustHeader,
_SectionLabelKey.docs => l.welcomeDocsHeader,
};
@ -210,7 +207,14 @@ class _PillarRow extends StatelessWidget {
],
);
}
return Row(
// IntrinsicHeight bounds the Row's height so
// `crossAxisAlignment: stretch` has something to
// stretch against. Without it the Row inherits the
// unbounded height of the surrounding
// `SingleChildScrollView` and Flutter throws
// "BoxConstraints forces an infinite height".
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (var i = 0; i < pillars.length; i++) ...[
@ -224,6 +228,7 @@ class _PillarRow extends StatelessWidget {
),
],
],
),
);
},
);
@ -683,8 +688,21 @@ class _DocsRow extends StatelessWidget {
LayoutBuilder(
builder: (context, constraints) {
final twoCols = constraints.maxWidth >= 640;
final cardWidth =
twoCols ? (constraints.maxWidth - FaiSpace.md) / 2 : double.infinity;
// Narrow windows stack to a single column. Putting
// `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(
spacing: FaiSpace.md,
runSpacing: FaiSpace.md,

View file

@ -1,7 +1,7 @@
name: fai_studio
description: "F∆I Studio — desktop GUI for the F∆I hub"
publish_to: 'none'
version: 0.39.0
version: 0.39.1
environment:
sdk: ^3.11.0-200.1.beta