fold the Deny & simplify denial instead of a red error (T-340)
A user-initiated denial (Deny & simplify) came back as an isError tool_result and rendered as a prominent expanded-red "Bash · error" block — pure noise, since the user chose it. It now folds to a muted, collapsed "denied" card. Built as a reusable filter rather than string-matching the note: DenyTool carries a `quiet` flag, the session collects quiet denials' tool_use_ids, and ConversationView renders any error whose id is in that set folded + muted. Genuine tool failures (ids not in the set) keep the expanded-red treatment. Adding future "expected error" cases is just adding ids. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -149,7 +149,10 @@ void main() {
|
||||
tearDown(() => f.dispose());
|
||||
|
||||
Future<ConversationController> pumpWith(WidgetTester tester, List<ConversationItem> items,
|
||||
{Set<String> hiddenToolUseIds = const {}, Map<String, bool> toolUseOutcomes = const {}, FoldLevel foldLevel = FoldLevel.none}) async {
|
||||
{Set<String> hiddenToolUseIds = const {},
|
||||
Map<String, bool> toolUseOutcomes = const {},
|
||||
Set<String> quietErrorToolUseIds = const {},
|
||||
FoldLevel foldLevel = FoldLevel.none}) async {
|
||||
tester.view.physicalSize = const Size(900, 700);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(() {
|
||||
@@ -166,7 +169,12 @@ void main() {
|
||||
Builder(
|
||||
builder: (ctx) => MediaQuery(
|
||||
data: MediaQuery.of(ctx).copyWith(disableAnimations: true),
|
||||
child: ConversationView(controller: c, hiddenToolUseIds: hiddenToolUseIds, toolUseOutcomes: toolUseOutcomes, foldLevel: foldLevel),
|
||||
child: ConversationView(
|
||||
controller: c,
|
||||
hiddenToolUseIds: hiddenToolUseIds,
|
||||
toolUseOutcomes: toolUseOutcomes,
|
||||
quietErrorToolUseIds: quietErrorToolUseIds,
|
||||
foldLevel: foldLevel),
|
||||
),
|
||||
),
|
||||
));
|
||||
@@ -697,6 +705,33 @@ void main() {
|
||||
expect(find.text('Bash · error'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a quiet (user-initiated) denial folds to a muted "denied" card (T-340)', (tester) async {
|
||||
await pumpWith(
|
||||
tester,
|
||||
[
|
||||
_tool('Bash', {'command': 'rm -rf x'}),
|
||||
_result('Denied — too complex\nretry simpler', isError: true),
|
||||
],
|
||||
quietErrorToolUseIds: {'x1'}, // the toolUseId shared by _tool/_result
|
||||
);
|
||||
// Labelled "denied", not the loud "error", and folded by default so the
|
||||
// body (its second line) is hidden behind a collapsed summary.
|
||||
expect(find.text('Bash · denied'), findsOneWidget);
|
||||
expect(find.text('Bash · error'), findsNothing);
|
||||
expect(find.textContaining('retry simpler'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('a genuine error (not in the quiet set) stays expanded red (T-340)', (tester) async {
|
||||
await pumpWith(tester, [
|
||||
_tool('Bash', {'command': 'rm -rf x'}),
|
||||
_result('Denied — too complex\nretry simpler', isError: true),
|
||||
]);
|
||||
// Same content, but not flagged quiet → the normal expanded error path.
|
||||
expect(find.text('Bash · error'), findsOneWidget);
|
||||
expect(find.text('Bash · denied'), findsNothing);
|
||||
expect(find.textContaining('retry simpler'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('result without a paired tool_use uses plain "result" label (T-168)', (tester) async {
|
||||
// Orphan result (no matching tool_use in the controller).
|
||||
await pumpWith(tester, [
|
||||
|
||||
Reference in New Issue
Block a user