fix(ui): make T1–T4 readable + lift Recl∆Im into the nav rail

Three small frictions a first-time reader hit at the same time,
fixed in one pass:

  1. The TierBadge said 'T1' without context. Default form is
     now 'T1 · amtlich' / 'T2 · Verband' / 'T3 · Erhebung' /
     'T4 · Signal'. Compact form (heatmap chip wrap) keeps the
     bare code. Tooltip spells out the long form + cites the
     Regel der niedrigsten Stufe (Studie §6.7) so the meaning
     of the cap is at hand on hover.

  2. The Legende's tier row was the abstract one of the four —
     it now inlines all four stage names (amtlich / Verband /
     Erhebung / Signal) and explains why a single T4 component
     drags a whole figure to T4. No more 'what is Tier?' on
     screen.

  3. The DemoBanner repeated 'Tier T4' without explaining what
     T4 is. Now: 'qualitatives Signal — bis NKR/Verbands-
     Pipeline + Juristen-Approval laufen'. The cause-and-cure
     is visible at a glance.

  4. The NavigationRail leading column showed only 'F∆I' as
     brand mark. Now shows 'Recl∆Im' prominently (petrol-signal,
     bold) with 'F∆I' as a small muted vendor tag below.
     Product first, vendor second.

EvidenceTierLabel extension grows a new 'headline' getter for
the one-word default ('amtlich'/'Verband'/…), and 'description'
gets longer, source-naming text (NKR / DESTATIS / DIHK / IHK /
ZDH / BDI). Code paths that used the old short-only badge keep
working because 'short' still returns 'T1'.

flutter pub get refreshed against chain_client_sdk 0.18, which
the fai_chain agent confirmed already carries the gRPC-Web
channel — no SDK bump needed for the web-hub-mode wire.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-18 14:44:14 +02:00
parent f61865a3c2
commit 1ad523a91a
5 changed files with 54 additions and 17 deletions

View file

@ -22,6 +22,7 @@ enum EvidenceTier {
} }
extension EvidenceTierLabel on EvidenceTier { extension EvidenceTierLabel on EvidenceTier {
/// Kompakt-Code für enge Layouts.
String get short => switch (this) { String get short => switch (this) {
EvidenceTier.t1 => 'T1', EvidenceTier.t1 => 'T1',
EvidenceTier.t2 => 'T2', EvidenceTier.t2 => 'T2',
@ -29,11 +30,24 @@ extension EvidenceTierLabel on EvidenceTier {
EvidenceTier.t4 => 'T4', EvidenceTier.t4 => 'T4',
}; };
String get description => switch (this) { /// Ein-Wort-Bedeutung, immer neben dem T-Code zu zeigen.
EvidenceTier.t1 => 'amtlich / peer-reviewed', String get headline => switch (this) {
EvidenceTier.t1 => 'amtlich',
EvidenceTier.t2 => 'Verband', EvidenceTier.t2 => 'Verband',
EvidenceTier.t3 => 'eigene Erhebung', EvidenceTier.t3 => 'Erhebung',
EvidenceTier.t4 => 'qualitatives Signal', EvidenceTier.t4 => 'Signal',
};
/// Lange Form für Listen, Tooltips, Legend-Texte.
String get description => switch (this) {
EvidenceTier.t1 =>
'amtlich oder peer-reviewed (NKR, DESTATIS, ifo, ZEW)',
EvidenceTier.t2 =>
'Verbands-Erhebung (DIHK, IHK, ZDH, BDI)',
EvidenceTier.t3 =>
'eigene strukturierte Erhebung (Phase 2/3)',
EvidenceTier.t4 =>
'qualitatives Signal — nur Hinweis, keine Magnitude',
}; };
} }

View file

@ -78,7 +78,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
), ),
), ),
const SizedBox(height: ReclaimSpace.md), const SizedBox(height: ReclaimSpace.md),
_LegendCard(), const _LegendCard(),
const SizedBox(height: ReclaimSpace.lg), const SizedBox(height: ReclaimSpace.lg),
Wrap( Wrap(
spacing: ReclaimSpace.md, spacing: ReclaimSpace.md,
@ -135,6 +135,8 @@ class _HeatmapPageState extends State<HeatmapPage> {
/// reading instructions a reviewer needs to defend the plot /// reading instructions a reviewer needs to defend the plot
/// against "what does that even mean?" /// against "what does that even mean?"
class _LegendCard extends StatelessWidget { class _LegendCard extends StatelessWidget {
const _LegendCard();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final t = Theme.of(context).textTheme; final t = Theme.of(context).textTheme;
@ -180,13 +182,18 @@ class _LegendCard extends StatelessWidget {
const SizedBox(height: ReclaimSpace.sm), const SizedBox(height: ReclaimSpace.sm),
_LegendRow( _LegendRow(
symbol: '', symbol: '',
axis: 'Tier-Farbe', axis: 'Farb-Kante',
label: 'Evidenz-Stufe', label: 'Evidenz-Stufe',
description: description:
'Petrol → warm-orange markiert die Stufe (T1T4) der ' 'Jeder Wert trägt eine von vier Stufen — '
'schwächsten Eingangs-Variable. Eine SKM-Zahl mit ' 'T1 amtlich (NKR, DESTATIS, ifo, ZEW), '
'T4-Schätzwert für h trägt die ganze Zahl als T4 ' 'T2 Verband (DIHK, IHK, ZDH, BDI), '
'(Regel der niedrigsten Stufe, Studie §6.7).', 'T3 eigene Erhebung, '
'T4 qualitatives Signal. '
'Eine Zahl bekommt die Stufe ihrer schwächsten '
'Eingangs-Variable — wenn h nur geschätzt ist (T4), '
'ist die ganze SKM-Zahl T4. Petrol → warm = stark → '
'schwach.',
), ),
], ],
), ),

View file

@ -52,10 +52,18 @@ class _ShellPageState extends State<ShellPage> {
const DeltaMark(size: 36), const DeltaMark(size: 36),
const SizedBox(height: ReclaimSpace.sm), const SizedBox(height: ReclaimSpace.sm),
Text( Text(
'F∆I', 'Recl∆Im',
style: t.labelLarge?.copyWith( style: t.labelLarge?.copyWith(
letterSpacing: 2, letterSpacing: 1.5,
color: ReclaimColors.signal, color: ReclaimColors.signal,
fontWeight: FontWeight.w600,
),
),
Text(
'F∆I',
style: t.labelSmall?.copyWith(
letterSpacing: 2,
color: ReclaimColors.mute,
), ),
), ),
], ],

View file

@ -32,9 +32,11 @@ class DemoBanner extends StatelessWidget {
const SizedBox(width: ReclaimSpace.sm), const SizedBox(width: ReclaimSpace.sm),
Expanded( Expanded(
child: Text( child: Text(
'Demo-Daten · KEINE Rechtsberatung · Tier T4. ' 'Demo-Daten · KEINE Rechtsberatung. '
'Phase 0, ohne Verbands-Erhebung, ohne Juristen- oder ' 'Alle Zahlen sind als T4 markiert (qualitatives Signal), '
'Beirats-Validierung. Volle Hinweise unter „Recht".', 'bis NKR/Verbands-Pipeline + Juristen-Approval laufen. '
'Erläuterung der Stufen unter „Methodik", '
'Disclaimer unter „Recht".',
style: t.labelSmall?.copyWith( style: t.labelSmall?.copyWith(
color: ReclaimColors.tierT4, color: ReclaimColors.tierT4,
), ),

View file

@ -20,8 +20,14 @@ class TierBadge extends StatelessWidget {
EvidenceTier.t3 => ReclaimColors.tierT3, EvidenceTier.t3 => ReclaimColors.tierT3,
EvidenceTier.t4 => ReclaimColors.tierT4, EvidenceTier.t4 => ReclaimColors.tierT4,
}; };
final label = compact
? tier.short
: '${tier.short} · ${tier.headline}';
return Tooltip( return Tooltip(
message: '${tier.short}${tier.description}', message: 'Evidenz-Stufe ${tier.short}${tier.description}.\n'
'Regel der niedrigsten Stufe (Studie §6.7): die Stufe '
'der schwächsten Eingangs-Variable bestimmt die Stufe '
'des Gesamt-Werts.',
child: Container( child: Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: compact ? 6 : ReclaimSpace.sm, horizontal: compact ? 6 : ReclaimSpace.sm,
@ -45,7 +51,7 @@ class TierBadge extends StatelessWidget {
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
tier.short, label,
style: (compact ? t.labelSmall : t.labelLarge)?.copyWith( style: (compact ? t.labelSmall : t.labelLarge)?.copyWith(
color: color, color: color,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,