Recl∆Im — the F∆I product line for Pflicht-basierte
Wirkungsanalyse und Reform-Vorschlags-Pipelines. Replaces the
working title 'Law-Heatmap', which described the demo view
rather than the platform. Heatmap, Norms list, Hub view,
Methodik are now four panes of the same Recl∆Im app.
Renames in this commit:
Flutter package lawheatmap_app -> reclaim_app
macOS bundle id ai.flemming.lawheatmapApp -> ai.flemming.reclaim
macOS PRODUCT_NAME lawheatmap_app -> reclaim_app
Theme tokens LawHeatmapColors/Space/Radius/Typography/Theme
-> Reclaim*
Surface card LawHeatmapCard -> ReclaimCard
Top-level widget LawHeatmapApp -> ReclaimApp
Theme files lawheatmap_theme.dart/lawheatmap_tokens.dart
-> reclaim_theme.dart/reclaim_tokens.dart
Widget file lawheatmap_card.dart -> reclaim_card.dart
Build script build-macos.sh paths and headline string
Docs MACHBARKEITSSTUDIE.md, METHODIK.md, RUN.md,
app/README.md, flow YAML
UI strings 'F∆I Law-Heatmap' / 'Law-Heatmap'
-> 'Recl∆Im' / 'Recl∆Im (F∆I)' for vendor-
prefixed contexts
flutter analyze: clean. flutter test: 2/2 passing.
The local working directory stays fai_lawheatmap/ for now —
the dir is referenced by the .claude project metadata; renaming
it would break session continuity for no real gain. The Forgejo
repo was renamed via API in the same change-set (fai/lawheatmap
-> fai/reclaim), with the legacy slug serving a redirect
courtesy of Forgejo's built-in rename handling.
Signed-off-by: flemming-it <sf@flemming.it>
128 lines
4.5 KiB
Dart
128 lines
4.5 KiB
Dart
import 'package:chain_client_sdk/chain_client_sdk.dart' as chain;
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../data/hub_repository.dart';
|
|
import '../data/repository.dart';
|
|
import '../theme/reclaim_tokens.dart';
|
|
import 'reclaim_card.dart';
|
|
|
|
/// Lists the capabilities the connected hub has installed.
|
|
/// Empty / explanatory state when the active repository is not
|
|
/// a live [HubRepository]. Real wire to HubClient.listCapabilities().
|
|
class HubCapabilitiesCard extends StatefulWidget {
|
|
const HubCapabilitiesCard({super.key, required this.repository});
|
|
|
|
final EvaluationRepository repository;
|
|
|
|
@override
|
|
State<HubCapabilitiesCard> createState() => _HubCapabilitiesCardState();
|
|
}
|
|
|
|
class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
|
|
late Future<List<chain.CapabilityEntry>> _future;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_future = _load();
|
|
}
|
|
|
|
Future<List<chain.CapabilityEntry>> _load() async {
|
|
final repo = widget.repository;
|
|
if (repo is! HubRepository || !repo.isHealthy) return const [];
|
|
return repo.client.listCapabilities();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final t = Theme.of(context).textTheme;
|
|
final repo = widget.repository;
|
|
final liveHub = repo is HubRepository && repo.isHealthy;
|
|
|
|
return ReclaimCard(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(children: [
|
|
Text('Installierte Capabilities', style: t.headlineSmall),
|
|
const Spacer(),
|
|
if (liveHub)
|
|
IconButton(
|
|
tooltip: 'Neu laden',
|
|
onPressed: () => setState(() => _future = _load()),
|
|
icon: const Icon(Icons.refresh),
|
|
),
|
|
]),
|
|
const SizedBox(height: ReclaimSpace.sm),
|
|
if (!liveHub)
|
|
Text(
|
|
'Sichtbar sobald die App im Hub-Modus startet und '
|
|
'der Hub erreichbar ist (siehe Verbindung oben).',
|
|
style: t.labelSmall?.copyWith(
|
|
color: ReclaimColors.mute,
|
|
),
|
|
)
|
|
else
|
|
FutureBuilder<List<chain.CapabilityEntry>>(
|
|
future: _future,
|
|
builder: (context, snap) {
|
|
if (snap.connectionState == ConnectionState.waiting) {
|
|
return const Padding(
|
|
padding: EdgeInsets.all(ReclaimSpace.md),
|
|
child: CircularProgressIndicator(),
|
|
);
|
|
}
|
|
if (snap.hasError) {
|
|
return Text('Fehler: ${snap.error}',
|
|
style: t.labelLarge
|
|
?.copyWith(color: ReclaimColors.harmWarm));
|
|
}
|
|
final items = snap.data ?? const [];
|
|
if (items.isEmpty) {
|
|
return Text(
|
|
'Hub hat noch keine Capabilities installiert. '
|
|
'Mit `chain install <name>` hinzufügen.',
|
|
style: t.bodyLarge?.copyWith(
|
|
color: ReclaimColors.mute,
|
|
),
|
|
);
|
|
}
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
for (final c in items)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 280,
|
|
child: Text(
|
|
'${c.capability}@${c.version}',
|
|
style: t.bodyLarge?.copyWith(
|
|
fontFamily: ReclaimTypography.mono,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
'${c.moduleName} ${c.moduleVersion}'
|
|
'${c.kind.isEmpty ? "" : " · ${c.kind}"}',
|
|
style: t.labelSmall?.copyWith(
|
|
color: ReclaimColors.mute,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|