feat(claude): reach bypass via Ctrl/Cmd+Shift+M + shift-click (T-510)
Restores the original intent that T-226's refinement wording lost: the shift modifier is the explicit opt-in for bypassPermissions, not a separate confirm flow. Plain Ctrl/Cmd+M keeps cycling the safe trio; Ctrl/Cmd+Shift+M cycles the full list. The composer menu's bypass row was permanently disabled, deferring to "the cockpit's confirmed path" — but that roster is ghost-fed (T-396), so bypass was unreachable from the primary session's UI entirely. The row now no-ops on a plain click (menu stays open) and selects on shift-click, with a hint naming the gesture. Roster badge and /permissions paths unchanged. lib/test_app.dart is a format-only follow-up to the previous commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -373,6 +373,39 @@ void main() {
|
||||
await tester.pump();
|
||||
expect(cycles, 0);
|
||||
});
|
||||
|
||||
testWidgets('Ctrl+Shift+M fires onCycleModeFull, not onCycleMode (T-510)', (tester) async {
|
||||
var safe = 0, full = 0;
|
||||
await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onCycleMode: () => safe++, onCycleModeFull: () => full++)));
|
||||
await tester.tap(find.byType(EditableText));
|
||||
await tester.pump();
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyM);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.pump();
|
||||
|
||||
expect(full, 1);
|
||||
expect(safe, 0);
|
||||
expect(tester.widget<EditableText>(find.byType(EditableText)).controller.text, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('plain Ctrl+M still cycles the safe trio only', (tester) async {
|
||||
var safe = 0, full = 0;
|
||||
await tester.pumpWidget(harness(f, ClaudeComposer(onSubmit: (_) {}, onCycleMode: () => safe++, onCycleModeFull: () => full++)));
|
||||
await tester.tap(find.byType(EditableText));
|
||||
await tester.pump();
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.keyM);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.pump();
|
||||
|
||||
expect(safe, 1);
|
||||
expect(full, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('ClaudeComposer external focus node (T-227)', () {
|
||||
|
||||
@@ -90,6 +90,20 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('nextPermissionMode (T-510)', () {
|
||||
test('cycles the full list including bypass and wraps', () {
|
||||
expect(nextPermissionMode('default'), 'acceptEdits');
|
||||
expect(nextPermissionMode('acceptEdits'), 'plan');
|
||||
expect(nextPermissionMode('plan'), 'bypassPermissions');
|
||||
expect(nextPermissionMode('bypassPermissions'), 'default');
|
||||
});
|
||||
|
||||
test('unknown restarts at default', () {
|
||||
expect(nextPermissionMode('whatever'), 'default');
|
||||
expect(kFullPermissionCycle, contains('bypassPermissions'));
|
||||
});
|
||||
});
|
||||
|
||||
group('statusSegmentsAroundMode (T-226)', () {
|
||||
test('splits model (leading) from ctx/cost/rate (trailing), mode excluded', () {
|
||||
const s = SessionStatus(model: 'claude-opus-4-7', permissionMode: 'plan', contextTokens: 21000, cost: 0.05);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/// Tests for the composer permission-mode control (T-275): opens a menu of the
|
||||
/// safe trio + a disabled bypass row, selecting sets the mode, and it coexists
|
||||
/// with the composer's Stop row while busy.
|
||||
/// safe trio + a shift-click-gated bypass row (T-510), selecting sets the
|
||||
/// mode, and it coexists with the composer's Stop row while busy.
|
||||
library;
|
||||
|
||||
import 'package:clide/builtin/claude/src/claude_composer.dart';
|
||||
import 'package:clide/builtin/claude/src/permission_mode_control.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
@@ -48,7 +49,7 @@ void main() {
|
||||
expect(find.text('accept-edits'), findsNothing, reason: 'menu closes on select');
|
||||
});
|
||||
|
||||
testWidgets('the bypass row is disabled — selecting it does nothing', (tester) async {
|
||||
testWidgets('a plain click on the bypass row does nothing and keeps the menu open', (tester) async {
|
||||
var picked = '';
|
||||
await pump(tester, 'default', (m) => picked = m);
|
||||
await tester.pump();
|
||||
@@ -56,7 +57,24 @@ void main() {
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('bypass'));
|
||||
await tester.pump();
|
||||
expect(picked, '', reason: 'bypass stays behind the cockpit guard (T-181)');
|
||||
expect(picked, '', reason: 'bypass is never a plain click away (T-510)');
|
||||
expect(find.text('bypass'), findsOneWidget, reason: 'menu stays open for a retry with shift');
|
||||
});
|
||||
|
||||
testWidgets('shift-click on the bypass row selects it and closes the menu (T-510)', (tester) async {
|
||||
var picked = '';
|
||||
await pump(tester, 'default', (m) => picked = m);
|
||||
await tester.pump();
|
||||
await tester.tap(find.byType(PermissionModeControl));
|
||||
await tester.pump();
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.tap(find.text('bypass'));
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.pump();
|
||||
|
||||
expect(picked, 'bypassPermissions');
|
||||
expect(find.text('bypass'), findsNothing, reason: 'menu closes after the shift-select');
|
||||
});
|
||||
|
||||
testWidgets('per-mode helpers map labels/colours/icons', (tester) async {
|
||||
|
||||
Reference in New Issue
Block a user