feat(claude): per-repo account badge in the Claude pane chrome (T-481)
The always-visible affordance for the multi-account epic: a compact badge in the pane header showing which account this workspace is bound to (or "default"), colour-tinted per account so two windows are distinguishable at a glance. Tapping opens a picker of the registered accounts + Default; selecting binds/unbinds via the shared bindWorkspaceAccount helper (respawn + lock-sync follow on the bus). Hidden when no accounts are registered, so it adds no chrome for users not using the feature. accountAccent derives the tint by hashing the name into a fixed set of theme tokens — never an arbitrary colour, so the palette stays theme-owned. The welcome-view accounts section (the other half of T-481) is split to T-486: the welcome builtin is intentionally decoupled from feature builtins, so it needs a welcome-section contribution point rather than importing claude directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -155,4 +155,65 @@ void main() {
|
||||
expect(reg.accountByName('personal'), isNull);
|
||||
expect(find.bySemanticsLabel(RegExp('bound to a workspace')), findsOneWidget, reason: 'the bound account guards its remove');
|
||||
});
|
||||
|
||||
// The Claude pane account badge (T-481).
|
||||
Future<void> pumpBadge(WidgetTester tester, String? root) => tester.pumpWidget(
|
||||
harness(
|
||||
f,
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(width: 220, child: ClaudeAccountBadge(workspaceRoot: root)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
testWidgets('badge: hidden (renders nothing) when no accounts are registered', (tester) async {
|
||||
await pumpBadge(tester, f.tempDir.path);
|
||||
await tester.pump();
|
||||
expect(find.byType(ClaudeAccountBadge), findsOneWidget);
|
||||
expect(find.text('default'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('badge: shows default when unbound, and switches account on pick', (tester) async {
|
||||
final reg = AccountRegistry(f.services.settings);
|
||||
final published = <Map<String, Object?>>[];
|
||||
final sub = f.services.messages.subscribe(channel: accountActionChannel).listen((m) => published.add(m.data));
|
||||
addTearDown(sub.cancel);
|
||||
await tester.runAsync(() => reg.registerAccount('work', '/home/u/.claude-work'));
|
||||
|
||||
await pumpBadge(tester, f.tempDir.path);
|
||||
await tester.pump();
|
||||
expect(find.text('default'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(ClaudeAccountBadge));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('work'));
|
||||
await tester.pump();
|
||||
expect(reg.boundName(f.tempDir.path), 'work');
|
||||
expect(published.single['action'], 'set');
|
||||
});
|
||||
|
||||
testWidgets('badge: shows the bound account name', (tester) async {
|
||||
final reg = AccountRegistry(f.services.settings);
|
||||
await tester.runAsync(() async {
|
||||
await reg.registerAccount('work', '/home/u/.claude-work');
|
||||
await reg.bindWorkspace(f.tempDir.path, 'work');
|
||||
});
|
||||
await pumpBadge(tester, f.tempDir.path);
|
||||
await tester.pump();
|
||||
expect(find.text('work'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('accountAccent is stable per name, muted for default, and theme-sourced', (tester) async {
|
||||
final tokens = f.services.theme.current.surface;
|
||||
expect(accountAccent(null, tokens), tokens.globalTextMuted);
|
||||
expect(accountAccent('work', tokens), accountAccent('work', tokens));
|
||||
expect([
|
||||
tokens.globalFocus,
|
||||
tokens.statusSuccess,
|
||||
tokens.statusWarning,
|
||||
tokens.statusError,
|
||||
tokens.buttonBackground,
|
||||
], contains(accountAccent('work', tokens)));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user