feat(claude): shared account budget in Team tab + meta-sidebar card facelift (T-158)

Implements T-158 option A: the forwarded /usage budget (already parsed for
the Activity tab, T-415) now also renders in the Team tab as a single
ACCOUNT card, captioned "shared across the team". Usage is per-account —
every team session shares one ~/.claude login — so it's shown once, not
split per member (which would just repeat identical numbers).

Facelift: the Activity, Team, and Config tabs now render their sections as
elevated cards (panelHeader fill, dividerColor hairline, 6px radius) under
small-caps mono headers, matching the settings overlay's card design
(settings_category_view). Shared helpers — metaSectionHeader / metaCard /
metaCardRow — live in meta_sidebar/models.dart; the SESSION control strip
and the pinned Config SETTINGS block adopt the same card. The shared
ClideAccordion (also used by decisions/tickets) is left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 11:31:50 +02:00
co-authored by Claude Opus 4.8
parent c4be179a87
commit 763d628fc9
11 changed files with 483 additions and 141 deletions
+75
View File
@@ -0,0 +1,75 @@
/// T-158: the Team tab carries the shared account budget — a single ACCOUNT
/// card from the forwarded `/usage`, NOT a per-member split (usage is
/// per-account: every team session shares one ~/.claude login).
library;
import 'package:clide/builtin/claude/src/claude_status.dart' show ClaudeUsage;
import 'package:clide/builtin/claude/src/meta_sidebar/team_tab.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
import '../../helpers/widget_harness.dart';
void main() {
late KernelFixture f;
late TextEditingController inject;
setUp(() async {
f = await KernelFixture.create();
inject = TextEditingController();
});
tearDown(() {
inject.dispose();
f.dispose();
});
// No team members + no catalog seeded, so labels fall back to their English
// placeholders ('ACCOUNT' is the uppercased section header).
TeamTabView view({ClaudeUsage? usage}) => TeamTabView(
members: const [],
memberStatus: const {},
orchestrator: null,
tasks: const [],
injectingAgentId: null,
injectController: inject,
onToggleInject: (_) {},
onInjectSubmit: (_, _) {},
onClose: (_) {},
onSetPermissionMode: (_, _) {},
onFork: (_) {},
onOpenChatPane: () {},
usage: usage,
);
testWidgets('renders the shared ACCOUNT budget card when usage is present', (tester) async {
await tester.pumpWidget(harness(f, view(usage: const ClaudeUsage(session: '15% used', week: '53% used', weekSonnet: '0% used'))));
await tester.pump();
expect(find.text('ACCOUNT'), findsOneWidget);
expect(find.text('15% used'), findsOneWidget);
expect(find.text('53% used'), findsOneWidget);
expect(find.text('0% used'), findsOneWidget);
// Labelled shared, not per-member.
expect(find.text('Shared across the team'), findsOneWidget);
// Solo (no members) still shows the budget above the empty-team notice.
expect(find.text('No team active.'), findsOneWidget);
});
testWidgets('omits the ACCOUNT card when no usage has been fetched', (tester) async {
await tester.pumpWidget(harness(f, view()));
await tester.pump();
expect(find.text('ACCOUNT'), findsNothing);
expect(find.text('Shared across the team'), findsNothing);
expect(find.text('No team active.'), findsOneWidget);
});
testWidgets('omits the ACCOUNT card when the usage result is empty', (tester) async {
await tester.pumpWidget(harness(f, view(usage: const ClaudeUsage())));
await tester.pump();
expect(find.text('ACCOUNT'), findsNothing);
});
}