feat(ui): ModeBanner replaces DemoBanner + no silent mock-fallback

Two related fixes so the app never silently presents itself as
a demo when it is in fact running against a real hub:

  - The orange 'Demo-Daten · KEINE Rechtsberatung' banner was
    hard-coded into every data screen, regardless of whether
    the active repository was the in-memory MockRepository or
    the live HubRepository. Replaced with ModeBanner, which
    picks one of three voices by repository.mode:

      demo      orange T4 — 'Demo-Daten · KEINE Rechtsberatung'
      hub-live  petrol     — 'Live aus Hub · <endpoint>'
      hub-down  warm-warn  — 'Hub nicht erreichbar — leere Liste'

  - main.dart's _pickRepository used to fall back to
    MockRepository whenever HubRepository.connect() came back
    unhealthy. That silently downgraded a live deployment to
    demo data, which is exactly what the user is trying to avoid.
    Removed: when useHub=true, the HubRepository is returned
    regardless. Its list() returns [] on hub-down, and the
    'hub-down' banner explains why the screens are empty.
    Mock data only shows when the user explicitly turns useHub
    off in the Hub-Reiter.

Also: legend arrow icons now point in the actual axis direction
(→ for Schaden, ↑ for Nutzen) instead of the bidirectional ↔/↕,
which suggested the axes ran both ways. Mini-chips and the
expanded legend rows are consistent.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-18 15:57:50 +02:00
parent e6e9c0753d
commit 51beb332c4
6 changed files with 138 additions and 67 deletions

View file

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import '../data/models.dart';
import '../data/repository.dart';
import '../theme/reclaim_tokens.dart';
import '../widgets/demo_banner.dart';
import '../widgets/mode_banner.dart';
import '../widgets/heatmap_grid.dart';
import '../widgets/reclaim_card.dart';
import '../widgets/tier_badge.dart';
@ -32,7 +32,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
final t = Theme.of(context).textTheme;
return Column(
children: [
const DemoBanner(),
ModeBanner(repository: widget.repository),
Expanded(
child: Padding(
padding: const EdgeInsets.all(ReclaimSpace.xl),
@ -190,8 +190,8 @@ class _LegendCardState extends State<_LegendCard> {
spacing: ReclaimSpace.lg,
runSpacing: ReclaimSpace.xs,
children: [
_MiniLabel(symbol: '', text: 'Schaden'),
_MiniLabel(symbol: '', text: 'Nutzen'),
_MiniLabel(symbol: '', text: 'Schaden'),
_MiniLabel(symbol: '', text: 'Nutzen'),
_MiniLabel(
symbol: '', text: 'Betroffenheit'),
_MiniLabel(
@ -219,7 +219,7 @@ class _LegendCardState extends State<_LegendCard> {
CrossAxisAlignment.start,
children: [
_LegendRow(
symbol: '',
symbol: '',
axis: 'Abszisse (X)',
label: 'Schaden',
description:
@ -231,7 +231,7 @@ class _LegendCardState extends State<_LegendCard> {
),
SizedBox(height: ReclaimSpace.sm),
_LegendRow(
symbol: '',
symbol: '',
axis: 'Ordinate (Y)',
label: 'Nutzen',
description:

View file

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import '../data/models.dart';
import '../data/repository.dart';
import '../theme/reclaim_tokens.dart';
import '../widgets/demo_banner.dart';
import '../widgets/mode_banner.dart';
import '../widgets/evidence_sidebar.dart';
import '../widgets/norm_text_card.dart';
import '../widgets/reclaim_card.dart';
@ -29,7 +29,7 @@ class NormDetailPage extends StatelessWidget {
),
body: Column(
children: [
const DemoBanner(),
ModeBanner(repository: repository),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(ReclaimSpace.xl),

View file

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import '../data/models.dart';
import '../data/repository.dart';
import '../theme/reclaim_tokens.dart';
import '../widgets/demo_banner.dart';
import '../widgets/mode_banner.dart';
import '../widgets/reclaim_card.dart';
import '../widgets/tier_badge.dart';
import 'norm_detail_page.dart';
@ -18,7 +18,7 @@ class NormsListPage extends StatelessWidget {
final t = Theme.of(context).textTheme;
return Column(
children: [
const DemoBanner(),
ModeBanner(repository: repository),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(ReclaimSpace.xl),