feat: live gRPC pages — drop mock data, talk to the real hub

Studio's three MVP pages now pull live data from a running hub
via fai_dart_sdk. Mock fixture removed.

- data/hub.dart: HubService singleton wraps fai_dart_sdk's
  HubClient and exposes UI-friendly types (ModuleSummary,
  AuditEvent, PendingApproval) so pages don't import protobuf.
- pages/modules.dart: FutureBuilder against listModules,
  groups CapabilityEntry rows by module, retry-on-error UI.
- pages/audit.dart: 2s polling Timer, status bar shows
  "live (polling 2s)" or "disconnected — <error>".
- pages/approvals.dart: live listApprovals, Approve sends
  DecideApproval(APPROVE), Reject opens a reason dialog and
  sends DecideApproval(REJECT). Both refresh after success.
- Connection-error states across all pages: cloud_off icon +
  hint to start `fai serve` + Retry button.

When no hub is running the pages render their disconnected
state instead of crashing. When a hub is running, the data is
real — no more mock fixture.

Bumps fai_studio 0.1.0 -> 0.2.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-05 16:27:54 +02:00
parent 7c11598846
commit 127c497f73
6 changed files with 602 additions and 354 deletions

View file

@ -1,5 +1,10 @@
// Smoke test: app boots, navigation rail shows the three MVP
// pages, and the Modules page renders the mocked module list.
// Smoke test: app boots without crashing and shows the three
// MVP destinations in the navigation rail.
//
// Pages themselves now make live gRPC calls. We pump only one
// frame so they enter the loading state without actually
// awaiting the connection (which would fail without a running
// hub in CI).
import 'package:flutter_test/flutter_test.dart';
import 'package:fai_studio/main.dart';
@ -8,21 +13,10 @@ void main() {
testWidgets('Studio shell shows the three MVP destinations',
(tester) async {
await tester.pumpWidget(const StudioApp());
await tester.pump();
expect(find.text('F∆I Studio'), findsOneWidget);
expect(find.text('Modules'), findsWidgets);
expect(find.text('Audit'), findsOneWidget);
expect(find.text('Approvals'), findsOneWidget);
});
testWidgets('Modules page lists the mocked modules',
(tester) async {
await tester.pumpWidget(const StudioApp());
await tester.pump();
expect(find.text('echo'), findsOneWidget);
expect(find.text('text-extract'), findsOneWidget);
expect(find.text('llm-chat'), findsOneWidget);
});
}