feat: wire chain_client_sdk gRPC client + HubRepository scaffold

Adds a real Ch∆In hub backend to the Flutter client as a
sibling-path dependency on chain_client_sdk_dart, alongside the
existing MockRepository (which stays the default until the
flow.completed-to-Evaluation pipeline lands in Phase 0 week 1).

Wiring

  HubSettings (lib/data/hub_endpoint.dart) — persists host /
  port / secure / token / useHub flags via SharedPreferences
  under the Studio-compatible hub.* keys. Distinct from the
  SDK's chain.HubEndpoint, which is a transport-level value
  type — HubSettings adapts in that direction.

  HubRepository (lib/data/hub_repository.dart) — implements
  EvaluationRepository on top of HubClient. connect() runs a
  4-second healthy() probe; on failure returns a repository
  with isHealthy=false rather than throwing, so the UI can
  surface "hub-down" without an exception-handling round-trip.
  list() / byEli() return empty data in Phase 0 because no
  hub-side flow yet produces Evaluation objects in the
  lawheatmap-specific shape.

  main.dart — loads HubSettings on startup; if useHub=false
  starts with MockRepository (fast offline default), otherwise
  probes the hub and falls back to MockRepository on probe
  failure. No mid-session swap to keep the Phase 0 wiring
  simple; a settings save instructs the user to restart.

  HubStatusPage — read-only summary card at the top, editable
  endpoint form, "Verbindung testen" probe button, "Speichern"
  persist button. Live-flow runbook below describes the
  chain serve / install / run / approve sequence so a user
  can wire the real flow themselves once Phase 1 lands.

flutter analyze: clean. flutter test: 2/2 passing.
Smoke-launch on macOS: app boots, Dart VM service comes up,
no runtime errors in mock mode.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-18 11:45:48 +02:00
parent fa8acd41b8
commit 39733628b6
6 changed files with 509 additions and 31 deletions

View file

@ -1,23 +1,45 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'data/hub_endpoint.dart';
import 'data/hub_repository.dart';
import 'data/repository.dart';
import 'pages/landing_page.dart';
import 'theme/lawheatmap_theme.dart';
void main() {
runApp(const LawHeatmapApp());
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final settings = await HubSettings.load();
final repository = await _pickRepository(settings);
runApp(LawHeatmapApp(repository: repository, settings: settings));
}
/// Decide which repository to start with.
///
/// useHub=false Mock, no probe (fast offline start)
/// useHub=true, healthy HubRepository
/// useHub=true, NOT ok MockRepository (graceful fallback;
/// UI banner shows hub-down so the
/// user can re-try from settings)
Future<EvaluationRepository> _pickRepository(HubSettings settings) async {
if (!settings.useHub) return const MockRepository();
final hub = await HubRepository.connect(settings);
if (hub.isHealthy) return hub;
return const MockRepository();
}
class LawHeatmapApp extends StatelessWidget {
const LawHeatmapApp({super.key});
const LawHeatmapApp({
super.key,
this.repository = const MockRepository(),
this.settings,
});
final EvaluationRepository repository;
final HubSettings? settings;
@override
Widget build(BuildContext context) {
// Phase 0: in-memory fixtures only. Swap to a HubRepository
// (gRPC against `chain serve`) without changing the UI.
const EvaluationRepository repository = MockRepository();
return MaterialApp(
title: 'F∆I Law-Heatmap',
debugShowCheckedModeBanner: false,
@ -30,7 +52,7 @@ class LawHeatmapApp extends StatelessWidget {
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('de'), Locale('en')],
home: const LandingPage(repository: repository),
home: LandingPage(repository: repository),
);
}
}