refactor(i18n): route claude pane labels through the catalog (T-464)
Migrate the hardcoded user-facing strings across builtin.claude — composer, conversation cards/segments, permission + AskUserQuestion prompts, task dock, meta-sidebar (activity/config/team/roster), session/model pickers — to ClideSettings.i18n.string/.interpolated, English kept as the placeholder; builtin.claude_en_us.json extended to cover them. Context threaded into the context-free render helpers (toolInputBody chain, _ConversationTurn) the same way the mono family already is. No en_US behaviour change (D-21). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,21 +49,37 @@ class ActivityTabView extends StatelessWidget {
|
||||
final latest = stats.latest;
|
||||
final u = usage;
|
||||
final sections = <MetaSection>[
|
||||
..._workflowSection(tokens),
|
||||
..._workflowSection(context, tokens),
|
||||
if (u != null)
|
||||
MetaSection('USAGE', [
|
||||
if (u.session != null) MetaRow('session', u.session!),
|
||||
if (u.week != null) MetaRow('week (all)', u.week!),
|
||||
if (u.weekSonnet != null) MetaRow('week (sonnet)', u.weekSonnet!),
|
||||
MetaSection(ClideSettings.i18n.string(context, 'activity.section.usage', namespace: 'builtin.claude', placeholder: 'USAGE'), [
|
||||
if (u.session != null)
|
||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.session', namespace: 'builtin.claude', placeholder: 'session'), u.session!),
|
||||
if (u.week != null)
|
||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.weekAll', namespace: 'builtin.claude', placeholder: 'week (all)'), u.week!),
|
||||
if (u.weekSonnet != null)
|
||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.weekSonnet', namespace: 'builtin.claude', placeholder: 'week (sonnet)'), u.weekSonnet!),
|
||||
]),
|
||||
if (latest != null)
|
||||
MetaSection('TODAY', [
|
||||
MetaRow('messages', '${latest.messageCount}'),
|
||||
MetaRow('sessions', '${latest.sessionCount}'),
|
||||
MetaRow('tool calls', '${latest.toolCallCount}'),
|
||||
MetaSection(ClideSettings.i18n.string(context, 'activity.section.today', namespace: 'builtin.claude', placeholder: 'TODAY'), [
|
||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.messages', namespace: 'builtin.claude', placeholder: 'messages'), '${latest.messageCount}'),
|
||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.sessions', namespace: 'builtin.claude', placeholder: 'sessions'), '${latest.sessionCount}'),
|
||||
MetaRow(
|
||||
ClideSettings.i18n.string(context, 'activity.row.toolCalls', namespace: 'builtin.claude', placeholder: 'tool calls'),
|
||||
'${latest.toolCallCount}',
|
||||
),
|
||||
]),
|
||||
if (latest != null) MetaSection('LIFETIME', [MetaRow('messages', '${stats.lifetimeMessages}'), MetaRow('sessions', '${stats.lifetimeSessions}')]),
|
||||
..._runtimeSection(tokens),
|
||||
if (latest != null)
|
||||
MetaSection(ClideSettings.i18n.string(context, 'activity.section.lifetime', namespace: 'builtin.claude', placeholder: 'LIFETIME'), [
|
||||
MetaRow(
|
||||
ClideSettings.i18n.string(context, 'activity.row.messages', namespace: 'builtin.claude', placeholder: 'messages'),
|
||||
'${stats.lifetimeMessages}',
|
||||
),
|
||||
MetaRow(
|
||||
ClideSettings.i18n.string(context, 'activity.row.sessions', namespace: 'builtin.claude', placeholder: 'sessions'),
|
||||
'${stats.lifetimeSessions}',
|
||||
),
|
||||
]),
|
||||
..._runtimeSection(context, tokens),
|
||||
];
|
||||
|
||||
return ListView(
|
||||
@@ -73,20 +89,57 @@ class ActivityTabView extends StatelessWidget {
|
||||
// the builtin.claude/command bus — identical to typing the command.
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: ClideText('SESSION', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(context, 'activity.section.session', namespace: 'builtin.claude', placeholder: 'SESSION'),
|
||||
fontSize: clideFontSmall,
|
||||
color: tokens.sidebarSectionHeader,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
_control(context, tokens, 'clear', 'trash', '/clear'),
|
||||
_control(context, tokens, 'compact', 'arrows-in-simple', '/compact'),
|
||||
_control(context, tokens, 'fork', 'git-branch', '/fork'),
|
||||
_control(context, tokens, 'resume', 'clock-counter-clockwise', '/resume'),
|
||||
_control(
|
||||
context,
|
||||
tokens,
|
||||
ClideSettings.i18n.string(context, 'activity.control.clear', namespace: 'builtin.claude', placeholder: 'clear'),
|
||||
'trash',
|
||||
'/clear',
|
||||
),
|
||||
_control(
|
||||
context,
|
||||
tokens,
|
||||
ClideSettings.i18n.string(context, 'activity.control.compact', namespace: 'builtin.claude', placeholder: 'compact'),
|
||||
'arrows-in-simple',
|
||||
'/compact',
|
||||
),
|
||||
_control(
|
||||
context,
|
||||
tokens,
|
||||
ClideSettings.i18n.string(context, 'activity.control.fork', namespace: 'builtin.claude', placeholder: 'fork'),
|
||||
'git-branch',
|
||||
'/fork',
|
||||
),
|
||||
_control(
|
||||
context,
|
||||
tokens,
|
||||
ClideSettings.i18n.string(context, 'activity.control.resume', namespace: 'builtin.claude', placeholder: 'resume'),
|
||||
'clock-counter-clockwise',
|
||||
'/resume',
|
||||
),
|
||||
const Spacer(),
|
||||
_control(context, tokens, 'refresh usage', 'arrow-clockwise', '/usage'),
|
||||
_control(
|
||||
context,
|
||||
tokens,
|
||||
ClideSettings.i18n.string(context, 'activity.control.refreshUsage', namespace: 'builtin.claude', placeholder: 'refresh usage'),
|
||||
'arrow-clockwise',
|
||||
'/usage',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (sections.isEmpty) metaPlaceholder('No activity recorded yet.') else ...metaTableChildren(tokens, sections),
|
||||
if (sections.isEmpty)
|
||||
metaPlaceholder(ClideSettings.i18n.string(context, 'activity.empty', namespace: 'builtin.claude', placeholder: 'No activity recorded yet.'))
|
||||
else
|
||||
...metaTableChildren(tokens, sections),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -94,11 +147,26 @@ class ActivityTabView extends StatelessWidget {
|
||||
Widget _control(BuildContext context, SurfaceTokens tokens, String label, String glyph, String command) {
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: '$label session',
|
||||
label: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'activity.control.semantics',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: '$label session',
|
||||
replacers: [I18nReplacer(from: '{label}', replace: label)],
|
||||
),
|
||||
excludeSemantics: true,
|
||||
onTap: () => _command(context, command),
|
||||
child: ClideTappable(
|
||||
tooltip: '$label · $command',
|
||||
tooltip: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'activity.control.tooltip',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: '$label · $command',
|
||||
replacers: [
|
||||
I18nReplacer(from: '{label}', replace: label),
|
||||
I18nReplacer(from: '{command}', replace: command),
|
||||
],
|
||||
),
|
||||
onTap: () => _command(context, command),
|
||||
builder: (ctx, hovered, _) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
@@ -111,31 +179,50 @@ class ActivityTabView extends StatelessWidget {
|
||||
/// An aggregate WORKFLOWS section while one or more workflow runs exist this
|
||||
/// session (T-416): a row per run — its name and `done/total agents`, tinted
|
||||
/// focus while running and success once complete.
|
||||
List<MetaSection> _workflowSection(SurfaceTokens tokens) {
|
||||
List<MetaSection> _workflowSection(BuildContext context, SurfaceTokens tokens) {
|
||||
final runs = workflows.values.toList();
|
||||
if (runs.isEmpty) return const [];
|
||||
final done = ClideSettings.i18n.string(context, 'activity.workflow.done', namespace: 'builtin.claude', placeholder: 'done');
|
||||
final starting = ClideSettings.i18n.string(context, 'activity.workflow.starting', namespace: 'builtin.claude', placeholder: 'starting');
|
||||
final fallback = ClideSettings.i18n.string(context, 'activity.workflow.fallback', namespace: 'builtin.claude', placeholder: 'workflow');
|
||||
return [
|
||||
MetaSection('WORKFLOWS', [
|
||||
MetaSection(ClideSettings.i18n.string(context, 'activity.section.workflows', namespace: 'builtin.claude', placeholder: 'WORKFLOWS'), [
|
||||
for (final r in runs)
|
||||
MetaRow(
|
||||
r.name ?? r.taskId ?? 'workflow',
|
||||
r.agentCount == 0 ? (r.done ? 'done' : 'starting') : '${r.doneCount}/${r.agentCount} agents${r.done ? ' ✓' : ''}',
|
||||
r.name ?? r.taskId ?? fallback,
|
||||
r.agentCount == 0 ? (r.done ? done : starting) : '${r.doneCount}/${r.agentCount} agents${r.done ? ' ✓' : ''}',
|
||||
valueColor: r.done ? tokens.statusSuccess : tokens.globalFocus,
|
||||
),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
List<MetaSection> _runtimeSection(SurfaceTokens tokens) {
|
||||
List<MetaSection> _runtimeSection(BuildContext context, SurfaceTokens tokens) {
|
||||
final st = primaryStatus;
|
||||
final skills = config?.skills.length;
|
||||
final rows = <MetaRow>[
|
||||
if (st?.model != null) MetaRow('model', shortModelLabel(st!.model!), valueColor: tokens.globalFocus),
|
||||
if (st?.effort != null) MetaRow('effort', st!.effort!),
|
||||
if (st?.contextTokens != null) MetaRow('context', '${formatTokenCount(st!.contextTokens!)} ctx'),
|
||||
if (st?.permissionMode != null) MetaRow('mode', permissionModeLabel(st!.permissionMode!)),
|
||||
if (skills != null) MetaRow('skills', '$skills'),
|
||||
if (st?.model != null)
|
||||
MetaRow(
|
||||
ClideSettings.i18n.string(context, 'activity.row.model', namespace: 'builtin.claude', placeholder: 'model'),
|
||||
shortModelLabel(st!.model!),
|
||||
valueColor: tokens.globalFocus,
|
||||
),
|
||||
if (st?.effort != null)
|
||||
MetaRow(ClideSettings.i18n.string(context, 'activity.row.effort', namespace: 'builtin.claude', placeholder: 'effort'), st!.effort!),
|
||||
if (st?.contextTokens != null)
|
||||
MetaRow(
|
||||
ClideSettings.i18n.string(context, 'activity.row.context', namespace: 'builtin.claude', placeholder: 'context'),
|
||||
'${formatTokenCount(st!.contextTokens!)} ctx',
|
||||
),
|
||||
if (st?.permissionMode != null)
|
||||
MetaRow(
|
||||
ClideSettings.i18n.string(context, 'activity.row.mode', namespace: 'builtin.claude', placeholder: 'mode'),
|
||||
permissionModeLabel(st!.permissionMode!),
|
||||
),
|
||||
if (skills != null) MetaRow(ClideSettings.i18n.string(context, 'activity.row.skills', namespace: 'builtin.claude', placeholder: 'skills'), '$skills'),
|
||||
];
|
||||
return rows.isEmpty ? const [] : [MetaSection('RUNTIME · primary', rows)];
|
||||
return rows.isEmpty
|
||||
? const []
|
||||
: [MetaSection(ClideSettings.i18n.string(context, 'activity.section.runtime', namespace: 'builtin.claude', placeholder: 'RUNTIME · primary'), rows)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class ConfigTabView extends StatelessWidget {
|
||||
final tokens = ClideSettings.theme.of(context).surface;
|
||||
final cfg = config;
|
||||
if (cfg == null) {
|
||||
return metaPlaceholder('Claude environment not loaded.');
|
||||
return metaPlaceholder(ClideSettings.i18n.string(context, 'config.empty', namespace: 'builtin.claude', placeholder: 'Claude environment not loaded.'));
|
||||
}
|
||||
final settings = cfg.settings;
|
||||
final model = status?.model ?? cfg.probe?.model ?? settings['model']?.toString() ?? 'default';
|
||||
@@ -56,26 +56,40 @@ class ConfigTabView extends StatelessWidget {
|
||||
// Pinned SETTINGS control panel — not collapsible.
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: ClideText('SETTINGS', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(context, 'config.section.settings', namespace: 'builtin.claude', placeholder: 'SETTINGS'),
|
||||
fontSize: clideFontSmall,
|
||||
color: tokens.sidebarSectionHeader,
|
||||
),
|
||||
),
|
||||
SettingControlRow(
|
||||
label: 'model',
|
||||
label: ClideSettings.i18n.string(context, 'config.row.model', namespace: 'builtin.claude', placeholder: 'model'),
|
||||
value: model,
|
||||
valueColor: tokens.globalFocus,
|
||||
options: (models == null || models!.isEmpty) ? kFallbackModels : models!,
|
||||
isActive: (o) => o.value == model || model.toLowerCase().contains(o.value.toLowerCase()),
|
||||
command: 'model',
|
||||
),
|
||||
SettingControlRow(label: 'effort', value: effort, options: kEffortLevels, isActive: (o) => o.value == effort, command: 'effort'),
|
||||
SettingControlRow(
|
||||
label: 'permission mode',
|
||||
label: ClideSettings.i18n.string(context, 'config.row.effort', namespace: 'builtin.claude', placeholder: 'effort'),
|
||||
value: effort,
|
||||
options: kEffortLevels,
|
||||
isActive: (o) => o.value == effort,
|
||||
command: 'effort',
|
||||
),
|
||||
SettingControlRow(
|
||||
label: ClideSettings.i18n.string(context, 'config.row.permissionMode', namespace: 'builtin.claude', placeholder: 'permission mode'),
|
||||
value: permissionModeLabel(mode),
|
||||
options: kPermissionModes,
|
||||
isActive: (o) => o.value == mode,
|
||||
command: 'permissions',
|
||||
),
|
||||
_configRow(tokens, 'output style', outputStyle),
|
||||
_configRow(tokens, 'source', '~/.claude + .claude'),
|
||||
_configRow(tokens, ClideSettings.i18n.string(context, 'config.row.outputStyle', namespace: 'builtin.claude', placeholder: 'output style'), outputStyle),
|
||||
_configRow(
|
||||
tokens,
|
||||
ClideSettings.i18n.string(context, 'config.row.source', namespace: 'builtin.claude', placeholder: 'source'),
|
||||
ClideSettings.i18n.string(context, 'config.row.source.value', namespace: 'builtin.claude', placeholder: '~/.claude + .claude'),
|
||||
),
|
||||
|
||||
// ---- Accordion sections ----
|
||||
for (final section in ConfigSection.values) _accordion(context, tokens, cfg, section),
|
||||
@@ -83,7 +97,16 @@ class ConfigTabView extends StatelessWidget {
|
||||
// Footer hint.
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: ClideText('expand a list to see all · click a skill/agent/command → opens its .md', muted: true, fontSize: clideFontSmall),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(
|
||||
context,
|
||||
'config.footer',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: 'expand a list to see all · click a skill/agent/command → opens its .md',
|
||||
),
|
||||
muted: true,
|
||||
fontSize: clideFontSmall,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -109,13 +132,13 @@ class ConfigTabView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
String _sectionLabel(ConfigSection section) => switch (section) {
|
||||
ConfigSection.skills => 'SKILLS',
|
||||
ConfigSection.agents => 'AGENTS',
|
||||
ConfigSection.commands => 'COMMANDS',
|
||||
ConfigSection.hooks => 'HOOKS',
|
||||
ConfigSection.permissions => 'PERMISSIONS',
|
||||
ConfigSection.mcpServers => 'MCP SERVERS',
|
||||
String _sectionLabel(BuildContext context, ConfigSection section) => switch (section) {
|
||||
ConfigSection.skills => ClideSettings.i18n.string(context, 'config.section.skills', namespace: 'builtin.claude', placeholder: 'SKILLS'),
|
||||
ConfigSection.agents => ClideSettings.i18n.string(context, 'config.section.agents', namespace: 'builtin.claude', placeholder: 'AGENTS'),
|
||||
ConfigSection.commands => ClideSettings.i18n.string(context, 'config.section.commands', namespace: 'builtin.claude', placeholder: 'COMMANDS'),
|
||||
ConfigSection.hooks => ClideSettings.i18n.string(context, 'config.section.hooks', namespace: 'builtin.claude', placeholder: 'HOOKS'),
|
||||
ConfigSection.permissions => ClideSettings.i18n.string(context, 'config.section.permissions', namespace: 'builtin.claude', placeholder: 'PERMISSIONS'),
|
||||
ConfigSection.mcpServers => ClideSettings.i18n.string(context, 'config.section.mcpServers', namespace: 'builtin.claude', placeholder: 'MCP SERVERS'),
|
||||
};
|
||||
|
||||
int _sectionCount(ClaudeConfig config, ConfigSection section) => switch (section) {
|
||||
@@ -131,7 +154,7 @@ class ConfigTabView extends StatelessWidget {
|
||||
final isExpanded = expanded.contains(section);
|
||||
final children = isExpanded ? _sectionChildren(context, tokens, config, section) : const <Widget>[];
|
||||
return ClideAccordion(
|
||||
label: _sectionLabel(section),
|
||||
label: _sectionLabel(context, section),
|
||||
count: _sectionCount(config, section),
|
||||
expanded: isExpanded,
|
||||
onToggle: () => onToggleSection(section),
|
||||
@@ -166,7 +189,7 @@ class ConfigTabView extends StatelessWidget {
|
||||
),
|
||||
];
|
||||
case ConfigSection.permissions:
|
||||
return _permissionRows(tokens, config.permissions);
|
||||
return _permissionRows(context, tokens, config.permissions);
|
||||
case ConfigSection.mcpServers:
|
||||
return [
|
||||
for (final srv in config.mcpServers)
|
||||
@@ -205,7 +228,7 @@ class ConfigTabView extends StatelessWidget {
|
||||
}
|
||||
|
||||
/// Renders grouped allow/ask/deny permission rows, colour-coded by kind.
|
||||
List<Widget> _permissionRows(SurfaceTokens tokens, ClaudePermissions perms) {
|
||||
List<Widget> _permissionRows(BuildContext context, SurfaceTokens tokens, ClaudePermissions perms) {
|
||||
// allow → statusSuccess, ask → statusWarning, deny → statusError
|
||||
Color kindColor(ConfigPermKind k) => switch (k) {
|
||||
ConfigPermKind.allow => tokens.statusSuccess,
|
||||
@@ -214,9 +237,9 @@ class ConfigTabView extends StatelessWidget {
|
||||
};
|
||||
|
||||
String kindLabel(ConfigPermKind k) => switch (k) {
|
||||
ConfigPermKind.allow => 'allow',
|
||||
ConfigPermKind.ask => 'ask',
|
||||
ConfigPermKind.deny => 'deny',
|
||||
ConfigPermKind.allow => ClideSettings.i18n.string(context, 'config.perm.allow', namespace: 'builtin.claude', placeholder: 'allow'),
|
||||
ConfigPermKind.ask => ClideSettings.i18n.string(context, 'config.perm.ask', namespace: 'builtin.claude', placeholder: 'ask'),
|
||||
ConfigPermKind.deny => ClideSettings.i18n.string(context, 'config.perm.deny', namespace: 'builtin.claude', placeholder: 'deny'),
|
||||
};
|
||||
|
||||
final groups = [(ConfigPermKind.allow, perms.allow), (ConfigPermKind.ask, perms.ask), (ConfigPermKind.deny, perms.deny)];
|
||||
@@ -320,18 +343,42 @@ class _SettingControlRowState extends State<SettingControlRow> {
|
||||
ClideMenuItem(
|
||||
label: o.description.isEmpty ? o.displayName : '${o.displayName} — ${o.description}',
|
||||
active: widget.isActive(o),
|
||||
semanticLabel: '${widget.label}: ${o.displayName}',
|
||||
semanticLabel: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'config.control.option.semantics',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: '${widget.label}: ${o.displayName}',
|
||||
replacers: [
|
||||
I18nReplacer(from: '{label}', replace: widget.label),
|
||||
I18nReplacer(from: '{name}', replace: o.displayName),
|
||||
],
|
||||
),
|
||||
onSelect: () => _pick(o.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
anchor: Semantics(
|
||||
button: true,
|
||||
label: '${widget.label}: ${widget.value}. Click to change.',
|
||||
label: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'config.control.semantics',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: '${widget.label}: ${widget.value}. Click to change.',
|
||||
replacers: [
|
||||
I18nReplacer(from: '{label}', replace: widget.label),
|
||||
I18nReplacer(from: '{value}', replace: widget.value),
|
||||
],
|
||||
),
|
||||
excludeSemantics: true,
|
||||
onTap: _overlay.toggle,
|
||||
child: ClideTappable(
|
||||
tooltip: 'change ${widget.label}',
|
||||
tooltip: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'config.control.tooltip',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: 'change ${widget.label}',
|
||||
replacers: [I18nReplacer(from: '{label}', replace: widget.label)],
|
||||
),
|
||||
onTap: _overlay.toggle,
|
||||
builder: (ctx, hovered, _) => DecoratedBox(
|
||||
decoration: BoxDecoration(color: hovered ? tokens.listItemHoverBackground : null, borderRadius: BorderRadius.circular(4)),
|
||||
|
||||
@@ -40,15 +40,27 @@ class PermissionModeBadge extends StatelessWidget {
|
||||
final isBypass = mode == 'bypassPermissions';
|
||||
final badgeColor = isBypass ? const Color(0xFFF06C6F) : tokens.globalFocus;
|
||||
|
||||
final tooltip =
|
||||
'Permission mode: ${permissionModeLabel(mode)}. '
|
||||
'Click to cycle default/acceptEdits/plan; Shift-click for bypassPermissions.';
|
||||
final tooltip = ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'permissionBadge.tooltip',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder:
|
||||
'Permission mode: ${permissionModeLabel(mode)}. '
|
||||
'Click to cycle default/acceptEdits/plan; Shift-click for bypassPermissions.',
|
||||
replacers: [I18nReplacer(from: '{mode}', replace: permissionModeLabel(mode))],
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 3),
|
||||
child: Semantics(
|
||||
button: true,
|
||||
label: 'Permission mode: $label',
|
||||
label: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'permissionBadge.semantics',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: 'Permission mode: $label',
|
||||
replacers: [I18nReplacer(from: '{label}', replace: label)],
|
||||
),
|
||||
excludeSemantics: true,
|
||||
onTap: () {
|
||||
if (HardwareKeyboard.instance.isShiftPressed) {
|
||||
|
||||
@@ -154,27 +154,40 @@ class _AgentRosterRowState extends State<AgentRosterRow> {
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ClideText('Enable bypassPermissions? All tool calls will be auto-allowed.', fontSize: clideFontSmall, color: tokens.globalTextMuted),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(
|
||||
context,
|
||||
'roster.bypass.confirmBody',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: 'Enable bypassPermissions? All tool calls will be auto-allowed.',
|
||||
),
|
||||
fontSize: clideFontSmall,
|
||||
color: tokens.globalTextMuted,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
// Confirm
|
||||
Semantics(
|
||||
button: true,
|
||||
label: 'Confirm bypass',
|
||||
label: ClideSettings.i18n.string(context, 'roster.bypass.confirm.semantics', namespace: 'builtin.claude', placeholder: 'Confirm bypass'),
|
||||
excludeSemantics: true,
|
||||
onTap: () {
|
||||
setState(() => _confirmingBypass = false);
|
||||
widget.onSetPermissionMode(widget.member.name, 'bypassPermissions');
|
||||
},
|
||||
child: ClideTappable(
|
||||
tooltip: 'Confirm',
|
||||
tooltip: ClideSettings.i18n.string(context, 'roster.bypass.confirm.tooltip', namespace: 'builtin.claude', placeholder: 'Confirm'),
|
||||
onTap: () {
|
||||
setState(() => _confirmingBypass = false);
|
||||
widget.onSetPermissionMode(widget.member.name, 'bypassPermissions');
|
||||
},
|
||||
builder: (ctx, hovered, _) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
|
||||
child: ClideText('OK', fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.globalFocus),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(context, 'roster.bypass.ok', namespace: 'builtin.claude', placeholder: 'OK'),
|
||||
fontSize: clideFontSmall,
|
||||
color: hovered ? tokens.globalForeground : tokens.globalFocus,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -182,15 +195,19 @@ class _AgentRosterRowState extends State<AgentRosterRow> {
|
||||
// Cancel
|
||||
Semantics(
|
||||
button: true,
|
||||
label: 'Cancel bypass',
|
||||
label: ClideSettings.i18n.string(context, 'roster.bypass.cancel.semantics', namespace: 'builtin.claude', placeholder: 'Cancel bypass'),
|
||||
excludeSemantics: true,
|
||||
onTap: () => setState(() => _confirmingBypass = false),
|
||||
child: ClideTappable(
|
||||
tooltip: 'Cancel',
|
||||
tooltip: ClideSettings.i18n.string(context, 'roster.bypass.cancel.tooltip', namespace: 'builtin.claude', placeholder: 'Cancel'),
|
||||
onTap: () => setState(() => _confirmingBypass = false),
|
||||
builder: (ctx, hovered, _) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
|
||||
child: ClideText('Cancel', fontSize: clideFontSmall, color: hovered ? tokens.globalForeground : tokens.globalTextMuted),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(context, 'roster.bypass.cancel', namespace: 'builtin.claude', placeholder: 'Cancel'),
|
||||
fontSize: clideFontSmall,
|
||||
color: hovered ? tokens.globalForeground : tokens.globalTextMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -206,7 +223,9 @@ class _AgentRosterRowState extends State<AgentRosterRow> {
|
||||
// Show / hide
|
||||
MetaIconButton(
|
||||
painter: isVisible ? PhosphorIcons.byName('eye') : PhosphorIcons.byName('eye-slash'),
|
||||
tooltip: isVisible ? 'Hide pane' : 'Show pane',
|
||||
tooltip: isVisible
|
||||
? ClideSettings.i18n.string(context, 'roster.hidePane', namespace: 'builtin.claude', placeholder: 'Hide pane')
|
||||
: ClideSettings.i18n.string(context, 'roster.showPane', namespace: 'builtin.claude', placeholder: 'Show pane'),
|
||||
color: tokens.globalTextMuted,
|
||||
onTap: () => isVisible ? widget.orchestrator!.hide(managed.id) : widget.orchestrator!.show(managed.id),
|
||||
),
|
||||
@@ -216,28 +235,30 @@ class _AgentRosterRowState extends State<AgentRosterRow> {
|
||||
// NOTE: We use eye/eyeSlash as stand-ins until a dedicated speaker
|
||||
// icon is added to PhosphorIcons (no speaker codepoint yet).
|
||||
// The semantic tooltip still says mute/unmute so AT users are clear.
|
||||
tooltip: isMuted ? 'Unmute messages' : 'Mute messages',
|
||||
tooltip: isMuted
|
||||
? ClideSettings.i18n.string(context, 'roster.unmute', namespace: 'builtin.claude', placeholder: 'Unmute messages')
|
||||
: ClideSettings.i18n.string(context, 'roster.mute', namespace: 'builtin.claude', placeholder: 'Mute messages'),
|
||||
color: isMuted ? tokens.globalFocus : tokens.globalTextMuted,
|
||||
onTap: () => isMuted ? widget.orchestrator!.unmute(managed.id) : widget.orchestrator!.mute(managed.id),
|
||||
),
|
||||
// Inject message
|
||||
MetaIconButton(
|
||||
painter: PhosphorIcons.byName('chat-circle'),
|
||||
tooltip: 'Inject message',
|
||||
tooltip: ClideSettings.i18n.string(context, 'roster.inject', namespace: 'builtin.claude', placeholder: 'Inject message'),
|
||||
color: isInjecting ? tokens.globalFocus : tokens.globalTextMuted,
|
||||
onTap: () => widget.onToggleInject(widget.member.name),
|
||||
),
|
||||
// Fork session (T-172): branch into a new pane without touching the original.
|
||||
MetaIconButton(
|
||||
painter: PhosphorIcons.byName('git-branch'),
|
||||
tooltip: 'Fork session',
|
||||
tooltip: ClideSettings.i18n.string(context, 'roster.fork', namespace: 'builtin.claude', placeholder: 'Fork session'),
|
||||
color: tokens.globalTextMuted,
|
||||
onTap: () => widget.onFork(widget.member.name),
|
||||
),
|
||||
// Close session
|
||||
MetaIconButton(
|
||||
painter: PhosphorIcons.byName('x'),
|
||||
tooltip: 'Close session',
|
||||
tooltip: ClideSettings.i18n.string(context, 'roster.close', namespace: 'builtin.claude', placeholder: 'Close session'),
|
||||
color: tokens.globalTextMuted,
|
||||
onTap: () => widget.onClose(widget.member.name),
|
||||
),
|
||||
@@ -262,7 +283,7 @@ class _AgentRosterRowState extends State<AgentRosterRow> {
|
||||
const SizedBox(width: 4),
|
||||
MetaIconButton(
|
||||
painter: PhosphorIcons.byName('x'),
|
||||
tooltip: 'Cancel',
|
||||
tooltip: ClideSettings.i18n.string(context, 'roster.inject.cancel', namespace: 'builtin.claude', placeholder: 'Cancel'),
|
||||
color: tokens.globalTextMuted,
|
||||
onTap: () => widget.onToggleInject(widget.member.name),
|
||||
),
|
||||
|
||||
@@ -29,7 +29,7 @@ class SidebarTabStrip extends StatelessWidget {
|
||||
child: Semantics(
|
||||
button: true,
|
||||
selected: t == current,
|
||||
label: _label(t),
|
||||
label: _label(context, t),
|
||||
excludeSemantics: true,
|
||||
onTap: () => onPick(t),
|
||||
child: ClideTappable(
|
||||
@@ -39,7 +39,11 @@ class SidebarTabStrip extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: t == current ? tokens.globalFocus : const Color(0x00000000), width: 2)),
|
||||
),
|
||||
child: ClideText(_label(t), fontSize: clideFontSmall, color: t == current || hovered ? tokens.globalForeground : tokens.globalTextMuted),
|
||||
child: ClideText(
|
||||
_label(context, t),
|
||||
fontSize: clideFontSmall,
|
||||
color: t == current || hovered ? tokens.globalForeground : tokens.globalTextMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -49,9 +53,18 @@ class SidebarTabStrip extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
String _label(SidebarTab t) => switch (t) {
|
||||
SidebarTab.activity => 'Activity',
|
||||
SidebarTab.team => memberCount == 0 ? 'Team' : 'Team · $memberCount',
|
||||
SidebarTab.config => 'Config',
|
||||
String _label(BuildContext context, SidebarTab t) => switch (t) {
|
||||
SidebarTab.activity => ClideSettings.i18n.string(context, 'tabStrip.activity', namespace: 'builtin.claude', placeholder: 'Activity'),
|
||||
SidebarTab.team =>
|
||||
memberCount == 0
|
||||
? ClideSettings.i18n.string(context, 'tabStrip.team', namespace: 'builtin.claude', placeholder: 'Team')
|
||||
: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'tabStrip.team.count',
|
||||
namespace: 'builtin.claude',
|
||||
placeholder: 'Team · $memberCount',
|
||||
replacers: [I18nReplacer(from: '{count}', replace: '$memberCount')],
|
||||
),
|
||||
SidebarTab.config => ClideSettings.i18n.string(context, 'tabStrip.config', namespace: 'builtin.claude', placeholder: 'Config'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class TaskRow extends StatelessWidget {
|
||||
if (broker != null && broker!.members.length > 1)
|
||||
MetaIconButton(
|
||||
painter: PhosphorIcons.byName('arrow-clockwise'),
|
||||
tooltip: 'Reassign task',
|
||||
tooltip: ClideSettings.i18n.string(context, 'taskRow.reassign', namespace: 'builtin.claude', placeholder: 'Reassign task'),
|
||||
color: tokens.globalTextMuted,
|
||||
onTap: () => _reassign(context),
|
||||
),
|
||||
|
||||
@@ -50,7 +50,7 @@ class TeamTabView extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = ClideSettings.theme.of(context).surface;
|
||||
if (members.isEmpty) {
|
||||
return metaPlaceholder('No team active.');
|
||||
return metaPlaceholder(ClideSettings.i18n.string(context, 'team.empty', namespace: 'builtin.claude', placeholder: 'No team active.'));
|
||||
}
|
||||
final children = <Widget>[
|
||||
for (final m in members)
|
||||
@@ -71,7 +71,7 @@ class TeamTabView extends StatelessWidget {
|
||||
|
||||
if (tasks.isNotEmpty) {
|
||||
children.add(const SizedBox(height: 12));
|
||||
children.add(_taskSection(tokens));
|
||||
children.add(_taskSection(context, tokens));
|
||||
}
|
||||
|
||||
// MESSAGES section (T-180): live broker chat feed + quick-post composer.
|
||||
@@ -85,11 +85,15 @@ class TeamTabView extends StatelessWidget {
|
||||
return ListView(padding: const EdgeInsets.all(12), children: children);
|
||||
}
|
||||
|
||||
Widget _taskSection(SurfaceTokens tokens) {
|
||||
Widget _taskSection(BuildContext context, SurfaceTokens tokens) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClideText('TASKS', fontSize: clideFontSmall, color: tokens.globalTextMuted),
|
||||
ClideText(
|
||||
ClideSettings.i18n.string(context, 'team.section.tasks', namespace: 'builtin.claude', placeholder: 'TASKS'),
|
||||
fontSize: clideFontSmall,
|
||||
color: tokens.globalTextMuted,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
for (final t in tasks) TaskRow(task: t, members: members, broker: orchestrator?.broker),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user