lock in slash typeahead hyphen + Tab-accept behaviour (T-278)
Both T-278 issues — typing '-' breaking the list, and flaky Tab-to-complete — were resolved by the D-88 ClideTypeahead migration (T-286): the popover now narrows live as you type (ValueNotifier-bridged suggestions) and the field keeps focus (captureFocus:false), so '-' filters normally and Tab reliably accepts the highlight. Add regression coverage: hyphenated queries in slash_commands_test and a composer widget test that types through a hyphen then Tab-completes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -950,3 +950,4 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
|
||||
|
||||
DONE: The editor honours .editorconfig end-to-end. New source-agnostic EditorSettings model (editor_settings.dart) + composition seam (editor_settings_resolver.dart); .editorconfig demoted to a source (editorconfig.dart, own INI parser + glob matcher, root/nearest-wins precedence, no deps). Registry resolves on open and re-resolves open buffers when a .editorconfig is saved in-app (editor.settings-changed; a save hook, not an fs-watch). Editor: Tab/Shift+Tab indent + max_line_length ruler; save applies end_of_line/trim_trailing_whitespace/insert_final_newline. Follow-ups: T-290 (edit from settings panel), T-291 (external fs-watch). 100% coverage on new model/resolver/parser.', NULL, '2026-06-09 10:46:41', '2026-06-09 10:46:41', '2026-06-09 10:46:41', NULL, '96304f51da31f3ea356faa207ea1873e', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-29', 'status', 'in_progress', 'done', NULL, '2026-06-09 10:46:41', '2026-06-09 10:46:41', '2026-06-09 10:46:41', NULL, '9419d87a1b9d0cbb938399638d6a1453', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'status', 'ready', 'in_progress', NULL, '2026-06-09 14:59:45', '2026-06-09 14:59:45', '2026-06-09 14:59:45', NULL, '57787b04f8886096911590b3e6357d90', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -2416,3 +2416,12 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority,
|
||||
Root cause: these tests spawn a real /bin/sh via NativePty and wait on the reader ISOLATE to deliver bytes. Under heavy CPU contention the reader isolate is starved and the 20s ioTimeout (test/helpers/timeouts.dart) elapses before the shell''s first byte / echo response is delivered. Already hardened once (T-108 swapped wall-clock sleeps for event-driven waits; first-byte Completer + 25ms-poll _waitForBuffer), but the isolate-scheduling assumption still breaks under load.
|
||||
|
||||
These tests are tagged ''pty'' and are meant to run in the gate''s serial pass. Fix options: (a) ensure the parallel coverage pass EXCLUDES tag:pty (run pty only in the serial pass) so they never compete for isolate scheduling — verify ci/test.sh / dart_test.yaml tag routing; (b) harden against isolate starvation (e.g. give the reader isolate priority, or assert on delivery via a more robust signal); (c) document that ''flutter test --coverage'' over the whole tree is not a supported invocation (use make test + the gate). Prefer (a). Refs: T-108, T-96 (reader-isolate hangs), T-192 (gate parallel/serial passes), D-23 (test pyramid).', 'backlog', 'medium', NULL, NULL, 'D-23', '2026-06-09 14:57:32', '2026-06-09 14:57:32', NULL, 'aa9a060645d02824f2d6c5389b812c8b', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-278', 'bug', 'T-276', 'Slash-command typeahead breaks on ''-''; Tab-to-complete is flaky', 'Two issues in the Claude composer slash-command typeahead (T-152/T-153, lib/builtin/claude/src/slash_commands.dart + claude_composer.dart):
|
||||
|
||||
1. Typing a ''-'' character breaks the typeahead. Many command names contain hyphens (e.g. clear-context-style names, custom commands), but typing ''-'' appears to drop/empty the suggestion list or mis-parse the active query. Suspect activeSlashQuery''s token run or filterSlashCommands prefix matching not handling ''-'' (or the composer treating ''-'' as a boundary).
|
||||
|
||||
2. Tab to accept the highlighted suggestion responds flakily — sometimes it completes, sometimes nothing happens. Suspect a focus/key-handling race between the composer''s key handler and the typeahead overlay, or Tab being consumed by focus traversal before the accept intent fires.
|
||||
|
||||
Repro: open the composer, type ''/'' then a command fragment; (a) include a ''-'' and watch the list; (b) arrow-select an item and press Tab repeatedly.
|
||||
|
||||
Acceptance: ''-'' is treated as a normal command-name character (suggestions keep filtering through hyphens); Tab reliably completes the highlighted suggestion every time (insert via completeSlash). Add/extend unit tests in slash_commands_test.dart for hyphenated queries and a composer widget test for Tab-accept.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-08 09:20:00', '2026-06-09 14:59:45', NULL, 'f0cae2f4ba80ad98e2115d4d3cf1a11f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
@@ -191,6 +191,28 @@ void main() {
|
||||
expect(tester.widget<EditableText>(find.byType(EditableText)).controller.text, '/clear ');
|
||||
});
|
||||
|
||||
testWidgets('a hyphenated command keeps filtering through the dash and Tab completes it (T-278)', (tester) async {
|
||||
await pumpWithCommands(tester, ['add-dir', 'add-context', 'model']);
|
||||
await tester.enterText(find.byType(EditableText), '/add-');
|
||||
await tester.pump();
|
||||
await tester.pump(); // ClideTypeahead inserts the popover post-frame
|
||||
expect(find.text('/add-dir'), findsOneWidget);
|
||||
expect(find.text('/add-context'), findsOneWidget);
|
||||
expect(find.text('/model'), findsNothing);
|
||||
|
||||
// Typing further through the hyphen narrows the list (it does not empty it).
|
||||
await tester.enterText(find.byType(EditableText), '/add-d');
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
expect(find.text('/add-dir'), findsOneWidget);
|
||||
expect(find.text('/add-context'), findsNothing);
|
||||
|
||||
// Tab reliably accepts the highlighted suggestion.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.pump();
|
||||
expect(tester.widget<EditableText>(find.byType(EditableText)).controller.text, '/add-dir ');
|
||||
});
|
||||
|
||||
testWidgets('Escape dismisses the typeahead', (tester) async {
|
||||
await pumpWithCommands(tester, ['model']);
|
||||
await tester.enterText(find.byType(EditableText), '/mo');
|
||||
|
||||
@@ -69,6 +69,12 @@ void main() {
|
||||
// Cursor sits right after "/mo" inside "/model".
|
||||
expect(activeSlashQuery('/model', 3), const SlashQuery(start: 0, query: 'mo'));
|
||||
});
|
||||
|
||||
test('hyphens are part of the command token, not a boundary (T-278)', () {
|
||||
expect(activeSlashQuery('/add-dir', 8), const SlashQuery(start: 0, query: 'add-dir'));
|
||||
expect(activeSlashQuery('/add-', 5), const SlashQuery(start: 0, query: 'add-'));
|
||||
expect(activeSlashQuery('go /output-st', 13), const SlashQuery(start: 3, query: 'output-st'));
|
||||
});
|
||||
});
|
||||
|
||||
group('filterSlashCommands', () {
|
||||
@@ -86,6 +92,14 @@ void main() {
|
||||
test('de-duplicates', () {
|
||||
expect(filterSlashCommands('p', ['pql', 'pql', 'plan']), ['plan', 'pql']);
|
||||
});
|
||||
|
||||
test('keeps filtering through a hyphen in the query (T-278)', () {
|
||||
const hyphenated = ['add-dir', 'add-context', 'agents', 'output-style'];
|
||||
// Typing the '-' narrows rather than emptying the list.
|
||||
expect(filterSlashCommands('add', hyphenated), ['add-context', 'add-dir']);
|
||||
expect(filterSlashCommands('add-', hyphenated), ['add-context', 'add-dir']);
|
||||
expect(filterSlashCommands('add-d', hyphenated), ['add-dir']);
|
||||
});
|
||||
});
|
||||
|
||||
group('completeSlash', () {
|
||||
|
||||
Reference in New Issue
Block a user