feat(brand): rename product to Recl∆Im (Reclaim) across app, bundle, docs

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>
This commit is contained in:
flemming-it 2026-06-18 13:42:55 +02:00
parent 90b7500aaf
commit e08cedcd3f
29 changed files with 291 additions and 226 deletions

View file

@ -2,7 +2,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../theme/lawheatmap_tokens.dart';
import '../theme/reclaim_tokens.dart';
/// The FI / ChIn brand mark: an upward triangle (the ``) with
/// a petrol "eye" at the centre. Geometry mirrors fai_web's
@ -89,7 +89,7 @@ class _DeltaPainter extends CustomPainter {
// Eye petrol fill, centred on the triangle's centroid.
final eyeCentre = Offset(w / 2, inset + (h - 2 * inset) * 2 / 3);
final eyeR = w * 0.06 * pulse;
final eye = Paint()..color = LawHeatmapColors.signal;
final eye = Paint()..color = ReclaimColors.signal;
canvas.drawCircle(eyeCentre, eyeR, eye);
}

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import '../theme/lawheatmap_tokens.dart';
import '../theme/reclaim_tokens.dart';
/// Permanent banner shown above all data screens in mock mode.
/// Carries the political-defensibility caveat: this is demo data,
@ -14,29 +14,29 @@ class DemoBanner extends StatelessWidget {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: LawHeatmapSpace.lg,
vertical: LawHeatmapSpace.sm,
horizontal: ReclaimSpace.lg,
vertical: ReclaimSpace.sm,
),
decoration: BoxDecoration(
color: LawHeatmapColors.tierT4.withValues(alpha: 0.12),
color: ReclaimColors.tierT4.withValues(alpha: 0.12),
border: Border(
bottom: BorderSide(
color: LawHeatmapColors.tierT4.withValues(alpha: 0.55),
color: ReclaimColors.tierT4.withValues(alpha: 0.55),
),
),
),
child: Row(
children: [
const Icon(Icons.science_outlined,
size: 18, color: LawHeatmapColors.tierT4),
const SizedBox(width: LawHeatmapSpace.sm),
size: 18, color: ReclaimColors.tierT4),
const SizedBox(width: ReclaimSpace.sm),
Expanded(
child: Text(
'Demo-Daten — Phase 0, ohne Verbands-Erhebung. '
'Keine Juristen-Approval, keine Beirats-Validierung. '
'Alle Werte tragen Tier T4, bis T1/T2-Pipeline läuft.',
style: t.labelSmall?.copyWith(
color: LawHeatmapColors.tierT4,
color: ReclaimColors.tierT4,
),
),
),

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart';
import 'lawheatmap_card.dart';
import '../theme/reclaim_tokens.dart';
import 'reclaim_card.dart';
import 'source_chip.dart';
/// Sidebar listing the citable sources behind an evaluation.
@ -16,39 +16,39 @@ class EvidenceSidebar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return LawHeatmapCard(
return ReclaimCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Quellen', style: t.headlineSmall),
const SizedBox(height: LawHeatmapSpace.sm),
const SizedBox(height: ReclaimSpace.sm),
Text(
'Jede Zahl trägt die Tier-Stufe ihres schwächsten '
'Bestandteils (Regel der niedrigsten Stufe, §6.7).',
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute),
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
),
const SizedBox(height: LawHeatmapSpace.lg),
const SizedBox(height: ReclaimSpace.lg),
for (final s in evaluation.sources) SourceChip(source: s),
const Divider(height: LawHeatmapSpace.xl),
const Divider(height: ReclaimSpace.xl),
Text('Audit-Event', style: t.labelLarge),
const SizedBox(height: 4),
SelectableText(
evaluation.auditEventId,
style: t.labelSmall?.copyWith(
fontFamily: LawHeatmapTypography.mono,
color: LawHeatmapColors.mute,
fontFamily: ReclaimTypography.mono,
color: ReclaimColors.mute,
),
),
const SizedBox(height: LawHeatmapSpace.sm),
const SizedBox(height: ReclaimSpace.sm),
Text(
'Quell-SHA-256:',
style: t.labelSmall?.copyWith(color: LawHeatmapColors.mute),
style: t.labelSmall?.copyWith(color: ReclaimColors.mute),
),
SelectableText(
evaluation.norm.sourceSha256,
style: t.labelSmall?.copyWith(
fontFamily: LawHeatmapTypography.mono,
color: LawHeatmapColors.mute,
fontFamily: ReclaimTypography.mono,
color: ReclaimColors.mute,
fontSize: 10,
),
),

View file

@ -3,7 +3,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart';
import '../theme/reclaim_tokens.dart';
/// 2D scatter heatmap x: Schaden (/Jahr, log-scaled),
/// y: Nutzen (05), point area: Betroffenheit (sqrt-scaled).
@ -121,7 +121,7 @@ class _HeatmapPainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
final layout = _Layout(size);
final onSurface = theme.colorScheme.onSurface;
const mute = LawHeatmapColors.mute;
const mute = ReclaimColors.mute;
// Plot frame.
final frame = Paint()
@ -163,7 +163,7 @@ class _HeatmapPainter extends CustomPainter {
const axisStyle = TextStyle(
color: mute,
fontSize: 10,
fontFamily: LawHeatmapTypography.body,
fontFamily: ReclaimTypography.body,
);
_drawText(
canvas,
@ -205,8 +205,8 @@ class _HeatmapPainter extends CustomPainter {
final r = layout.radius(e);
final hot = e.skmEurPerYear > 50000000;
final color = Color.lerp(
LawHeatmapColors.benefitSaturated,
LawHeatmapColors.harmWarm,
ReclaimColors.benefitSaturated,
ReclaimColors.harmWarm,
hot ? 0.5 : 0.0,
)!;
final body = Paint()..color = color.withValues(alpha: 0.7);
@ -224,7 +224,7 @@ class _HeatmapPainter extends CustomPainter {
TextStyle(
color: onSurface,
fontSize: 11,
fontFamily: LawHeatmapTypography.body,
fontFamily: ReclaimTypography.body,
fontWeight: i == hoverIndex ? FontWeight.w600 : FontWeight.w400,
),
);
@ -240,8 +240,8 @@ class _HeatmapPainter extends CustomPainter {
..color = theme.colorScheme.surface.withValues(alpha: 0.92);
final boxBorder = Paint()
..style = PaintingStyle.stroke
..color = LawHeatmapColors.signal.withValues(alpha: 0.6);
final rrect = RRect.fromRectAndRadius(box, LawHeatmapRadius.sm);
..color = ReclaimColors.signal.withValues(alpha: 0.6);
final rrect = RRect.fromRectAndRadius(box, ReclaimRadius.sm);
canvas.drawRRect(rrect, boxPaint);
canvas.drawRRect(rrect, boxBorder);
_drawText(
@ -252,7 +252,7 @@ class _HeatmapPainter extends CustomPainter {
color: onSurface,
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: LawHeatmapTypography.body,
fontFamily: ReclaimTypography.body,
),
);
_drawText(
@ -262,7 +262,7 @@ class _HeatmapPainter extends CustomPainter {
const TextStyle(
color: mute,
fontSize: 11,
fontFamily: LawHeatmapTypography.body,
fontFamily: ReclaimTypography.body,
),
);
_drawText(
@ -272,7 +272,7 @@ class _HeatmapPainter extends CustomPainter {
const TextStyle(
color: mute,
fontSize: 11,
fontFamily: LawHeatmapTypography.body,
fontFamily: ReclaimTypography.body,
),
);
}

View file

@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import '../data/hub_repository.dart';
import '../data/repository.dart';
import '../theme/lawheatmap_tokens.dart';
import 'lawheatmap_card.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
@ -39,7 +39,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
final repo = widget.repository;
final liveHub = repo is HubRepository && repo.isHealthy;
return LawHeatmapCard(
return ReclaimCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -53,13 +53,13 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
icon: const Icon(Icons.refresh),
),
]),
const SizedBox(height: LawHeatmapSpace.sm),
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: LawHeatmapColors.mute,
color: ReclaimColors.mute,
),
)
else
@ -68,14 +68,14 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
builder: (context, snap) {
if (snap.connectionState == ConnectionState.waiting) {
return const Padding(
padding: EdgeInsets.all(LawHeatmapSpace.md),
padding: EdgeInsets.all(ReclaimSpace.md),
child: CircularProgressIndicator(),
);
}
if (snap.hasError) {
return Text('Fehler: ${snap.error}',
style: t.labelLarge
?.copyWith(color: LawHeatmapColors.harmWarm));
?.copyWith(color: ReclaimColors.harmWarm));
}
final items = snap.data ?? const [];
if (items.isEmpty) {
@ -83,7 +83,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
'Hub hat noch keine Capabilities installiert. '
'Mit `chain install <name>` hinzufügen.',
style: t.bodyLarge?.copyWith(
color: LawHeatmapColors.mute,
color: ReclaimColors.mute,
),
);
}
@ -101,7 +101,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
child: Text(
'${c.capability}@${c.version}',
style: t.bodyLarge?.copyWith(
fontFamily: LawHeatmapTypography.mono,
fontFamily: ReclaimTypography.mono,
),
),
),
@ -110,7 +110,7 @@ class _HubCapabilitiesCardState extends State<HubCapabilitiesCard> {
'${c.moduleName} ${c.moduleVersion}'
'${c.kind.isEmpty ? "" : " · ${c.kind}"}',
style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute,
color: ReclaimColors.mute,
),
),
),

View file

@ -1,15 +1,15 @@
import 'package:flutter/material.dart';
import '../theme/lawheatmap_tokens.dart';
import '../theme/reclaim_tokens.dart';
/// Surface container visual equivalent of fai_chain_studio's
/// `ChainCard`, retuned to the fai_web palette.
class LawHeatmapCard extends StatelessWidget {
const LawHeatmapCard({
class ReclaimCard extends StatelessWidget {
const ReclaimCard({
super.key,
required this.child,
this.padding =
const EdgeInsets.all(LawHeatmapSpace.lg),
const EdgeInsets.all(ReclaimSpace.lg),
this.accent = false,
this.expand = false,
});
@ -39,9 +39,9 @@ class LawHeatmapCard extends StatelessWidget {
Container(
height: 2,
decoration: const BoxDecoration(
color: LawHeatmapColors.signal,
color: ReclaimColors.signal,
borderRadius: BorderRadius.vertical(
top: LawHeatmapRadius.md,
top: ReclaimRadius.md,
),
),
),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart';
import '../theme/reclaim_tokens.dart';
import 'tier_badge.dart';
/// A citable source line: tier badge + label + (planned) link.
@ -16,7 +16,7 @@ class SourceChip extends StatelessWidget {
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
return Padding(
padding: const EdgeInsets.only(bottom: LawHeatmapSpace.sm),
padding: const EdgeInsets.only(bottom: ReclaimSpace.sm),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -24,7 +24,7 @@ class SourceChip extends StatelessWidget {
padding: const EdgeInsets.only(top: 2),
child: TierBadge(tier: source.tier, compact: true),
),
const SizedBox(width: LawHeatmapSpace.sm),
const SizedBox(width: ReclaimSpace.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -33,14 +33,14 @@ class SourceChip extends StatelessWidget {
Text(
source.url,
style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute,
color: ReclaimColors.mute,
),
),
if (source.note != null)
Text(
source.note!,
style: t.labelSmall?.copyWith(
color: LawHeatmapColors.mute,
color: ReclaimColors.mute,
fontStyle: FontStyle.italic,
),
),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import '../data/models.dart';
import '../theme/lawheatmap_tokens.dart';
import '../theme/reclaim_tokens.dart';
/// Tier badge surfaces the lowest evidence tier in a composed
/// value. Every figure in the app carries one of these next to it.
@ -15,22 +15,22 @@ class TierBadge extends StatelessWidget {
Widget build(BuildContext context) {
final t = Theme.of(context).textTheme;
final color = switch (tier) {
EvidenceTier.t1 => LawHeatmapColors.tierT1,
EvidenceTier.t2 => LawHeatmapColors.tierT2,
EvidenceTier.t3 => LawHeatmapColors.tierT3,
EvidenceTier.t4 => LawHeatmapColors.tierT4,
EvidenceTier.t1 => ReclaimColors.tierT1,
EvidenceTier.t2 => ReclaimColors.tierT2,
EvidenceTier.t3 => ReclaimColors.tierT3,
EvidenceTier.t4 => ReclaimColors.tierT4,
};
return Tooltip(
message: '${tier.short}${tier.description}',
child: Container(
padding: EdgeInsets.symmetric(
horizontal: compact ? 6 : LawHeatmapSpace.sm,
horizontal: compact ? 6 : ReclaimSpace.sm,
vertical: compact ? 2 : 4,
),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.18),
border: Border.all(color: color.withValues(alpha: 0.55)),
borderRadius: const BorderRadius.all(LawHeatmapRadius.sm),
borderRadius: const BorderRadius.all(ReclaimRadius.sm),
),
child: Row(
mainAxisSize: MainAxisSize.min,