surface clide-owned commands in the slash typeahead

The composer sourced its slash suggestions only from the CLI probe
(activeClaudeConfig.slashCommands), which never advertises clide-owned
commands, so /resume and /fork were missing from the typeahead. Union
kClideOwnedCommands onto the command source unconditionally — whether a
caller supplies a resolver or the default probe is used — de-duped via a
Set so /clear (in both) appears once.

T-162.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-31 16:17:14 +02:00
co-authored by Claude
parent d0e169eba2
commit 3c9cc4cbae
3 changed files with 52 additions and 1 deletions
+3
View File
@@ -18,6 +18,9 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
### Fixed
- The composer slash typeahead now lists clide-owned commands — `/resume` and
`/fork` (and `/clear`) surface even though the CLI probe doesn't advertise
them, unioned onto whatever command source the composer uses. (T-162)
- Clicking a decision opens it in the decision reader again — the decisions
extension no longer tears down and re-contributes its panel tab on every
selection; it activates a static tab and reveals the panel like the ticket
+8 -1
View File
@@ -116,7 +116,14 @@ class _ClaudeComposerState extends State<ClaudeComposer> {
if (!_focus.hasFocus) _closeTypeahead();
}
Iterable<String> _commands() => (widget.slashCommandsResolver ?? () => activeClaudeConfig?.slashCommands ?? const <String>[])();
Iterable<String> _commands() {
// Always union the clide-owned commands (/clear, /resume, /fork) onto the
// command source — whether that's a caller-supplied resolver or the default
// ClaudeConfig probe — since the CLI probe never advertises them (T-162).
// The Set literal de-dupes (e.g. 'clear' present in both).
final base = widget.slashCommandsResolver?.call() ?? activeClaudeConfig?.slashCommands ?? const <String>[];
return {...base, ...kClideOwnedCommands};
}
/// Recompute the active slash query + suggestions from the current text and
/// caret, opening/updating/closing the typeahead overlay accordingly.
@@ -5,6 +5,7 @@ library;
import 'package:clide/builtin/claude/src/claude_composer.dart';
import 'package:clide/builtin/claude/src/clipboard_paste.dart';
import 'package:clide/builtin/claude/src/slash_commands.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -292,5 +293,45 @@ void main() {
));
expect(find.text('Stop ⎋'), findsNothing);
});
// T-162: clide-owned commands surface in typeahead even when absent from the
// CLI probe (slashCommandsResolver).
testWidgets('clide-owned /resume surfaces even when absent from the probe list', (tester) async {
// The probe list (CLI-sourced) only has 'model' — no 'resume' or 'fork'.
await pumpWithCommands(tester, ['model']);
await tester.enterText(find.byType(EditableText), '/res');
await tester.pump();
// /resume must appear (sourced from kClideOwnedCommands).
expect(find.text('/resume'), findsOneWidget);
// /model must not appear (doesn't match '/res').
expect(find.text('/model'), findsNothing);
});
testWidgets('clide-owned /clear surfaces without duplicate when also in probe', (tester) async {
// 'clear' is in both the probe list AND kClideOwnedCommands.
await pumpWithCommands(tester, ['clear', 'model']);
await tester.enterText(find.byType(EditableText), '/cl');
await tester.pump();
// /clear must appear exactly once (filterSlashCommands de-dupes via seen set).
expect(find.text('/clear'), findsOneWidget);
});
testWidgets('clide-owned commands are reachable via the default resolver', (tester) async {
// No slashCommandsResolver → default path; kClideOwnedCommands must be included.
final submitted = <String>[];
await tester.pumpWidget(harness(
f,
ClaudeComposer(onSubmit: submitted.add),
));
await tester.enterText(find.byType(EditableText), '/fo');
await tester.pump();
// /fork is a kClideOwnedCommands member; it must appear without a probe.
expect(find.text('/fork'), findsOneWidget);
// Sanity: kClideOwnedCommands is the source (not a coincidence).
expect(kClideOwnedCommands, contains('fork'));
});
});
}